litcheck_lit/formats/sh/script/
mod.rs1#![expect(unused_assignments)]
2
3pub mod directives;
4mod parser;
5#[cfg(test)]
6mod tests;
7
8use std::{borrow::Cow, sync::Arc};
9
10use litcheck::diagnostics::{
11 DiagResult, Diagnostic, FileName, SourceFile, SourceSpan, Span, Spanned,
12};
13
14use self::directives::*;
15use self::parser::TestScriptParser;
16
17use crate::config::{BooleanExpr, InvalidBooleanExprError, ScopedSubstitutionSet};
18
19#[derive(Diagnostic, Debug, thiserror::Error)]
20#[error("parsing test script at '{file}' failed")]
21#[diagnostic()]
22pub struct InvalidTestScriptError {
23 file: FileName,
24 #[related]
25 errors: Vec<TestScriptError>,
26}
27impl InvalidTestScriptError {
28 pub fn new(file: FileName, errors: Vec<TestScriptError>) -> Self {
29 Self { file, errors }
30 }
31}
32
33#[derive(Diagnostic, Debug, thiserror::Error)]
34pub enum TestScriptError {
35 #[error("unable to read file")]
36 #[diagnostic()]
37 Io(#[from] std::io::Error),
38 #[error("invalid line substitution syntax")]
39 #[diagnostic()]
40 InvalidLineSubstitution {
41 #[label("{error}")]
42 span: SourceSpan,
43 #[source]
44 error: core::num::ParseIntError,
45 },
46 #[error(transparent)]
47 #[diagnostic(transparent)]
48 InvalidBooleanExpr(
49 #[from]
50 #[diagnostic_source]
51 InvalidBooleanExprError,
52 ),
53 #[error(transparent)]
54 #[diagnostic(transparent)]
55 InvalidSubstitution(
56 #[from]
57 #[diagnostic_source]
58 InvalidSubstitutionError,
59 ),
60 #[error("unexpected directive keyword")]
61 #[diagnostic()]
62 InvalidDirectiveContinuation {
63 #[label("expected {expected} here")]
64 span: SourceSpan,
65 #[label("because this line ended with a continuation")]
66 prev_line: SourceSpan,
67 expected: DirectiveKind,
68 },
69 #[error("missing directive keyword")]
70 #[diagnostic()]
71 MissingDirectiveContinuation {
72 #[label("expected to find the {expected} keyword on this line")]
73 span: SourceSpan,
74 #[label("because this line ended with a continuation")]
75 prev_line: SourceSpan,
76 expected: DirectiveKind,
77 },
78 #[error("test has no 'RUN:' directive")]
79 #[diagnostic(help("lit requires at least one command to run per test"))]
80 MissingRunDirective,
81 #[error("'{kind}' directive conflict")]
82 #[diagnostic()]
83 DirectiveConflict {
84 #[label("this directive conflicts with a previous occurrence")]
85 span: SourceSpan,
86 #[label("previously occurred here")]
87 prev_span: SourceSpan,
88 kind: DirectiveKind,
89 },
90 #[error("unable to parse value of '{kind}' directive")]
91 #[diagnostic()]
92 InvalidIntegerDirective {
93 #[label("{error}")]
94 span: SourceSpan,
95 kind: DirectiveKind,
96 #[source]
97 error: core::num::ParseIntError,
98 },
99}
100
101#[derive(Debug, Default)]
102pub struct TestScript {
103 pub commands: Vec<ScriptCommand>,
107 pub xfails: Vec<Span<BooleanExpr>>,
112 pub requires: Vec<Span<BooleanExpr>>,
116 pub unsupported: Vec<Span<BooleanExpr>>,
120 pub allowed_retries: Option<Span<usize>>,
123}
124impl TestScript {
125 pub fn parse(source: Arc<SourceFile>) -> DiagResult<Self> {
126 let parser = TestScriptParser::new(&source);
127 parser
128 .parse()
129 .map_err(|err| litcheck::diagnostics::Report::from(err).with_source_code(source))
130 }
131
132 pub fn apply_substitutions(
133 &mut self,
134 substitutions: &mut ScopedSubstitutionSet<'_>,
135 ) -> DiagResult<()> {
136 for script_command in self.commands.iter_mut() {
137 match script_command {
138 ScriptCommand::Run(ref mut run) => {
139 let span = run.span();
140 let command = substitutions.apply(Span::new(span, run.command.as_str()))?;
141 if let Cow::Owned(command) = command {
142 run.command = command;
143 }
144 }
145 ScriptCommand::Substitution(ref subst) => {
146 subst.apply(substitutions)?;
147 }
148 }
149 }
150
151 Ok(())
152 }
153}