1
#![allow(unused)]
2

            
3
use conjure_cp::settings::{Parser, QuantifiedExpander, Rewriter, SolverFamily};
4
use serde::Deserialize;
5
use std::fs;
6
use std::io;
7
use std::path::Path;
8
use std::str::FromStr;
9
use std::time::Duration;
10
use toml_edit::{DocumentMut, value};
11

            
12
4065
fn parse_values<T>(values: &[String]) -> Result<Vec<T>, String>
13
4065
where
14
4065
    T: FromStr<Err = String>,
15
{
16
6783
    values.iter().map(|value| value.parse()).collect()
17
4065
}
18

            
19
6682
fn deserialize_string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
20
6682
where
21
6682
    D: serde::Deserializer<'de>,
22
{
23
    #[derive(Deserialize)]
24
    #[serde(untagged)]
25
    enum StringOrVec {
26
        String(String),
27
        Vec(Vec<String>),
28
    }
29

            
30
6682
    Ok(match StringOrVec::deserialize(deserializer)? {
31
37
        StringOrVec::String(s) => vec![s],
32
6645
        StringOrVec::Vec(v) => v,
33
    })
34
6682
}
35

            
36
3073
fn default_true() -> bool {
37
3073
    true
38
3073
}
39

            
40
6873
fn default_minion_discrete_threshold() -> usize {
41
6873
    conjure_cp::settings::DEFAULT_MINION_DISCRETE_THRESHOLD
42
6873
}
43

            
44
1639
fn deserialise_expected_time<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
45
1639
where
46
1639
    D: serde::Deserializer<'de>,
47
{
48
1639
    Option::<u64>::deserialize(deserializer)
49
1639
}
50

            
51
/// Rounds an observed runtime into the coarse `expected-time` buckets used by test configs,
52
/// such as `1`, `5`, `10`, `30`, `60`, and so on.
53
pub fn round_expected_time(duration: Duration) -> u64 {
54
    let seconds = duration.as_secs_f64();
55

            
56
    if seconds <= 1.0 {
57
        1
58
    } else if seconds <= 5.0 {
59
        5
60
    } else if seconds <= 10.0 {
61
        10
62
    } else {
63
        ((seconds / 30.0).ceil() as u64) * 30
64
    }
65
}
66

            
67
/// Inserts or updates the `expected-time` entry in a test `config.toml`.
68
pub fn upsert_expected_time_config(path: &Path, expected_time: u64) -> io::Result<()> {
69
    let expected_time = i64::try_from(expected_time).map_err(|err| {
70
        io::Error::new(
71
            io::ErrorKind::InvalidInput,
72
            format!("expected-time is too large to write to TOML: {err}"),
73
        )
74
    })?;
75

            
76
    let mut document = if path.exists() {
77
        let contents = fs::read_to_string(path)?;
78
        if contents.trim().is_empty() {
79
            DocumentMut::new()
80
        } else {
81
            contents
82
                .parse::<DocumentMut>()
83
                .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err))?
84
        }
85
    } else {
86
        DocumentMut::new()
87
    };
88

            
89
    document["expected-time"] = value(expected_time);
90

            
91
    let mut new_contents = document.to_string();
92
    if !new_contents.ends_with('\n') {
93
        new_contents.push('\n');
94
    }
95

            
96
    fs::write(path, new_contents)
97
}
98

            
99
#[derive(Deserialize, Debug)]
100
#[serde(default)]
101
#[serde(deny_unknown_fields)]
102
pub struct TestConfig {
103
    #[serde(
104
        default,
105
        rename = "parser",
106
        deserialize_with = "deserialize_string_or_vec"
107
    )]
108
    pub parser: Vec<String>, // Stage 1a: list of parsers (tree-sitter or via-conjure)
109

            
110
    #[serde(
111
        default,
112
        rename = "rewriter",
113
        deserialize_with = "deserialize_string_or_vec"
114
    )]
115
    pub rewriter: Vec<String>,
116
    #[serde(
117
        default,
118
        rename = "comprehension-expander",
119
        deserialize_with = "deserialize_string_or_vec"
120
    )]
121
    pub comprehension_expander: Vec<String>,
122
    #[serde(
123
        default,
124
        rename = "solver",
125
        deserialize_with = "deserialize_string_or_vec"
126
    )]
127
    pub solver: Vec<String>,
128

            
129
    #[serde(
130
        default = "default_minion_discrete_threshold",
131
        rename = "minion-discrete-threshold"
132
    )]
133
    pub minion_discrete_threshold: usize,
134

            
135
    #[serde(default = "default_true", rename = "validate-with-conjure")]
136
    pub validate_with_conjure: bool,
137

            
138
    // Generate this test but do not run it
139
    pub skip: bool,
140

            
141
    #[serde(
142
        default,
143
        rename = "expected-time",
144
        deserialize_with = "deserialise_expected_time"
145
    )]
146
    pub expected_time: Option<u64>,
147
}
148

            
149
impl Default for TestConfig {
150
3730
    fn default() -> Self {
151
3730
        Self {
152
3730
            skip: false,
153
3730
            expected_time: None,
154
3730
            parser: vec!["tree-sitter".to_string(), "via-conjure".to_string()],
155
3730
            rewriter: vec!["naive".to_string()],
156
3730
            comprehension_expander: vec![
157
3730
                "native".to_string(),
158
3730
                "via-solver".to_string(),
159
3730
                "via-solver-ac".to_string(),
160
3730
            ],
161
3730
            solver: {
162
3730
                let mut solvers = vec![
163
3730
                    "minion".to_string(),
164
3730
                    "sat-log".to_string(),
165
3730
                    "sat-direct".to_string(),
166
3730
                    "sat-order".to_string(),
167
3730
                ];
168
3730

            
169
3730
                {
170
3730
                    solvers.extend([
171
3730
                        "smt".to_string(),
172
3730
                        "smt-lia-arrays-nodiscrete".to_string(),
173
3730
                        "smt-lia-atomic".to_string(),
174
3730
                        "smt-lia-atomic-nodiscrete".to_string(),
175
3730
                        "smt-bv-arrays".to_string(),
176
3730
                        "smt-bv-arrays-nodiscrete".to_string(),
177
3730
                        "smt-bv-atomic".to_string(),
178
3730
                        "smt-bv-atomic-nodiscrete".to_string(),
179
3730
                    ]);
180
3730
                }
181
3730
                solvers
182
3730
            },
183
3730
            minion_discrete_threshold: default_minion_discrete_threshold(),
184
3730
            validate_with_conjure: true,
185
3730
        }
186
3730
    }
187
}
188

            
189
impl TestConfig {
190
1374
    pub fn configured_parsers(&self) -> Result<Vec<Parser>, String> {
191
1374
        parse_values(&self.parser)
192
1374
    }
193

            
194
897
    pub fn configured_rewriters(&self) -> Result<Vec<Rewriter>, String> {
195
897
        if self.rewriter.is_empty() {
196
            return Err("setting 'rewriter' has no values".to_string());
197
897
        }
198

            
199
897
        parse_values(&self.rewriter)
200
897
    }
201

            
202
897
    pub fn configured_comprehension_expanders(&self) -> Result<Vec<QuantifiedExpander>, String> {
203
897
        let values = if self.comprehension_expander.is_empty() {
204
            vec!["native".to_string()]
205
        } else {
206
897
            self.comprehension_expander.clone()
207
        };
208

            
209
897
        parse_values(&values)
210
897
    }
211

            
212
897
    pub fn configured_solvers(&self) -> Result<Vec<SolverFamily>, String> {
213
897
        parse_values(&self.solver)
214
897
    }
215

            
216
    pub fn uses_smt_solver(&self) -> bool {
217
        self.solver
218
            .iter()
219
            .any(|solver| solver == "smt" || solver.starts_with("smt-"))
220
    }
221
}