1
use std::collections::{BTreeMap, HashMap, HashSet};
2
use std::fmt::Debug;
3

            
4
use std::fs::File;
5
use std::fs::{read_to_string, OpenOptions};
6
use std::hash::Hash;
7
use std::io::Write;
8
use std::sync::{Arc, RwLock};
9

            
10
use conjure_core::ast::model::SerdeModel;
11
use conjure_core::context::Context;
12
use serde_json::{json, Error as JsonError, Value as JsonValue};
13

            
14
use conjure_core::error::Error;
15

            
16
use crate::ast::Name::UserName;
17
use crate::ast::{Literal, Name};
18
use crate::utils::conjure::minion_solutions_to_json;
19
use crate::utils::json::sort_json_object;
20
use crate::utils::misc::to_set;
21
use crate::Model as ConjureModel;
22

            
23
pub fn assert_eq_any_order<T: Eq + Hash + Debug + Clone>(a: &Vec<Vec<T>>, b: &Vec<Vec<T>>) {
24
    assert_eq!(a.len(), b.len());
25

            
26
    let mut a_rows: Vec<HashSet<T>> = Vec::new();
27
    for row in a {
28
        let hash_row = to_set(row);
29
        a_rows.push(hash_row);
30
    }
31

            
32
    let mut b_rows: Vec<HashSet<T>> = Vec::new();
33
    for row in b {
34
        let hash_row = to_set(row);
35
        b_rows.push(hash_row);
36
    }
37

            
38
    println!("{:?},{:?}", a_rows, b_rows);
39
    for row in a_rows {
40
        assert!(b_rows.contains(&row));
41
    }
42
}
43

            
44
2136
pub fn serialise_model(model: &ConjureModel) -> Result<String, JsonError> {
45
2136
    // A consistent sorting of the keys of json objects
46
2136
    // only required for the generated version
47
2136
    // since the expected version will already be sorted
48
2136
    let serde_model: SerdeModel = model.clone().into();
49
2136
    let generated_json = sort_json_object(&serde_json::to_value(serde_model)?, false);
50

            
51
    // serialise to string
52
2136
    let generated_json_str = serde_json::to_string_pretty(&generated_json)?;
53

            
54
2136
    Ok(generated_json_str)
55
2136
}
56

            
57
2136
pub fn save_model_json(
58
2136
    model: &ConjureModel,
59
2136
    path: &str,
60
2136
    test_name: &str,
61
2136
    test_stage: &str,
62
2136
    accept: bool,
63
2136
) -> Result<(), std::io::Error> {
64
2136
    let generated_json_str = serialise_model(model)?;
65

            
66
2136
    File::create(format!(
67
2136
        "{path}/{test_name}.generated-{test_stage}.serialised.json"
68
2136
    ))?
69
2136
    .write_all(generated_json_str.as_bytes())?;
70

            
71
2136
    if accept {
72
        std::fs::copy(
73
            format!("{path}/{test_name}.generated-{test_stage}.serialised.json"),
74
            format!("{path}/{test_name}.expected-{test_stage}.serialised.json"),
75
        )?;
76
2136
    }
77

            
78
2136
    Ok(())
79
2136
}
80

            
81
712
pub fn save_stats_json(
82
712
    context: Arc<RwLock<Context<'static>>>,
83
712
    path: &str,
84
712
    test_name: &str,
85
712
) -> Result<(), std::io::Error> {
86
712
    #[allow(clippy::unwrap_used)]
87
712
    let stats = context.read().unwrap().clone();
88
712
    let generated_json = sort_json_object(&serde_json::to_value(stats)?, false);
89

            
90
    // serialise to string
91
712
    let generated_json_str = serde_json::to_string_pretty(&generated_json)?;
92

            
93
712
    File::create(format!("{path}/{test_name}-stats.json"))?
94
712
        .write_all(generated_json_str.as_bytes())?;
95

            
96
712
    Ok(())
97
712
}
98

            
99
2136
pub fn read_model_json(
100
2136
    ctx: &Arc<RwLock<Context<'static>>>,
101
2136
    path: &str,
102
2136
    test_name: &str,
103
2136
    prefix: &str,
104
2136
    test_stage: &str,
105
2136
) -> Result<ConjureModel, std::io::Error> {
106
2136
    let expected_json_str = std::fs::read_to_string(format!(
107
2136
        "{path}/{test_name}.{prefix}-{test_stage}.serialised.json"
108
2136
    ))?;
109

            
110
2136
    let expected_model: SerdeModel = serde_json::from_str(&expected_json_str)?;
111

            
112
2136
    Ok(expected_model.initialise(ctx.clone()).unwrap())
113
2136
}
114

            
115
pub fn minion_solutions_from_json(
116
    serialized: &str,
117
) -> Result<Vec<HashMap<Name, Literal>>, anyhow::Error> {
118
    let json: JsonValue = serde_json::from_str(serialized)?;
119

            
120
    let json_array = json
121
        .as_array()
122
        .ok_or(Error::Parse("Invalid JSON".to_owned()))?;
123

            
124
    let mut solutions = Vec::new();
125

            
126
    for solution in json_array {
127
        let mut sol = HashMap::new();
128
        let solution = solution
129
            .as_object()
130
            .ok_or(Error::Parse("Invalid JSON".to_owned()))?;
131

            
132
        for (var_name, constant) in solution {
133
            let constant = match constant {
134
                JsonValue::Number(n) => {
135
                    let n = n
136
                        .as_i64()
137
                        .ok_or(Error::Parse("Invalid integer".to_owned()))?;
138
                    Literal::Int(n as i32)
139
                }
140
                JsonValue::Bool(b) => Literal::Bool(*b),
141
                _ => return Err(Error::Parse("Invalid constant".to_owned()).into()),
142
            };
143

            
144
            sol.insert(UserName(var_name.into()), constant);
145
        }
146

            
147
        solutions.push(sol);
148
    }
149

            
150
    Ok(solutions)
151
}
152

            
153
704
pub fn save_minion_solutions_json(
154
704
    solutions: &Vec<BTreeMap<Name, Literal>>,
155
704
    path: &str,
156
704
    test_name: &str,
157
704
    accept: bool,
158
704
) -> Result<JsonValue, std::io::Error> {
159
704
    let json_solutions = minion_solutions_to_json(solutions);
160

            
161
704
    let generated_json_str = serde_json::to_string_pretty(&json_solutions)?;
162

            
163
704
    File::create(format!(
164
704
        "{path}/{test_name}.generated-minion.solutions.json"
165
704
    ))?
166
704
    .write_all(generated_json_str.as_bytes())?;
167

            
168
704
    if accept {
169
        std::fs::copy(
170
            format!("{path}/{test_name}.generated-minion.solutions.json"),
171
            format!("{path}/{test_name}.expected-minion.solutions.json"),
172
        )?;
173
704
    }
174

            
175
704
    Ok(json_solutions)
176
704
}
177

            
178
704
pub fn read_minion_solutions_json(
179
704
    path: &str,
180
704
    test_name: &str,
181
704
    prefix: &str,
182
704
) -> Result<JsonValue, anyhow::Error> {
183
704
    let expected_json_str =
184
704
        std::fs::read_to_string(format!("{path}/{test_name}.{prefix}-minion.solutions.json"))?;
185

            
186
704
    let expected_solutions: JsonValue =
187
704
        sort_json_object(&serde_json::from_str(&expected_json_str)?, true);
188

            
189
704
    Ok(expected_solutions)
190
704
}
191

            
192
1424
pub fn read_rule_trace(
193
1424
    path: &str,
194
1424
    test_name: &str,
195
1424
    prefix: &str,
196
1424
    accept: bool,
197
1424
) -> Result<JsonValue, anyhow::Error> {
198
1424
    let filename = format!("{path}/{test_name}-{prefix}-rule-trace.json");
199

            
200
1424
    let rule_traces = if prefix == "generated" {
201
712
        count_and_sort_rules(&filename)?
202
    } else {
203
712
        let file_contents = std::fs::read_to_string(filename)?;
204
712
        serde_json::from_str(&file_contents)?
205
    };
206

            
207
1424
    if accept {
208
        std::fs::copy(
209
            format!("{path}/{test_name}-generated-rule-trace.json"),
210
            format!("{path}/{test_name}-expected-rule-trace.json"),
211
        )?;
212
1424
    }
213

            
214
1424
    Ok(rule_traces)
215
1424
}
216

            
217
712
pub fn count_and_sort_rules(filename: &str) -> Result<JsonValue, anyhow::Error> {
218
712
    let file_contents = read_to_string(filename)?;
219

            
220
712
    let sorted_json_rules = if file_contents.trim().is_empty() {
221
24
        let rule_count_message = json!({
222
24
            "Number of rules applied": 0,
223
24
        });
224
24
        rule_count_message
225
    } else {
226
688
        let rule_count = file_contents.lines().count();
227
688
        let mut sorted_json_rules = sort_json_rules(&file_contents)?;
228

            
229
688
        let rule_count_message = json!({
230
688
            "Number of rules applied": rule_count,
231
688
        });
232

            
233
688
        if let Some(array) = sorted_json_rules.as_array_mut() {
234
688
            array.push(rule_count_message);
235
688
        } else {
236
            return Err(anyhow::anyhow!("Expected JSON array"));
237
        }
238
688
        sort_json_object(&sorted_json_rules, false)
239
    };
240

            
241
712
    let generated_sorted_json_rules = serde_json::to_string_pretty(&sorted_json_rules)?;
242

            
243
712
    let mut file = OpenOptions::new()
244
712
        .write(true)
245
712
        .truncate(true)
246
712
        .open(filename)?;
247

            
248
712
    file.write_all(generated_sorted_json_rules.as_bytes())?;
249

            
250
712
    Ok(sorted_json_rules)
251
712
}
252

            
253
688
fn sort_json_rules(json_rule_traces: &str) -> Result<JsonValue, anyhow::Error> {
254
688
    let mut sorted_rule_traces = Vec::new();
255

            
256
5760
    for line in json_rule_traces.lines() {
257
5760
        let pretty_json = sort_json_object(&serde_json::from_str(line)?, true);
258
5760
        sorted_rule_traces.push(pretty_json);
259
    }
260

            
261
688
    Ok(JsonValue::Array(sorted_rule_traces))
262
688
}
263

            
264
1424
pub fn read_human_rule_trace(
265
1424
    path: &str,
266
1424
    test_name: &str,
267
1424
    prefix: &str,
268
1424
    accept: bool,
269
1424
) -> Result<Vec<String>, std::io::Error> {
270
1424
    let filename = format!("{path}/{test_name}-{prefix}-rule-trace-human.txt");
271
1424
    let rules_trace: Vec<String> = read_to_string(&filename)
272
1424
        .unwrap()
273
1424
        .lines()
274
1424
        .map(String::from)
275
1424
        .collect();
276
1424

            
277
1424
    if accept {
278
        std::fs::copy(
279
            format!("{path}/{test_name}-generated-rule-trace-human.txt"),
280
            format!("{path}/{test_name}-expected-rule-trace-human.txt"),
281
        )?;
282
1424
    }
283

            
284
1424
    Ok(rules_trace)
285
1424
}