summaryrefslogtreecommitdiff
path: root/tests/src/metadata.rs
diff options
context:
space:
mode:
Diffstat (limited to 'tests/src/metadata.rs')
-rw-r--r--tests/src/metadata.rs334
1 files changed, 0 insertions, 334 deletions
diff --git a/tests/src/metadata.rs b/tests/src/metadata.rs
deleted file mode 100644
index 53cbbdff..00000000
--- a/tests/src/metadata.rs
+++ /dev/null
@@ -1,334 +0,0 @@
-use std::collections::HashSet;
-use std::fmt::{self, Display, Formatter};
-use std::ops::Range;
-use std::str::FromStr;
-
-use ecow::EcoString;
-use typst::syntax::package::PackageVersion;
-use typst::syntax::Source;
-use unscanny::Scanner;
-
-/// Each test and subset may contain metadata.
-#[derive(Debug)]
-pub struct TestMetadata {
- /// Configures how the test is run.
- pub config: TestConfig,
- /// Declares properties that must hold for a test.
- ///
- /// For instance, `// Warning: 1-3 no text within underscores`
- /// will fail the test if the warning isn't generated by your test.
- pub annotations: HashSet<Annotation>,
-}
-
-/// Configuration of a test or subtest.
-#[derive(Debug, Default)]
-pub struct TestConfig {
- /// Reference images will be generated and compared.
- ///
- /// Defaults to `true`, can be disabled with `Ref: false`.
- pub compare_ref: Option<bool>,
- /// Hint annotations will be compared to compiler hints.
- ///
- /// Defaults to `true`, can be disabled with `Hints: false`.
- pub validate_hints: Option<bool>,
- /// Autocompletion annotations will be validated against autocompletions.
- /// Mutually exclusive with error and hint annotations.
- ///
- /// Defaults to `false`, can be enabled with `Autocomplete: true`.
- pub validate_autocomplete: Option<bool>,
-}
-
-/// Parsing error when the metadata is invalid.
-pub(crate) enum InvalidMetadata {
- /// An invalid annotation and it's error message.
- InvalidAnnotation(Annotation, String),
- /// Setting metadata can only be done with `true` or `false` as a value.
- InvalidSet(String),
-}
-
-impl InvalidMetadata {
- pub(crate) fn write(
- invalid_data: Vec<InvalidMetadata>,
- output: &mut String,
- print_annotation: &mut impl FnMut(&Annotation, &mut String),
- ) {
- use std::fmt::Write;
- for data in invalid_data.into_iter() {
- let (annotation, error) = match data {
- InvalidMetadata::InvalidAnnotation(a, e) => (Some(a), e),
- InvalidMetadata::InvalidSet(e) => (None, e),
- };
- write!(output, "{error}",).unwrap();
- if let Some(annotation) = annotation {
- print_annotation(&annotation, output)
- } else {
- writeln!(output).unwrap();
- }
- }
- }
-}
-
-/// Annotation of the form `// KIND: RANGE TEXT`.
-#[derive(Debug, Clone, Eq, PartialEq, Hash)]
-pub struct Annotation {
- /// Which kind of annotation this is.
- pub kind: AnnotationKind,
- /// May be written as:
- /// - `{line}:{col}-{line}:{col}`, e.g. `0:4-0:6`.
- /// - `{col}-{col}`, e.g. `4-6`:
- /// The line is assumed to be the line after the annotation.
- /// - `-1`: Produces a range of length zero at the end of the next line.
- /// Mostly useful for autocompletion tests which require an index.
- pub range: Option<Range<usize>>,
- /// The raw text after the annotation.
- pub text: EcoString,
-}
-
-/// The different kinds of in-test annotations.
-#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
-pub enum AnnotationKind {
- Error,
- Warning,
- Hint,
- AutocompleteContains,
- AutocompleteExcludes,
-}
-
-impl AnnotationKind {
- /// Returns the user-facing string for this annotation.
- pub fn as_str(self) -> &'static str {
- match self {
- AnnotationKind::Error => "Error",
- AnnotationKind::Warning => "Warning",
- AnnotationKind::Hint => "Hint",
- AnnotationKind::AutocompleteContains => "Autocomplete contains",
- AnnotationKind::AutocompleteExcludes => "Autocomplete excludes",
- }
- }
-}
-
-impl FromStr for AnnotationKind {
- type Err = &'static str;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Ok(match s {
- "Error" => AnnotationKind::Error,
- "Warning" => AnnotationKind::Warning,
- "Hint" => AnnotationKind::Hint,
- "Autocomplete contains" => AnnotationKind::AutocompleteContains,
- "Autocomplete excludes" => AnnotationKind::AutocompleteExcludes,
- _ => return Err("invalid annotatino"),
- })
- }
-}
-
-impl Display for AnnotationKind {
- fn fmt(&self, f: &mut Formatter) -> fmt::Result {
- f.pad(self.as_str())
- }
-}
-
-/// Parse metadata for a test.
-pub fn parse_part_metadata(
- source: &Source,
- is_header: bool,
-) -> Result<TestMetadata, Vec<InvalidMetadata>> {
- let mut config = TestConfig::default();
- let mut annotations = HashSet::default();
- let mut invalid_data = vec![];
-
- let lines = source_to_lines(source);
-
- for (i, line) in lines.iter().enumerate() {
- if let Some((key, value)) = parse_metadata_line(line) {
- let key = key.trim();
- match key {
- "Ref" => validate_set_annotation(
- value,
- &mut config.compare_ref,
- &mut invalid_data,
- ),
- "Hints" => validate_set_annotation(
- value,
- &mut config.validate_hints,
- &mut invalid_data,
- ),
- "Autocomplete" => validate_set_annotation(
- value,
- &mut config.validate_autocomplete,
- &mut invalid_data,
- ),
- annotation_key => {
- let Ok(kind) = AnnotationKind::from_str(annotation_key) else {
- continue;
- };
- let mut s = Scanner::new(value);
- let range = parse_range(&mut s, i, source);
- let rest = if range.is_some() { s.after() } else { s.string() };
- let message = rest
- .trim()
- .replace("VERSION", &PackageVersion::compiler().to_string())
- .into();
-
- let annotation =
- Annotation { kind, range: range.clone(), text: message };
-
- if is_header {
- invalid_data.push(InvalidMetadata::InvalidAnnotation(
- annotation,
- format!(
- "Error: header may not contain annotations of type {kind}"
- ),
- ));
- continue;
- }
-
- if matches!(
- kind,
- AnnotationKind::AutocompleteContains
- | AnnotationKind::AutocompleteExcludes
- ) {
- if let Some(range) = range {
- if range.start != range.end {
- invalid_data.push(InvalidMetadata::InvalidAnnotation(
- annotation,
- "Error: found range in Autocomplete annotation where range.start != range.end, range.end would be ignored."
- .to_string()
- ));
- continue;
- }
- } else {
- invalid_data.push(InvalidMetadata::InvalidAnnotation(
- annotation,
- "Error: autocomplete annotation but no range specified"
- .to_string(),
- ));
- continue;
- }
- }
- annotations.insert(annotation);
- }
- }
- }
- }
- if invalid_data.is_empty() {
- Ok(TestMetadata { config, annotations })
- } else {
- Err(invalid_data)
- }
-}
-
-/// Extract key and value for a metadata line of the form: `// KEY: VALUE`.
-fn parse_metadata_line(line: &str) -> Option<(&str, &str)> {
- let mut s = Scanner::new(line);
- if !s.eat_if("// ") {
- return None;
- }
-
- let key = s.eat_until(':').trim();
- if !s.eat_if(':') {
- return None;
- }
-
- let value = s.eat_until('\n').trim();
- Some((key, value))
-}
-
-/// Parse a quoted string.
-fn parse_string<'a>(s: &mut Scanner<'a>) -> Option<&'a str> {
- if !s.eat_if('"') {
- return None;
- }
- let sub = s.eat_until('"');
- if !s.eat_if('"') {
- return None;
- }
-
- Some(sub)
-}
-
-/// Parse a number.
-fn parse_num(s: &mut Scanner) -> Option<isize> {
- let mut first = true;
- let n = &s.eat_while(|c: char| {
- let valid = first && c == '-' || c.is_numeric();
- first = false;
- valid
- });
- n.parse().ok()
-}
-
-/// Parse a comma-separated list of strings.
-pub fn parse_string_list(text: &str) -> HashSet<&str> {
- let mut s = Scanner::new(text);
- let mut result = HashSet::new();
- while let Some(sub) = parse_string(&mut s) {
- result.insert(sub);
- s.eat_whitespace();
- if !s.eat_if(',') {
- break;
- }
- s.eat_whitespace();
- }
- result
-}
-
-/// Parse a position.
-fn parse_pos(s: &mut Scanner, i: usize, source: &Source) -> Option<usize> {
- let first = parse_num(s)? - 1;
- let (delta, column) =
- if s.eat_if(':') { (first, parse_num(s)? - 1) } else { (0, first) };
- let line = (i + comments_until_code(source, i)).checked_add_signed(delta)?;
- source.line_column_to_byte(line, usize::try_from(column).ok()?)
-}
-
-/// Parse a range.
-fn parse_range(s: &mut Scanner, i: usize, source: &Source) -> Option<Range<usize>> {
- let lines = source_to_lines(source);
- s.eat_whitespace();
- if s.eat_if("-1") {
- let mut add = 1;
- while let Some(line) = lines.get(i + add) {
- if !line.starts_with("//") {
- break;
- }
- add += 1;
- }
- let next_line = lines.get(i + add)?;
- let col = next_line.chars().count();
-
- let index = source.line_column_to_byte(i + add, col)?;
- s.eat_whitespace();
- return Some(index..index);
- }
- let start = parse_pos(s, i, source)?;
- let end = if s.eat_if('-') { parse_pos(s, i, source)? } else { start };
- s.eat_whitespace();
- Some(start..end)
-}
-
-/// Returns the number of lines of comment from line i to next line of code.
-fn comments_until_code(source: &Source, i: usize) -> usize {
- source_to_lines(source)[i..]
- .iter()
- .take_while(|line| line.starts_with("//"))
- .count()
-}
-
-fn source_to_lines(source: &Source) -> Vec<&str> {
- source.text().lines().map(str::trim).collect()
-}
-
-fn validate_set_annotation(
- value: &str,
- flag: &mut Option<bool>,
- invalid_data: &mut Vec<InvalidMetadata>,
-) {
- let value = value.trim();
- if value != "false" && value != "true" {
- invalid_data.push(
- InvalidMetadata::InvalidSet(format!("Error: trying to set Ref, Hints, or Autocomplete with value {value:?} != true, != false.")))
- } else {
- *flag = Some(value == "true")
- }
-}