use std::{collections::BTreeMap, path::Path};
use litcheck::{
diagnostics::{DiagResult, Diagnostic, IntoDiagnostic, Report, SourceFile, SourceSpan},
fs::PatternSet,
Input, StaticCow,
};
use serde::Deserialize;
use crate::{
config::{BooleanExpr, FeatureSet, SubstitutionSet},
format::SelectedTestFormat,
Config,
};
#[derive(Debug, Diagnostic, thiserror::Error)]
pub enum TestConfigError {
#[error("invalid syntax in configuration file")]
#[diagnostic()]
Syntax {
#[label("{error}")]
span: SourceSpan,
#[source]
error: toml::de::Error,
},
}
#[derive(Default, Clone, Deserialize)]
pub struct TestConfig {
#[serde(default)]
pub format: SelectedTestFormat,
#[serde(default)]
pub patterns: PatternSet,
#[serde(default)]
pub env: BTreeMap<StaticCow<str>, StaticCow<str>>,
#[serde(default)]
pub substitutions: SubstitutionSet,
#[serde(default)]
pub available_features: FeatureSet,
}
impl TestConfig {
pub fn parse<P: AsRef<Path>>(path: P) -> DiagResult<Box<Self>> {
let path = path.as_ref();
let path = if path.is_absolute() {
path.to_path_buf()
} else {
path.canonicalize().into_diagnostic()?
};
let source = Input::from(path).into_source(false).into_diagnostic()?;
toml::from_str::<Self>(source.source())
.map(Box::new)
.map_err(|error| {
let span = error.span().unwrap_or(0..0);
Report::new(TestConfigError::Syntax {
span: SourceSpan::from(span),
error,
})
.with_source_code(source)
})
}
pub fn inherit(&mut self, parent: &Self) {
if matches!(self.format, SelectedTestFormat::Default) {
self.format = parent.format.clone();
}
if self.patterns.is_empty() {
self.patterns = parent.patterns.clone();
}
if !parent.env.is_empty() {
let env = core::mem::replace(&mut self.env, parent.env.clone());
self.env.extend(env);
}
if !parent.substitutions.is_empty() {
let subs = core::mem::replace(&mut self.substitutions, parent.substitutions.clone());
self.substitutions.extend(subs);
}
if !parent.available_features.is_empty() {
let features = core::mem::replace(
&mut self.available_features,
parent.available_features.clone(),
);
self.available_features.extend(features);
}
}
pub fn set_default_features(&mut self, config: &Config) {
use target_lexicon::*;
let host = config.host();
let target = config.target();
self.available_features
.insert(format!("system-{}", &host.operating_system));
self.available_features.insert(format!("host={}", &host));
self.available_features
.insert(format!("target={}", &target));
if host == target {
self.available_features.insert("native");
}
match target.architecture {
Architecture::X86_64 | Architecture::X86_64h => {
self.available_features.insert("target-x86_64");
match target.operating_system {
OperatingSystem::Darwin => {
self.available_features.insert("x86_64-apple");
}
OperatingSystem::Linux => {
self.available_features.insert("x86_64-linux");
}
_ => (),
}
}
Architecture::X86_32(_) => {
self.available_features.insert("target-x86");
}
Architecture::Aarch64(_) => {
self.available_features.insert("target-aarch64");
}
Architecture::Arm(_) => {
self.available_features.insert("target-arm");
}
arch => {
self.available_features.insert(format!("target-{}", arch));
}
}
match target.operating_system {
OperatingSystem::Darwin | OperatingSystem::MacOSX { .. } => {
self.available_features.insert("system-linker-mach-o");
}
_ => (),
}
}
pub fn set_default_substitutions(
&mut self,
_config: &Config,
suite: &super::TestSuite,
source_dir: &Path,
) {
let mut exe =
std::env::current_exe().expect("unable to detect lit/filecheck executable path");
let filecheck = if exe.ends_with("filecheck") {
StaticCow::Owned(format!("{} check", exe.display()))
} else if exe.ends_with("lit") {
exe.set_file_name("filecheck");
StaticCow::Owned(exe.to_string_lossy().into_owned())
} else {
StaticCow::Borrowed("filecheck")
};
self.substitutions.insert("[Ff]ile[Cc]heck", filecheck);
self.substitutions
.insert(r"%\{pathsep\}", if cfg!(windows) { ";" } else { ":" });
let test_root = source_dir
.components()
.next()
.unwrap()
.as_os_str()
.to_string_lossy()
.into_owned();
self.substitutions.insert(r"%\{fs-src-root\}", test_root);
let temp_root = suite
.working_dir()
.components()
.next()
.unwrap()
.as_os_str()
.to_string_lossy()
.into_owned();
self.substitutions.insert(r"%\{fs-tmp-root\}", temp_root);
self.substitutions
.insert(r"%\{fs-sep\}", std::path::MAIN_SEPARATOR_STR);
}
#[inline]
pub fn missing_features<F: AsRef<BooleanExpr>>(&self, required: &[F]) -> Option<String> {
self.available_features.missing_features(required)
}
#[inline]
pub fn unsupported_features<F: AsRef<BooleanExpr>>(&self, unsupported: &[F]) -> Option<String> {
self.available_features.unsupported_features(unsupported)
}
}