#![deny(missing_docs)]
pub extern crate anyhow;
use std::{
borrow::Borrow, collections::HashSet, convert::TryFrom, fs, path::Path,
};
use {
anyhow::{bail, Context, Result},
bstr::{BString, ByteSlice, ByteVec},
serde::Deserialize,
};
const ENV_REGEX_TEST: &str = "REGEX_TEST";
const ENV_REGEX_TEST_VERBOSE: &str = "REGEX_TEST_VERBOSE";
#[derive(Clone, Debug, Deserialize)]
pub struct RegexTests {
#[serde(default, rename = "test")]
tests: Vec<RegexTest>,
#[serde(skip)]
seen: HashSet<String>,
}
impl RegexTests {
pub fn new() -> RegexTests {
RegexTests { tests: vec![], seen: HashSet::new() }
}
pub fn load<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
let path = path.as_ref();
let data = fs::read(path)
.with_context(|| format!("failed to read {}", path.display()))?;
let group_name = path
.file_stem()
.with_context(|| {
format!("failed to get file name of {}", path.display())
})?
.to_str()
.with_context(|| {
format!("invalid UTF-8 found in {}", path.display())
})?;
self.load_slice(&group_name, &data)
.with_context(|| format!("error loading {}", path.display()))?;
Ok(())
}
pub fn load_slice(&mut self, group_name: &str, data: &[u8]) -> Result<()> {
let data = std::str::from_utf8(&data).with_context(|| {
format!("data in {} is not valid UTF-8", group_name)
})?;
let mut index = 1;
let mut tests: RegexTests =
toml::from_str(&data).with_context(|| {
format!("error decoding TOML for '{}'", group_name)
})?;
for t in &mut tests.tests {
t.group = group_name.to_string();
if t.name.is_empty() {
t.name = format!("{}", index);
index += 1;
}
t.full_name = format!("{}/{}", t.group, t.name);
if t.unescape {
t.haystack = BString::from(Vec::unescape_bytes(
t.haystack.to_str().unwrap(),
));
}
if t.line_terminator.is_empty() {
t.line_terminator = BString::from("\n");
} else {
t.line_terminator = BString::from(Vec::unescape_bytes(
t.line_terminator.to_str().unwrap(),
));
anyhow::ensure!(
t.line_terminator.len() == 1,
"line terminator '{:?}' has length not equal to 1",
t.line_terminator,
);
}
if self.seen.contains(t.full_name()) {
bail!("found duplicate tests for name '{}'", t.full_name());
}
self.seen.insert(t.full_name().to_string());
}
self.tests.extend(tests.tests);
Ok(())
}
pub fn iter(&self) -> RegexTestsIter {
RegexTestsIter(self.tests.iter())
}
}
#[derive(Clone, Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RegexTest {
#[serde(skip)]
group: String,
#[serde(default)]
name: String,
#[serde(skip)]
additional_name: String,
#[serde(skip)]
full_name: String,
regex: RegexesFormat,
haystack: BString,
bounds: Option<Span>,
matches: Vec<Captures>,
#[serde(rename = "match-limit")]
match_limit: Option<usize>,
#[serde(default = "default_true")]
compiles: bool,
#[serde(default)]
anchored: bool,
#[serde(default, rename = "case-insensitive")]
case_insensitive: bool,
#[serde(default)]
unescape: bool,
#[serde(default = "default_true")]
unicode: bool,
#[serde(default = "default_true")]
utf8: bool,
#[serde(default, rename = "line-terminator")]
line_terminator: BString,
#[serde(default, rename = "match-kind")]
match_kind: MatchKind,
#[serde(default, rename = "search-kind")]
search_kind: SearchKind,
}
impl RegexTest {
pub fn group(&self) -> &str {
&self.group
}
pub fn name(&self) -> &str {
&self.name
}
pub fn additional_name(&self) -> &str {
&self.additional_name
}
pub fn full_name(&self) -> &str {
&self.full_name
}
pub fn regexes(&self) -> &[String] {
self.regex.patterns()
}
pub fn haystack(&self) -> &[u8] {
&self.haystack
}
pub fn bounds(&self) -> Span {
self.bounds.unwrap_or(Span { start: 0, end: self.haystack().len() })
}
pub fn match_limit(&self) -> Option<usize> {
self.match_limit
}
pub fn compiles(&self) -> bool {
self.compiles
}
pub fn anchored(&self) -> bool {
self.anchored
}
pub fn case_insensitive(&self) -> bool {
self.case_insensitive
}
pub fn unicode(&self) -> bool {
self.unicode
}
pub fn utf8(&self) -> bool {
self.utf8
}
pub fn line_terminator(&self) -> u8 {
self.line_terminator[0]
}
pub fn match_kind(&self) -> MatchKind {
self.match_kind
}
pub fn search_kind(&self) -> SearchKind {
self.search_kind
}
fn test(&self, regex: &mut CompiledRegex) -> TestResult {
match regex.matcher {
None => TestResult::skip(),
Some(ref mut match_regex) => match_regex(self),
}
}
fn with_additional_name(&self, name: &str) -> RegexTest {
let additional_name = name.to_string();
let full_name = format!("{}/{}", self.full_name, additional_name);
RegexTest { additional_name, full_name, ..self.clone() }
}
fn is_match(&self) -> bool {
!self.matches.is_empty()
}
fn which_matches(&self) -> Vec<usize> {
let mut seen = HashSet::new();
let mut ids = vec![];
for cap in self.matches.iter() {
if !seen.contains(&cap.id) {
seen.insert(cap.id);
ids.push(cap.id);
}
}
ids.sort();
ids
}
fn matches(&self) -> Vec<Match> {
let mut matches = vec![];
for cap in self.matches.iter() {
matches.push(cap.to_match());
}
matches
}
fn captures(&self) -> Vec<Captures> {
self.matches.clone()
}
}
pub struct CompiledRegex {
matcher: Option<Box<dyn FnMut(&RegexTest) -> TestResult + 'static>>,
}
impl CompiledRegex {
pub fn compiled(
matcher: impl FnMut(&RegexTest) -> TestResult + 'static,
) -> CompiledRegex {
CompiledRegex { matcher: Some(Box::new(matcher)) }
}
pub fn skip() -> CompiledRegex {
CompiledRegex { matcher: None }
}
pub fn is_skip(&self) -> bool {
self.matcher.is_none()
}
}
impl std::fmt::Debug for CompiledRegex {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
let status = match self.matcher {
None => "Skip",
Some(_) => "Run(...)",
};
f.debug_struct("CompiledRegex").field("matcher", &status).finish()
}
}
#[derive(Debug, Clone)]
pub struct TestResult {
kind: TestResultKind,
}
#[derive(Debug, Clone)]
enum TestResultKind {
Match(bool),
Which(Vec<usize>),
StartEnd(Vec<Match>),
Captures(Vec<Captures>),
Skip,
Fail { why: String },
}
impl TestResult {
pub fn matched(yes: bool) -> TestResult {
TestResult { kind: TestResultKind::Match(yes) }
}
pub fn which<I: IntoIterator<Item = usize>>(it: I) -> TestResult {
let mut which: Vec<usize> = it.into_iter().collect();
which.sort();
TestResult { kind: TestResultKind::Which(which) }
}
pub fn matches<I: IntoIterator<Item = Match>>(it: I) -> TestResult {
TestResult { kind: TestResultKind::StartEnd(it.into_iter().collect()) }
}
pub fn captures<I: IntoIterator<Item = Captures>>(it: I) -> TestResult {
TestResult { kind: TestResultKind::Captures(it.into_iter().collect()) }
}
pub fn skip() -> TestResult {
TestResult { kind: TestResultKind::Skip }
}
pub fn fail(why: &str) -> TestResult {
TestResult { kind: TestResultKind::Fail { why: why.to_string() } }
}
}
#[derive(Debug)]
pub struct TestRunner {
include: Vec<IncludePattern>,
results: RegexTestResults,
expanders: Vec<Expander>,
}
impl TestRunner {
pub fn new() -> Result<TestRunner> {
let mut runner = TestRunner {
include: vec![],
results: RegexTestResults::new(),
expanders: vec![],
};
for mut substring in read_env(ENV_REGEX_TEST)?.split(",") {
substring = substring.trim();
if substring.is_empty() {
continue;
}
if substring.starts_with("-") {
runner.blacklist(&substring[1..]);
} else {
runner.whitelist(substring);
}
}
Ok(runner)
}
pub fn assert(&mut self) {
self.results.assert();
}
pub fn whitelist(&mut self, substring: &str) -> &mut TestRunner {
self.include.push(IncludePattern {
blacklist: false,
substring: BString::from(substring),
});
self
}
pub fn whitelist_iter<I, S>(&mut self, substrings: I) -> &mut TestRunner
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
for substring in substrings {
self.whitelist(substring.as_ref());
}
self
}
pub fn blacklist(&mut self, substring: &str) -> &mut TestRunner {
self.include.push(IncludePattern {
blacklist: true,
substring: BString::from(substring),
});
self
}
pub fn blacklist_iter<I, S>(&mut self, substrings: I) -> &mut TestRunner
where
I: IntoIterator<Item = S>,
S: AsRef<str>,
{
for substring in substrings {
self.blacklist(substring.as_ref());
}
self
}
pub fn expand<S: AsRef<str>>(
&mut self,
additional_names: &[S],
predicate: impl FnMut(&RegexTest) -> bool + 'static,
) -> &mut TestRunner {
self.expanders.push(Expander {
predicate: Box::new(predicate),
additional_names: additional_names
.iter()
.map(|s| s.as_ref().to_string())
.collect(),
});
self
}
pub fn test_iter<I, T>(
&mut self,
it: I,
mut compile: impl FnMut(&RegexTest, &[String]) -> Result<CompiledRegex>,
) -> &mut TestRunner
where
I: IntoIterator<Item = T>,
T: Borrow<RegexTest>,
{
for test in it {
let test = test.borrow();
let mut additional = vec![];
for expander in &mut self.expanders {
if (expander.predicate)(test) {
for name in expander.additional_names.iter() {
additional.push(test.with_additional_name(name));
}
break;
}
}
if additional.is_empty() {
additional.push(test.to_owned());
}
for test in &additional {
if self.should_skip(test) {
self.results.skip(test);
continue;
}
self.test(test, |regexes| compile(test, regexes));
}
}
self
}
pub fn test(
&mut self,
test: &RegexTest,
mut compile: impl FnMut(&[String]) -> Result<CompiledRegex>,
) -> &mut TestRunner {
let mut compiled = match safe(|| compile(test.regexes())) {
Err(msg) => {
self.results.fail(
test,
RegexTestFailureKind::UnexpectedPanicCompile(msg),
);
return self;
}
Ok(Ok(compiled)) => compiled,
Ok(Err(err)) => {
if !test.compiles() {
self.results.pass(test);
} else {
self.results.fail(
test,
RegexTestFailureKind::CompileError { err },
);
}
return self;
}
};
if !test.compiles() && !compiled.is_skip() {
self.results.fail(test, RegexTestFailureKind::NoCompileError);
return self;
}
let result = match safe(|| test.test(&mut compiled)) {
Ok(result) => result,
Err(msg) => {
self.results.fail(
test,
RegexTestFailureKind::UnexpectedPanicSearch(msg),
);
return self;
}
};
match result.kind {
TestResultKind::Match(yes) => {
if yes == test.is_match() {
self.results.pass(test);
} else {
self.results.fail(test, RegexTestFailureKind::IsMatch);
}
}
TestResultKind::Which(which) => {
if which != test.which_matches() {
self.results
.fail(test, RegexTestFailureKind::Many { got: which });
} else {
self.results.pass(test);
}
}
TestResultKind::StartEnd(matches) => {
let expected = test.matches();
if expected != matches {
self.results.fail(
test,
RegexTestFailureKind::StartEnd { got: matches },
);
} else {
self.results.pass(test);
}
}
TestResultKind::Captures(caps) => {
let expected = test.captures();
if expected != caps {
self.results.fail(
test,
RegexTestFailureKind::Captures { got: caps },
);
} else {
self.results.pass(test);
}
}
TestResultKind::Skip => {
self.results.skip(test);
}
TestResultKind::Fail { why } => {
self.results
.fail(test, RegexTestFailureKind::UserFailure { why });
}
}
self
}
fn should_skip(&self, test: &RegexTest) -> bool {
if self.include.is_empty() {
return false;
}
let mut skip = self.include.iter().any(|pat| !pat.blacklist);
for pat in &self.include {
if test.full_name().as_bytes().contains_str(&pat.substring) {
skip = pat.blacklist;
}
}
skip
}
}
#[derive(Debug)]
struct IncludePattern {
blacklist: bool,
substring: BString,
}
struct Expander {
predicate: Box<dyn FnMut(&RegexTest) -> bool>,
additional_names: Vec<String>,
}
impl std::fmt::Debug for Expander {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Expander")
.field("predicate", &"<FnMut(..)>")
.field("additional_names", &self.additional_names)
.finish()
}
}
#[derive(Debug)]
struct RegexTestResults {
pass: Vec<RegexTestResult>,
fail: Vec<RegexTestFailure>,
skip: Vec<RegexTestResult>,
}
#[derive(Debug)]
struct RegexTestResult {
test: RegexTest,
}
#[derive(Debug)]
struct RegexTestFailure {
test: RegexTest,
kind: RegexTestFailureKind,
}
#[derive(Debug)]
enum RegexTestFailureKind {
UserFailure { why: String },
IsMatch,
Many { got: Vec<usize> },
StartEnd { got: Vec<Match> },
Captures { got: Vec<Captures> },
NoCompileError,
CompileError { err: anyhow::Error },
UnexpectedPanicCompile(String),
UnexpectedPanicSearch(String),
}
impl RegexTestResults {
fn new() -> RegexTestResults {
RegexTestResults { pass: vec![], fail: vec![], skip: vec![] }
}
fn pass(&mut self, test: &RegexTest) {
self.pass.push(RegexTestResult { test: test.clone() });
}
fn fail(&mut self, test: &RegexTest, kind: RegexTestFailureKind) {
self.fail.push(RegexTestFailure { test: test.clone(), kind });
}
fn skip(&mut self, test: &RegexTest) {
self.skip.push(RegexTestResult { test: test.clone() });
}
fn assert(&self) {
if read_env(ENV_REGEX_TEST_VERBOSE).map_or(false, |s| s == "1") {
self.verbose();
}
if self.fail.is_empty() {
return;
}
let failures = self
.fail
.iter()
.map(|f| f.to_string())
.collect::<Vec<String>>()
.join("\n\n");
panic!(
"found {} failures:\n{}\n{}\n{}\n\n\
Set the REGEX_TEST environment variable to filter tests, \n\
e.g., REGEX_TEST=foo,-foo2 runs every test whose name contains \n\
foo but not foo2\n\n",
self.fail.len(),
"~".repeat(79),
failures.trim(),
"~".repeat(79),
)
}
fn verbose(&self) {
println!("{}", "~".repeat(79));
for t in &self.skip {
println!("skip: {}", t.full_name());
}
for t in &self.pass {
println!("pass: {}", t.full_name());
}
for t in &self.fail {
println!("FAIL: {}", t.test.full_name());
}
println!(
"\npassed: {}, skipped: {}, failed: {}",
self.pass.len(),
self.skip.len(),
self.fail.len()
);
println!("{}", "~".repeat(79));
}
}
impl RegexTestResult {
fn full_name(&self) -> String {
self.test.full_name().to_string()
}
}
impl RegexTestFailure {
fn full_name(&self) -> String {
self.test.full_name().to_string()
}
}
impl std::fmt::Display for RegexTestFailure {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"{}: {}\n\
pattern: {:?}\n\
haystack: {:?}",
self.full_name(),
self.kind.fmt(&self.test)?,
self.test.regexes(),
self.test.haystack().as_bstr(),
)?;
Ok(())
}
}
impl RegexTestFailureKind {
fn fmt(&self, test: &RegexTest) -> Result<String, std::fmt::Error> {
use std::fmt::Write;
let mut buf = String::new();
match *self {
RegexTestFailureKind::UserFailure { ref why } => {
write!(buf, "failed by implementor because: {}", why)?;
}
RegexTestFailureKind::IsMatch => {
if test.is_match() {
write!(buf, "expected match, but none found")?;
} else {
write!(buf, "expected no match, but found a match")?;
}
}
RegexTestFailureKind::Many { ref got } => {
write!(
buf,
"expected regexes {:?} to match, but found {:?}",
test.which_matches(),
got
)?;
}
RegexTestFailureKind::StartEnd { ref got } => {
write!(
buf,
"did not find expected matches\n\
expected: {:?}\n \
got: {:?}",
test.matches(),
got,
)?;
}
RegexTestFailureKind::Captures { ref got } => {
write!(
buf,
"expected to find {:?} captures, but got {:?}",
test.captures(),
got,
)?;
}
RegexTestFailureKind::NoCompileError => {
write!(buf, "expected regex to NOT compile, but it did")?;
}
RegexTestFailureKind::CompileError { ref err } => {
write!(buf, "expected regex to compile, failed: {}", err)?;
}
RegexTestFailureKind::UnexpectedPanicCompile(ref msg) => {
write!(buf, "got unexpected panic while compiling:\n{}", msg)?;
}
RegexTestFailureKind::UnexpectedPanicSearch(ref msg) => {
write!(buf, "got unexpected panic while searching:\n{}", msg)?;
}
}
Ok(buf)
}
}
#[derive(Debug)]
pub struct RegexTestsIter<'a>(std::slice::Iter<'a, RegexTest>);
impl<'a> Iterator for RegexTestsIter<'a> {
type Item = &'a RegexTest;
fn next(&mut self) -> Option<&'a RegexTest> {
self.0.next()
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
#[serde(untagged)]
enum RegexesFormat {
Single(String),
Many(Vec<String>),
}
impl RegexesFormat {
fn patterns(&self) -> &[String] {
match *self {
RegexesFormat::Single(ref pat) => std::slice::from_ref(pat),
RegexesFormat::Many(ref pats) => pats,
}
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
#[serde(try_from = "CapturesFormat")]
pub struct Captures {
id: usize,
groups: Vec<Option<Span>>,
}
impl Captures {
pub fn new<I: IntoIterator<Item = Option<Span>>>(
id: usize,
it: I,
) -> Result<Captures> {
let groups: Vec<Option<Span>> = it.into_iter().collect();
if groups.is_empty() {
bail!("captures must contain at least one group");
} else if groups[0].is_none() {
bail!("first group (index 0) of captures must be non-None");
}
Ok(Captures { id, groups })
}
pub fn id(&self) -> usize {
self.id
}
pub fn groups(&self) -> &[Option<Span>] {
&self.groups
}
pub fn len(&self) -> usize {
self.groups.len()
}
pub fn to_match(&self) -> Match {
Match { id: self.id(), span: self.to_span() }
}
pub fn to_span(&self) -> Span {
self.groups[0].unwrap()
}
}
impl From<Match> for Captures {
fn from(m: Match) -> Captures {
Captures { id: m.id, groups: vec![Some(m.span)] }
}
}
impl From<Span> for Captures {
fn from(sp: Span) -> Captures {
Captures { id: 0, groups: vec![Some(sp)] }
}
}
#[derive(Deserialize)]
#[serde(untagged)]
enum CapturesFormat {
Span([usize; 2]),
Match { id: usize, span: [usize; 2] },
Spans(Vec<MaybeSpan>),
Captures { id: usize, spans: Vec<MaybeSpan> },
}
impl TryFrom<CapturesFormat> for Captures {
type Error = anyhow::Error;
fn try_from(data: CapturesFormat) -> Result<Captures> {
match data {
CapturesFormat::Span([start, end]) => {
Ok(Captures { id: 0, groups: vec![Some(Span { start, end })] })
}
CapturesFormat::Match { id, span: [start, end] } => {
Ok(Captures { id, groups: vec![Some(Span { start, end })] })
}
CapturesFormat::Spans(spans) => {
Captures::new(0, spans.into_iter().map(|s| s.into_option()))
}
CapturesFormat::Captures { id, spans } => {
Captures::new(id, spans.into_iter().map(|s| s.into_option()))
}
}
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Match {
pub id: usize,
pub span: Span,
}
impl std::fmt::Debug for Match {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Match({:?}: {:?})", self.id, self.span)
}
}
#[derive(Clone, Copy, Deserialize, Eq, PartialEq)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl std::fmt::Debug for Span {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{:?}..{:?}", self.start, self.end)
}
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq)]
#[serde(untagged)]
enum MaybeSpan {
None([usize; 0]),
Some([usize; 2]),
}
impl MaybeSpan {
fn into_option(self) -> Option<Span> {
match self {
MaybeSpan::None(_) => None,
MaybeSpan::Some([start, end]) => Some(Span { start, end }),
}
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum MatchKind {
All,
LeftmostFirst,
LeftmostLongest,
}
impl Default for MatchKind {
fn default() -> MatchKind {
MatchKind::LeftmostFirst
}
}
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum SearchKind {
Earliest,
Leftmost,
Overlapping,
}
impl Default for SearchKind {
fn default() -> SearchKind {
SearchKind::Leftmost
}
}
fn read_env(var: &str) -> Result<String> {
let val = match std::env::var_os(var) {
None => return Ok("".to_string()),
Some(val) => val,
};
let val = val.into_string().map_err(|os| {
anyhow::anyhow!(
"invalid UTF-8 in env var {}={:?}",
var,
Vec::from_os_str_lossy(&os)
)
})?;
Ok(val)
}
fn safe<T, F>(fun: F) -> Result<T, String>
where
F: FnOnce() -> T,
{
use std::panic;
panic::catch_unwind(panic::AssertUnwindSafe(fun)).map_err(|any_err| {
if let Some(&s) = any_err.downcast_ref::<&str>() {
s.to_owned()
} else if let Some(s) = any_err.downcast_ref::<String>() {
s.to_owned()
} else {
"UNABLE TO SHOW RESULT OF PANIC.".to_owned()
}
})
}
fn default_true() -> bool {
true
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn err_no_regexes() {
let data = r#"
[[test]]
name = "foo"
haystack = "lib.rs"
matches = true
case-insensitive = true
"#;
let mut tests = RegexTests::new();
assert!(tests.load_slice("test", data.as_bytes()).is_err());
}
#[test]
fn err_unknown_field() {
let data = r#"
[[test]]
name = "foo"
regex = ".*.rs"
haystack = "lib.rs"
matches = true
something = 0
"#;
let mut tests = RegexTests::new();
assert!(tests.load_slice("test", data.as_bytes()).is_err());
}
#[test]
fn err_no_matches() {
let data = r#"
[[test]]
name = "foo"
regex = ".*.rs"
haystack = "lib.rs"
"#;
let mut tests = RegexTests::new();
assert!(tests.load_slice("test", data.as_bytes()).is_err());
}
#[test]
fn load_match() {
let data = r#"
[[test]]
name = "foo"
regex = ".*.rs"
haystack = "lib.rs"
matches = [[0, 6]]
compiles = false
anchored = true
case-insensitive = true
unicode = false
utf8 = false
"#;
let mut tests = RegexTests::new();
tests.load_slice("test", data.as_bytes()).unwrap();
let t0 = &tests.tests[0];
assert_eq!("test", t0.group());
assert_eq!("foo", t0.name());
assert_eq!("test/foo", t0.full_name());
assert_eq!(&[".*.rs"], t0.regexes());
assert_eq!(true, t0.is_match());
assert_eq!(vec![0], t0.which_matches());
assert!(!t0.compiles());
assert!(t0.anchored());
assert!(t0.case_insensitive());
assert!(!t0.unicode());
assert!(!t0.utf8());
}
#[test]
fn load_which_matches() {
let data = r#"
[[test]]
name = "foo"
regex = [".*.rs", ".*.toml"]
haystack = "lib.rs"
matches = [
{ id = 0, spans = [[0, 0]] },
{ id = 2, spans = [[0, 0]] },
{ id = 5, spans = [[0, 0]] },
]
"#;
let mut tests = RegexTests::new();
tests.load_slice("test", data.as_bytes()).unwrap();
let t0 = &tests.tests[0];
assert_eq!(&[".*.rs", ".*.toml"], t0.regexes());
assert_eq!(true, t0.is_match());
assert_eq!(vec![0, 2, 5], t0.which_matches());
assert!(t0.compiles());
assert!(!t0.anchored());
assert!(!t0.case_insensitive());
assert!(t0.unicode());
assert!(t0.utf8());
}
#[test]
fn load_spans() {
let data = r#"
[[test]]
name = "foo"
regex = ".*.rs"
haystack = "lib.rs"
matches = [[0, 2], [5, 10]]
"#;
let mut tests = RegexTests::new();
tests.load_slice("test", data.as_bytes()).unwrap();
let spans =
vec![Span { start: 0, end: 2 }, Span { start: 5, end: 10 }];
let t0 = &tests.tests[0];
assert_eq!(t0.regexes(), &[".*.rs"]);
assert_eq!(t0.is_match(), true);
assert_eq!(t0.which_matches(), &[0]);
assert_eq!(
t0.matches(),
vec![
Match { id: 0, span: spans[0] },
Match { id: 0, span: spans[1] },
]
);
assert_eq!(
t0.captures(),
vec![
Captures::new(0, vec![Some(spans[0])]).unwrap(),
Captures::new(0, vec![Some(spans[1])]).unwrap(),
]
);
}
#[test]
fn load_capture_spans() {
let data = r#"
[[test]]
name = "foo"
regex = ".*.rs"
haystack = "lib.rs"
matches = [
[[0, 15], [5, 10], [], [13, 14]],
[[20, 30], [22, 24], [25, 27], []],
]
"#;
let mut tests = RegexTests::new();
tests.load_slice("test", data.as_bytes()).unwrap();
let t0 = &tests.tests[0];
assert_eq!(t0.regexes(), &[".*.rs"]);
assert_eq!(t0.is_match(), true);
assert_eq!(t0.which_matches(), &[0]);
assert_eq!(
t0.matches(),
vec![
Match { id: 0, span: Span { start: 0, end: 15 } },
Match { id: 0, span: Span { start: 20, end: 30 } },
]
);
assert_eq!(
t0.captures(),
vec![
Captures::new(
0,
vec![
Some(Span { start: 0, end: 15 }),
Some(Span { start: 5, end: 10 }),
None,
Some(Span { start: 13, end: 14 }),
]
)
.unwrap(),
Captures::new(
0,
vec![
Some(Span { start: 20, end: 30 }),
Some(Span { start: 22, end: 24 }),
Some(Span { start: 25, end: 27 }),
None,
]
)
.unwrap(),
]
);
}
#[test]
fn fail_spans_empty1() {
let data = r#"
[[test]]
name = "foo"
regex = ".*.rs"
haystack = "lib.rs"
matches = [
[],
]
"#;
let mut tests = RegexTests::new();
assert!(tests.load_slice("test", data.as_bytes()).is_err());
}
#[test]
fn fail_spans_empty2() {
let data = r#"
[[test]]
name = "foo"
regex = ".*.rs"
haystack = "lib.rs"
matches = [
[[]],
]
"#;
let mut tests = RegexTests::new();
assert!(tests.load_slice("test", data.as_bytes()).is_err());
}
#[test]
fn fail_spans_empty3() {
let data = r#"
[[test]]
name = "foo"
regex = ".*.rs"
haystack = "lib.rs"
matches = [
[[], [0, 2]],
]
"#;
let mut tests = RegexTests::new();
assert!(tests.load_slice("test", data.as_bytes()).is_err());
}
#[test]
fn fail_captures_empty1() {
let data = r#"
[[test]]
name = "foo"
regex = ".*.rs"
haystack = "lib.rs"
matches = [
{ id = 0, spans = [] },
]
"#;
let mut tests = RegexTests::new();
assert!(tests.load_slice("test", data.as_bytes()).is_err());
}
#[test]
fn fail_captures_empty2() {
let data = r#"
[[test]]
name = "foo"
regex = ".*.rs"
haystack = "lib.rs"
matches = [
{ id = 0, spans = [[]] },
]
"#;
let mut tests = RegexTests::new();
assert!(tests.load_slice("test", data.as_bytes()).is_err());
}
#[test]
fn fail_captures_empty3() {
let data = r#"
[[test]]
name = "foo"
regex = ".*.rs"
haystack = "lib.rs"
matches = [
{ id = 0, spans = [[], [0, 2]] },
]
"#;
let mut tests = RegexTests::new();
assert!(tests.load_slice("test", data.as_bytes()).is_err());
}
}