Include the uxntal-test-suite submodule and add the generator crate
This commit is contained in:
parent
4ae2372c02
commit
a242f7fcfb
|
@ -0,0 +1,3 @@
|
|||
[submodule "tests/suite"]
|
||||
path = tests/suite
|
||||
url = git@github.com:karolbelina/uxntal-test-suite.git
|
|
@ -2,6 +2,12 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61604a8f862e1d5c3229fdd78f8b02c68dcf73a4c4b05fd636d12240aaa242c1"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
|
@ -18,6 +24,16 @@ dependencies = [
|
|||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generator"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.27"
|
||||
|
@ -41,6 +57,7 @@ name = "ruxnasm"
|
|||
version = "0.2.0"
|
||||
dependencies = [
|
||||
"codespan-reporting",
|
||||
"generator",
|
||||
"test-case",
|
||||
]
|
||||
|
||||
|
|
|
@ -11,6 +11,9 @@ keywords = ["assembler", "uxn", "uxntal"]
|
|||
categories = ["command-line-utilities", "compilers"]
|
||||
exclude = [".github", ".vscode", "docs"]
|
||||
|
||||
[workspace]
|
||||
members = ["tests/generator"]
|
||||
|
||||
[[bin]]
|
||||
name = "ruxnasm"
|
||||
required-features = ["bin"]
|
||||
|
@ -26,3 +29,4 @@ codespan-reporting = { version = "0.11.1", optional = true }
|
|||
|
||||
[dev-dependencies]
|
||||
test-case = "1.1.0"
|
||||
generator = { path = "tests/generator" }
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "generator"
|
||||
version = "0.0.0"
|
||||
authors = ["Karol Belina <karolbelina@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0", features = ["full"] }
|
|
@ -0,0 +1,27 @@
|
|||
mod test;
|
||||
mod tests;
|
||||
mod tests_mod;
|
||||
mod utils;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use test::Test;
|
||||
use tests::Tests;
|
||||
use tests_mod::TestsMod;
|
||||
|
||||
#[proc_macro]
|
||||
pub fn generate_tests(_: TokenStream) -> TokenStream {
|
||||
let tests = match Tests::discover("tests/suite") {
|
||||
Ok(tests) => tests,
|
||||
Err(err) => {
|
||||
panic!("\n{:?}", err);
|
||||
}
|
||||
};
|
||||
|
||||
let tests = tests.expand();
|
||||
|
||||
(quote! {
|
||||
#tests
|
||||
})
|
||||
.into()
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
use crate::utils::escape_name;
|
||||
use anyhow::{bail, Result};
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use std::path::{Component, Path, PathBuf};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Test {
|
||||
name: String,
|
||||
dirs: Vec<String>,
|
||||
path: PathBuf,
|
||||
}
|
||||
|
||||
impl Test {
|
||||
pub fn load(path: impl AsRef<Path>) -> Result<Self> {
|
||||
let path = path.as_ref();
|
||||
let mut components = human_readable_components(path).collect::<Vec<_>>();
|
||||
let name = components.pop().unwrap();
|
||||
let relative_path = Path::new("tests/suite").join(path);
|
||||
|
||||
if !relative_path.join("input.tal").exists() {
|
||||
bail!("Test is missing the `input.tal` file");
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
name,
|
||||
dirs: components,
|
||||
path: relative_path,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn dirs(&self) -> impl Iterator<Item = String> + '_ {
|
||||
self.dirs.iter().cloned()
|
||||
}
|
||||
|
||||
pub fn expand(&self) -> TokenStream {
|
||||
let name = escape_name(&self.name);
|
||||
let input_path_string = self.path.join("input.tal").display().to_string();
|
||||
let output_path_string = self.path.join("output.rom").display().to_string();
|
||||
|
||||
quote! {
|
||||
#[test]
|
||||
fn #name() {
|
||||
let input_path = ::std::path::Path::new(#input_path_string);
|
||||
let output_path = ::std::path::Path::new(#output_path_string);
|
||||
|
||||
let input = ::std::fs::read(input_path).unwrap();
|
||||
let expected_output = if output_path.exists() {
|
||||
Some(::std::fs::read(output_path).unwrap())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let actual_output = match assemble(&input) {
|
||||
Ok((binary, _)) => Some(binary),
|
||||
Err((errors, _)) => {
|
||||
println!("{:?}", errors);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
assert_eq!(actual_output, expected_output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn human_readable_components<'a>(path: &'a Path) -> impl Iterator<Item = String> + 'a {
|
||||
path.components().flat_map(|component| {
|
||||
if let Component::Normal(dir) = component {
|
||||
Some(dir.to_string_lossy().into_owned())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
use crate::{Test, TestsMod};
|
||||
use anyhow::{Context as _, Result};
|
||||
use proc_macro2::TokenStream;
|
||||
use std::{
|
||||
fs,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Tests {
|
||||
tests: Vec<Test>,
|
||||
}
|
||||
|
||||
impl Tests {
|
||||
pub fn discover(dir: impl AsRef<Path>) -> Result<Self> {
|
||||
let tests = fs::read_to_string(dir.as_ref().join("index"))
|
||||
.with_context(|| format!("Couldn't find the test suite's index"))?
|
||||
.lines()
|
||||
.map(|line| line.into())
|
||||
.map(|path: PathBuf| {
|
||||
Test::load(&path)
|
||||
.with_context(|| format!("Couldn't load test: {}", path.display(),))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
Ok(Self { tests })
|
||||
}
|
||||
|
||||
pub fn expand(&self) -> TokenStream {
|
||||
TestsMod::build(&self.tests).expand()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
use crate::utils::escape_name;
|
||||
use crate::Test;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::quote;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct TestsMod<'a> {
|
||||
tests: BTreeSet<&'a Test>,
|
||||
children: BTreeMap<String, Self>,
|
||||
}
|
||||
|
||||
impl<'a> TestsMod<'a> {
|
||||
pub fn build(tests: &'a [Test]) -> Self {
|
||||
tests.iter().fold(Self::default(), |mut this, test| {
|
||||
this.add(test);
|
||||
this
|
||||
})
|
||||
}
|
||||
|
||||
fn add(&mut self, test: &'a Test) {
|
||||
let mut this = self;
|
||||
|
||||
for test_dir in test.dirs() {
|
||||
this = this.children.entry(test_dir).or_default();
|
||||
}
|
||||
|
||||
this.tests.insert(test);
|
||||
}
|
||||
|
||||
pub fn expand(&self) -> TokenStream {
|
||||
let tests = self.expand_tests();
|
||||
let children = self.expand_children();
|
||||
|
||||
quote! {
|
||||
#(#tests)*
|
||||
#(#children)*
|
||||
}
|
||||
}
|
||||
|
||||
fn expand_tests(&self) -> impl Iterator<Item = TokenStream> + '_ {
|
||||
self.tests.iter().map(|test| test.expand())
|
||||
}
|
||||
|
||||
fn expand_children(&self) -> impl Iterator<Item = TokenStream> + '_ {
|
||||
self.children.iter().map(|(name, children)| {
|
||||
let name = escape_name(name);
|
||||
let children = children.expand();
|
||||
|
||||
quote! {
|
||||
mod #name {
|
||||
use super::*;
|
||||
|
||||
#children
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
use proc_macro2::{Ident, Span};
|
||||
|
||||
pub fn escape_name(name: &str) -> Ident {
|
||||
if name.is_empty() {
|
||||
return Ident::new("_empty", Span::call_site());
|
||||
}
|
||||
|
||||
let mut last_under = false;
|
||||
let mut ident: String = name
|
||||
.to_ascii_lowercase()
|
||||
.chars()
|
||||
.filter_map(|c| match c {
|
||||
c if c.is_alphanumeric() => {
|
||||
last_under = false;
|
||||
Some(c.to_ascii_lowercase())
|
||||
}
|
||||
_ if !last_under => {
|
||||
last_under = true;
|
||||
Some('_')
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
|
||||
if !ident.starts_with(|c: char| c == '_' || c.is_ascii_alphabetic()) {
|
||||
ident = format!("_{}", ident);
|
||||
}
|
||||
|
||||
Ident::new(&ident, Span::call_site())
|
||||
}
|
|
@ -1,33 +1,3 @@
|
|||
use ruxnasm::{assemble, Error, Error::*};
|
||||
use test_case::test_case;
|
||||
use ruxnasm::assemble;
|
||||
|
||||
macro_rules! ev {
|
||||
[] => {{
|
||||
Vec::<Error>::new()
|
||||
}};
|
||||
|
||||
[$($error: expr),+] => {{
|
||||
let mut vec = Vec::<Error>::new();
|
||||
$( vec.push($error); )+
|
||||
vec
|
||||
}}
|
||||
}
|
||||
|
||||
#[test_case("(" => ev![NoMatchingClosingParenthesis { span: 0..1 }] ; "test 1")]
|
||||
#[test_case("()" => ev![] ; "test 2")]
|
||||
#[test_case("( )" => ev![] ; "test 3")]
|
||||
#[test_case("( )" => ev![] ; "test 4")]
|
||||
#[test_case("( ( )" => ev![NoMatchingClosingParenthesis { span: 0..1 }] ; "test 5")]
|
||||
#[test_case("( ( ) )" => ev![] ; "test 6")]
|
||||
#[test_case("( () )" => ev![] ; "test 7")]
|
||||
#[test_case("(())" => ev![] ; "test 8")]
|
||||
#[test_case(")" => ev![NoMatchingOpeningParenthesis { span: 0..1 }] ; "test 9")]
|
||||
#[test_case("( ) )" => ev![NoMatchingOpeningParenthesis { span: 4..5 }] ; "test 10")]
|
||||
#[test_case("( ))" => ev![NoMatchingOpeningParenthesis { span: 3..4 }] ; "test 11")]
|
||||
#[test_case("() )" => ev![NoMatchingOpeningParenthesis { span: 3..4 }] ; "test 12")]
|
||||
fn scanner_error_tests(source: &str) -> Vec<Error> {
|
||||
match assemble(source.as_bytes()) {
|
||||
Ok(_) => Vec::new(),
|
||||
Err((errors, _)) => errors,
|
||||
}
|
||||
}
|
||||
generator::generate_tests!();
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 79f3d04e97c5fd79321c64d827386899326a0abf
|
Loading…
Reference in New Issue