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

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

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

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

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

            
44
1675
fn deserialise_expected_time<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
45
1675
where
46
1675
    D: serde::Deserializer<'de>,
47
{
48
1675
    Option::<u64>::deserialize(deserializer)
49
1675
}
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
3973
    fn default() -> Self {
151
3973
        Self {
152
3973
            skip: false,
153
3973
            expected_time: None,
154
3973
            parser: vec!["tree-sitter".to_string(), "via-conjure".to_string()],
155
3973
            rewriter: vec!["naive".to_string()],
156
3973
            comprehension_expander: vec![
157
3973
                "native".to_string(),
158
3973
                "via-solver".to_string(),
159
3973
                "via-solver-ac".to_string(),
160
3973
            ],
161
3973
            solver: {
162
3973
                let mut solvers = vec![
163
3973
                    "minion".to_string(),
164
3973
                    "sat-log".to_string(),
165
3973
                    "sat-direct".to_string(),
166
3973
                    "sat-order".to_string(),
167
3973
                ];
168
3973

            
169
3973
                {
170
3973
                    solvers.extend([
171
3973
                        "smt".to_string(),
172
3973
                        "smt-lia-arrays-nodiscrete".to_string(),
173
3973
                        "smt-lia-atomic".to_string(),
174
3973
                        "smt-lia-atomic-nodiscrete".to_string(),
175
3973
                        "smt-bv-arrays".to_string(),
176
3973
                        "smt-bv-arrays-nodiscrete".to_string(),
177
3973
                        "smt-bv-atomic".to_string(),
178
3973
                        "smt-bv-atomic-nodiscrete".to_string(),
179
3973
                    ]);
180
3973
                }
181
3973
                solvers
182
3973
            },
183
3973
            minion_discrete_threshold: default_minion_discrete_threshold(),
184
3973
            validate_with_conjure: true,
185
3973
        }
186
3973
    }
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
894
    pub fn configured_rewriters(&self) -> Result<Vec<Rewriter>, String> {
195
894
        if self.rewriter.is_empty() {
196
            return Err("setting 'rewriter' has no values".to_string());
197
894
        }
198

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

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

            
209
894
        parse_values(&values)
210
894
    }
211

            
212
894
    pub fn configured_solvers(&self) -> Result<Vec<SolverFamily>, String> {
213
894
        parse_values(&self.solver)
214
894
    }
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
}