ui_test 0.30.4

A test framework for testing rustc diagnostics output
Documentation
use super::Comments;
use crate::{
    parser::{Condition, ErrorMatchKind, Pattern},
    Config, Error,
};
use spanned::{Span, Spanned};
use std::path::PathBuf;

macro_rules! line {
    ($thing:expr, $s:expr) => {{
        let file = Spanned::new(
            $s.as_bytes(),
            Span {
                file: PathBuf::new(),
                bytes: 0..$s.len(),
            },
        );
        let pos = file
            .lines()
            .position(|line| line.span.bytes.contains(&$thing.bytes.start))
            .unwrap();
        pos + 1
    }};
}

#[test]
fn parse_simple_comment() {
    let s = r"
use std::mem;

fn main() {
    let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ ERROR: encountered a dangling reference (address $HEX is unallocated)
}
    ";
    let comments = Comments::parse(
        Spanned::new(
            s.as_bytes(),
            Span {
                file: PathBuf::new(),
                bytes: 0..s.len(),
            },
        ),
        &Config::dummy(),
    )
    .unwrap();
    println!("parsed comments: {:#?}", comments);
    assert_eq!(comments.revisioned.len(), 1);
    let revisioned = &comments.revisioned[&vec![]];
    let ErrorMatchKind::Pattern { pattern, .. } = &revisioned.error_matches[0].kind else {
        panic!("expected pattern matcher");
    };
    assert_eq!(line!(&pattern.span, s), 5);
    match &**pattern {
        Pattern::SubString(s) => {
            assert_eq!(
                s,
                "encountered a dangling reference (address $HEX is unallocated)"
            )
        }
        other => panic!("expected substring, got {other:?}"),
    }
}

#[test]
fn parse_error_code_comment() {
    let s = r"
fn main() {
    let _x: i32 = 0u32; //~ E0308
}
    ";
    let comments = Comments::parse(
        Spanned::new(
            s.as_bytes(),
            Span {
                file: PathBuf::new(),
                bytes: 0..s.len(),
            },
        ),
        &Config::dummy(),
    )
    .unwrap();
    println!("parsed comments: {:#?}", comments);
    assert_eq!(comments.revisioned.len(), 1);
    let revisioned = &comments.revisioned[&vec![]];
    let ErrorMatchKind::Code(code) = &revisioned.error_matches[0].kind else {
        panic!("expected diagnostic code matcher");
    };
    assert_eq!(line!(&code.span, s), 3);
    assert_eq!(**code, "E0308");
}

#[test]
fn parse_missing_level() {
    let s = r"
use std::mem;

fn main() {
    let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ encountered a dangling reference (address $HEX is unallocated)
}
    ";
    let errors = Comments::parse(
        Spanned::new(
            s.as_bytes(),
            Span {
                file: PathBuf::new(),
                bytes: 0..s.len(),
            },
        ),
        &Config::dummy(),
    )
    .unwrap_err();
    println!("parsed comments: {:#?}", errors);
    assert_eq!(errors.len(), 1);
    match &errors[0] {
        Error::InvalidComment { msg, span } if line!(span, s) == 5 => {
            assert_eq!(msg, "text found after error code `encountered`")
        }
        _ => unreachable!(),
    }
}

#[test]
fn parse_slash_slash_at() {
    let s = r"
//@  error-in-other-file:  foomp
use std::mem;

    ";
    let comments = Comments::parse(
        Spanned::new(
            s.as_bytes(),
            Span {
                file: PathBuf::new(),
                bytes: 0..s.len(),
            },
        ),
        &Config::dummy(),
    )
    .unwrap();
    println!("parsed comments: {:#?}", comments);
    assert_eq!(comments.revisioned.len(), 1);
    let revisioned = &comments.revisioned[&vec![]];
    let pat = &revisioned.error_in_other_files[0];
    assert_eq!(format!("{:?}", **pat), r#"SubString("foomp")"#);
    assert_eq!(line!(pat.span, s), 2);
}

#[test]
fn parse_regex_error_pattern() {
    let s = r"
//@  error-in-other-file:  /foomp/
use std::mem;

    ";
    let comments = Comments::parse(
        Spanned::new(
            s.as_bytes(),
            Span {
                file: PathBuf::new(),
                bytes: 0..s.len(),
            },
        ),
        &Config::dummy(),
    )
    .unwrap();
    println!("parsed comments: {:#?}", comments);
    assert_eq!(comments.revisioned.len(), 1);
    let revisioned = &comments.revisioned[&vec![]];
    let pat = &revisioned.error_in_other_files[0];
    assert_eq!(format!("{:?}", **pat), r#"Regex(Regex("foomp"))"#);
    assert_eq!(line!(pat.span, s), 2);
}

#[test]
fn parse_slash_slash_at_fail() {
    let s = r"
//@  error-patttern  foomp
use std::mem;

    ";
    let errors = Comments::parse(
        Spanned::new(
            s.as_bytes(),
            Span {
                file: PathBuf::new(),
                bytes: 0..s.len(),
            },
        ),
        &Config::dummy(),
    )
    .unwrap_err();
    println!("parsed comments: {:#?}", errors);
    assert_eq!(errors.len(), 2);
    match &errors[0] {
        Error::InvalidComment { msg, span } if line!(span, s) == 2 => {
            assert!(msg.contains("must be followed by `:`"))
        }
        _ => unreachable!(),
    }
    match &errors[1] {
        Error::InvalidComment { msg, span } if line!(span, s) == 2 => {
            assert_eq!(msg, "`error-patttern` is not a command known to `ui_test`, did you mean `error-pattern`?");
        }
        _ => unreachable!(),
    }
}

#[test]
fn missing_colon_fail() {
    let s = r"
//@stderr-per-bitwidth hello
use std::mem;

    ";
    let errors = Comments::parse(
        Spanned::new(
            s.as_bytes(),
            Span {
                file: PathBuf::new(),
                bytes: 0..s.len(),
            },
        ),
        &Config::dummy(),
    )
    .unwrap_err();
    println!("parsed comments: {:#?}", errors);
    assert_eq!(errors.len(), 1);
    match &errors[0] {
        Error::InvalidComment { msg, span } if line!(span, s) == 2 => {
            assert!(msg.contains("must be followed by `:`"))
        }
        _ => unreachable!(),
    }
}

#[test]
fn parse_x86_64() {
    let s = r"//@ only-target:x86_64-unknown-linux";
    let comments = Comments::parse(
        Spanned::new(
            s.as_bytes(),
            Span {
                file: PathBuf::new(),
                bytes: 0..s.len(),
            },
        ),
        &Config::dummy(),
    )
    .unwrap();
    println!("parsed comments: {:#?}", comments);
    assert_eq!(comments.revisioned.len(), 1);
    let revisioned = &comments.revisioned[&vec![]];
    assert_eq!(revisioned.only.len(), 1);
    match &revisioned.only[0] {
        Condition::Target(t) => {
            assert_eq!(t.len(), 1);
            assert_eq!(t[0], "x86_64-unknown-linux")
        }
        _ => unreachable!(),
    }
}

#[test]
fn parse_two_only_filters() {
    let s = r"//@only-target: hello world # some comment";
    let comments = Comments::parse(
        Spanned::new(
            s.as_bytes(),
            Span {
                file: PathBuf::new(),
                bytes: 0..s.len(),
            },
        ),
        &Config::dummy(),
    )
    .unwrap();
    println!("parsed comments: {:#?}", comments);
    assert_eq!(comments.revisioned.len(), 1);
    let revisioned = &comments.revisioned[&vec![]];
    assert_eq!(revisioned.only.len(), 1);
    match &revisioned.only[0] {
        Condition::Target(t) => {
            assert_eq!(t.len(), 2);
            assert_eq!(t[0], "hello");
            assert_eq!(t[1], "world")
        }
        _ => unreachable!(),
    }
}

#[test]
fn parse_invalid_filter() {
    let s = r"//@only-target: hello world: somecomment";
    Comments::parse(
        Spanned::new(
            s.as_bytes(),
            Span {
                file: PathBuf::new(),
                bytes: 0..s.len(),
            },
        ),
        &Config::dummy(),
    )
    .unwrap_err();
}