1
use std::collections::{BTreeMap, HashMap, HashSet};
2
use std::fmt::Debug;
3
use std::path::Path;
4
use std::{io, mem, vec};
5

            
6
use conjure_cp::ast::records::Field;
7
use conjure_cp::ast::serde::ObjId;
8
use itertools::Itertools as _;
9
use std::fs::File;
10
use std::hash::Hash;
11
use std::io::{BufRead, BufReader};
12
use std::sync::{Arc, RwLock};
13
use uniplate::Uniplate;
14

            
15
use conjure_cp::ast::pretty::pretty_expression_domain_annotation;
16
use conjure_cp::ast::{AbstractLiteral, Expression, GroundDomain, Moo, SerdeModel};
17
use conjure_cp::context::Context;
18
use serde_json::{Error as JsonError, Value as JsonValue};
19

            
20
use conjure_cp::error::Error;
21

            
22
use crate::utils::conjure::solutions_to_essence;
23
use crate::utils::json::sort_json_object;
24
use crate::utils::misc::to_set;
25
use conjure_cp::Model as ConjureModel;
26
use conjure_cp::ast::Name::User;
27
use conjure_cp::ast::{Literal, Name};
28
use conjure_cp::settings::SolverFamily;
29

            
30
/// Limit how many lines of the rewrite serialisation we persist/compare in exhaustive tests.
31
pub const REWRITE_SERIALISED_JSON_MAX_LINES: usize = 1000;
32

            
33
/// Limit how many characters we persist/compare for large text snapshots.
34
pub const DEFAULT_TEXT_SNAPSHOT_CHARACTER_LIMIT: usize = 1_000_000;
35

            
36
/// Converts a SerdeModel to JSON with stable IDs.
37
///
38
/// This ensures that the same model structure always produces the same IDs,
39
/// regardless of the order in which objects were created in memory.
40
474
fn model_to_json_with_stable_ids(model: &SerdeModel) -> Result<JsonValue, JsonError> {
41
    // Collect stable ID mapping using uniplate traversal on the SerdeModel
42
474
    let id_map = model.collect_stable_id_mapping();
43

            
44
    // Serialize the model to JSON
45
474
    let mut json = serde_json::to_value(model)?;
46

            
47
    // Replace all IDs in the JSON with their stable counterparts
48
474
    replace_ids(&mut json, &id_map);
49

            
50
474
    Ok(json)
51
474
}
52

            
53
/// Recursively replaces all IDs in the JSON with their stable counterparts.
54
///
55
/// This is applied to all fields that are called "id" or "ptr" - be mindful
56
/// of potential naming clashes in the future!
57
37274
fn replace_ids(value: &mut JsonValue, id_map: &HashMap<ObjId, ObjId>) {
58
37274
    match value {
59
        JsonValue::Object(map) => {
60
            // Replace IDs in three places:
61
            // - "id" fields (SymbolTable IDs)
62
            // - "parent" fields (SymbolTable nesting)
63
            // - "ptr" fields (DeclarationPtr IDs)
64
15744
            for (k, v) in map.iter_mut() {
65
23822
                if (k == "id" || k == "ptr" || k == "parent")
66
2178
                    && let Ok(old_id) = serde_json::from_value::<ObjId>(mem::take(v))
67
                {
68
1704
                    let new_id = id_map.get(&old_id).expect("all ids to be in the id map");
69
1704
                    *v = serde_json::to_value(new_id)
70
1704
                        .expect("serialization of an ObjId to always succeed");
71
22118
                }
72
            }
73

            
74
            // Recursively process all values
75
15744
            for val in map.values_mut() {
76
23822
                replace_ids(val, id_map);
77
            }
78
        }
79
        JsonValue::Array(arr) => {
80
7192
            for item in arr {
81
12978
                replace_ids(item, id_map);
82
            }
83
        }
84
14338
        _ => {}
85
    }
86
37274
}
87

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

            
91
    let mut a_rows: Vec<HashSet<T>> = Vec::new();
92
    for row in a {
93
        let hash_row = to_set(row);
94
        a_rows.push(hash_row);
95
    }
96

            
97
    let mut b_rows: Vec<HashSet<T>> = Vec::new();
98
    for row in b {
99
        let hash_row = to_set(row);
100
        b_rows.push(hash_row);
101
    }
102

            
103
    for row in a_rows {
104
        assert!(b_rows.contains(&row));
105
    }
106
}
107

            
108
474
pub fn serialize_model(model: &ConjureModel) -> Result<String, JsonError> {
109
474
    let serde_model: SerdeModel = model.clone().into();
110

            
111
    // Convert to JSON with stable IDs
112
474
    let json_with_stable_ids = model_to_json_with_stable_ids(&serde_model)?;
113

            
114
    // Sort JSON object keys for consistent output
115
474
    let sorted_json = sort_json_object(&json_with_stable_ids, false);
116

            
117
    // Serialize to pretty-printed string
118
474
    serde_json::to_string_pretty(&sorted_json)
119
474
}
120

            
121
108
pub fn serialize_domains(model: &ConjureModel) -> Result<String, JsonError> {
122
108
    let mut output = String::new();
123
108
    for constraint in model.constraints() {
124
138
        serialize_domains_expr(constraint, 0, &mut output);
125
    }
126
108
    Ok(output)
127
108
}
128

            
129
708
fn serialize_domains_expr(expr: &Expression, depth: usize, output: &mut String) {
130
708
    let domain = expr
131
708
        .domain_of()
132
708
        .map(|domain| domain.to_string())
133
        .unwrap_or_else(|| "<unknown>".to_owned());
134
708
    output.push_str(&" ".repeat(depth));
135
708
    output.push_str(&pretty_expression_domain_annotation(expr, domain));
136
708
    output.push('\n');
137

            
138
708
    for child in expr.children() {
139
570
        serialize_domains_expr(&child, depth + 1, output);
140
    }
141
708
}
142

            
143
pub fn save_model_json(
144
    model: &ConjureModel,
145
    path: &str,
146
    test_name: &str,
147
    test_stage: &str,
148
    solver: SolverFamily,
149
) -> Result<(), std::io::Error> {
150
    let marker = solver.as_str();
151
    let generated_json_str = serialize_model(model)?;
152
    let generated_json_str = maybe_truncate_serialised_json(generated_json_str, test_stage);
153
    let filename = format!("{path}/{test_name}-{marker}.generated-{test_stage}.serialised.json");
154
    println!("saving: {filename}");
155
    std::fs::write(filename, format!("{generated_json_str}\n"))?;
156
    Ok(())
157
}
158

            
159
167132
pub fn save_stats_json(
160
167132
    context: Arc<RwLock<Context<'static>>>,
161
167132
    path: &str,
162
167132
    test_name: &str,
163
167132
    solver: SolverFamily,
164
167132
) -> Result<(), std::io::Error> {
165
    #[allow(clippy::unwrap_used)]
166
167132
    let solver_name = solver.as_str();
167

            
168
167132
    let stats = context.read().unwrap().clone();
169
167132
    let generated_json = sort_json_object(&serde_json::to_value(stats)?, false);
170

            
171
    // serialise to string
172
167132
    let generated_json_str = serde_json::to_string_pretty(&generated_json)?;
173

            
174
167132
    std::fs::write(
175
167132
        format!("{path}/{test_name}-{solver_name}-stats.json"),
176
167132
        format!("{generated_json_str}\n"),
177
167132
    )?;
178

            
179
167132
    Ok(())
180
167132
}
181

            
182
/// Reads a file into a `String`, providing a clearer error message that includes the file path.
183
19632
fn read_with_path(path: String) -> Result<String, std::io::Error> {
184
19632
    std::fs::read_to_string(&path)
185
        .map_err(|e| io::Error::new(e.kind(), format!("{e} (path: {path})")))
186
19632
}
187

            
188
pub fn read_model_json(
189
    ctx: &Arc<RwLock<Context<'static>>>,
190
    path: &str,
191
    test_name: &str,
192
    prefix: &str,
193
    test_stage: &str,
194
    solver: SolverFamily,
195
) -> Result<ConjureModel, std::io::Error> {
196
    let marker = solver.as_str();
197
    let filepath = format!("{path}/{test_name}-{marker}.{prefix}-{test_stage}.serialised.json");
198
    let expected_json_str = std::fs::read_to_string(filepath)?;
199
    let expected_model: SerdeModel = serde_json::from_str(&expected_json_str)?;
200

            
201
    Ok(expected_model.initialise(ctx.clone()).unwrap())
202
}
203

            
204
/// Reads only the first `max_lines` from a serialised model JSON file.
205
pub fn read_model_json_prefix(
206
    path: &str,
207
    test_name: &str,
208
    prefix: &str,
209
    test_stage: &str,
210
    solver: SolverFamily,
211
    max_lines: usize,
212
) -> Result<String, std::io::Error> {
213
    let marker = solver.as_str();
214
    let filename = format!("{path}/{test_name}-{marker}.{prefix}-{test_stage}.serialised.json");
215
    println!("reading: {filename}");
216
    read_first_n_lines(filename, max_lines)
217
}
218

            
219
pub fn minion_solutions_from_json(
220
    serialized: &str,
221
) -> Result<Vec<HashMap<Name, Literal>>, anyhow::Error> {
222
    let json: JsonValue = serde_json::from_str(serialized)?;
223

            
224
    let json_array = json
225
        .as_array()
226
        .ok_or(Error::Parse("Invalid JSON".to_owned()))?;
227

            
228
    let mut solutions = Vec::new();
229

            
230
    for solution in json_array {
231
        let mut sol = HashMap::new();
232
        let solution = solution
233
            .as_object()
234
            .ok_or(Error::Parse("Invalid JSON".to_owned()))?;
235

            
236
        for (var_name, constant) in solution {
237
            let constant = match constant {
238
                JsonValue::Number(n) => {
239
                    let n = n
240
                        .as_i64()
241
                        .ok_or(Error::Parse("Invalid integer".to_owned()))?;
242
                    Literal::Int(n as i32)
243
                }
244
                JsonValue::Bool(b) => Literal::Bool(*b),
245
                _ => return Err(Error::Parse("Invalid constant".to_owned()).into()),
246
            };
247

            
248
            sol.insert(User(var_name.into()), constant);
249
        }
250

            
251
        solutions.push(sol);
252
    }
253

            
254
    Ok(solutions)
255
}
256

            
257
/// Writes solutions in Conjure's multi-solution Essence format.
258
167104
pub fn save_solutions_essence(
259
167104
    solutions: &[BTreeMap<Name, Literal>],
260
167104
    path: &str,
261
167104
    test_name: &str,
262
167104
    solver: SolverFamily,
263
167104
) -> Result<String, std::io::Error> {
264
167104
    let rendered = solutions_to_essence(solutions);
265
167104
    let solver_name = solver.as_str();
266
167104
    let filename = format!("{path}/{test_name}-{solver_name}.generated.solutions");
267
167104
    std::fs::write(filename, &rendered)?;
268

            
269
167104
    Ok(rendered)
270
167104
}
271

            
272
7048
pub fn read_solutions_essence(
273
7048
    path: &str,
274
7048
    test_name: &str,
275
7048
    prefix: &str,
276
7048
    solver: SolverFamily,
277
7048
) -> Result<String, anyhow::Error> {
278
7048
    let solver_name = solver.as_str();
279
7048
    let filename = format!("{path}/{test_name}-{solver_name}.{prefix}.solutions");
280
7048
    Ok(read_with_path(filename)?)
281
7048
}
282

            
283
/// Reads a default rule trace text file.
284
12584
pub fn read_default_rule_trace(
285
12584
    path: &str,
286
12584
    test_name: &str,
287
12584
    prefix: &str,
288
12584
    solver: &SolverFamily,
289
12584
) -> Result<String, std::io::Error> {
290
12584
    let solver_name = solver.as_str();
291
12584
    let filename = format!("{path}/{test_name}-{solver_name}-{prefix}-rule-trace.txt");
292
12584
    Ok(truncate_to_first_chars(
293
12584
        &read_with_path(filename)?,
294
12584
        DEFAULT_TEXT_SNAPSHOT_CHARACTER_LIMIT,
295
    ))
296
12584
}
297

            
298
#[doc(hidden)]
299
161698
pub fn normalize_solutions_for_comparison(
300
161698
    input_solutions: &[BTreeMap<Name, Literal>],
301
161698
) -> Vec<BTreeMap<Name, Literal>> {
302
161698
    let mut normalized = input_solutions.to_vec();
303

            
304
161698
    for solset in &mut normalized {
305
        // remove machine names
306
1921970
        let keys_to_remove: Vec<Name> = solset
307
1921970
            .keys()
308
5965238
            .filter(|k| matches!(k, Name::Machine(_)))
309
            .cloned()
310
            .collect();
311
1921970
        for k in keys_to_remove {
312
            solset.remove(&k);
313
        }
314

            
315
1921970
        let mut updates = vec![];
316
1921970
        for (k, v) in solset.clone() {
317
5965238
            if let Name::User(_) = k {
318
5965238
                match v {
319
8420
                    Literal::Bool(true) => updates.push((k, Literal::Int(1))),
320
10372
                    Literal::Bool(false) => updates.push((k, Literal::Int(0))),
321
4788044
                    Literal::Int(_) | Literal::EnumVariant(_) | Literal::Unnamed(_) => {}
322
                    Literal::AbstractLiteral(AbstractLiteral::Matrix(elems, _)) => {
323
                        // make all domains the same (this is just in the tester so the types dont
324
                        // actually matter)
325

            
326
805820
                        let mut matrix = AbstractLiteral::Matrix(
327
805820
                            elems,
328
805820
                            Moo::new(GroundDomain::Int(vec![], None)),
329
805820
                        );
330
1308696
                        matrix = matrix.transform(&move |x: AbstractLiteral<Literal>| match x {
331
                            AbstractLiteral::Matrix(items, _) => {
332
986400
                                let items = items
333
986400
                                    .into_iter()
334
2998792
                                    .map(|x| match x {
335
75156
                                        Literal::Bool(false) => Literal::Int(0),
336
86296
                                        Literal::Bool(true) => Literal::Int(1),
337
2837340
                                        x => x,
338
2998792
                                    })
339
                                    .collect_vec();
340

            
341
986400
                                AbstractLiteral::Matrix(
342
986400
                                    items,
343
986400
                                    Moo::new(GroundDomain::Int(vec![], None)),
344
986400
                                )
345
                            }
346
322296
                            x => x,
347
1308696
                        });
348
805820
                        updates.push((k, Literal::AbstractLiteral(matrix)));
349
                    }
350
                    Literal::AbstractLiteral(AbstractLiteral::Tuple(elems)) => {
351
                        // just the same as matrix but with tuples instead
352
                        // only conversion needed is to convert bools to ints
353
10688
                        let mut tuple = AbstractLiteral::Tuple(elems);
354
10688
                        tuple = tuple.transform(
355
15800
                            &(move |x: AbstractLiteral<Literal>| match x {
356
                                AbstractLiteral::Tuple(items) => {
357
15800
                                    let items = items
358
15800
                                        .into_iter()
359
31600
                                        .map(|x| match x {
360
6064
                                            Literal::Bool(false) => Literal::Int(0),
361
6864
                                            Literal::Bool(true) => Literal::Int(1),
362
18672
                                            x => x,
363
31600
                                        })
364
                                        .collect_vec();
365

            
366
15800
                                    AbstractLiteral::Tuple(items)
367
                                }
368
                                x => x,
369
                            }),
370
                        );
371
10688
                        updates.push((k, Literal::AbstractLiteral(tuple)));
372
                    }
373
                    Literal::AbstractLiteral(AbstractLiteral::Record(entries)) => {
374
                        // just the same as matrix but with tuples instead
375
                        // only conversion needed is to convert bools to ints
376
59040
                        let mut record = AbstractLiteral::Record(entries);
377
59040
                        record = record.transform(&move |x: AbstractLiteral<Literal>| match x {
378
                            AbstractLiteral::Record(entries) => {
379
59040
                                let entries = entries
380
59040
                                    .into_iter()
381
118080
                                    .map(|x| {
382
118080
                                        let Field { name, value } = x;
383
                                        {
384
118080
                                            let value = match value {
385
28104
                                                Literal::Bool(false) => Literal::Int(0),
386
30936
                                                Literal::Bool(true) => Literal::Int(1),
387
59040
                                                x => x,
388
                                            };
389
118080
                                            Field { name, value }
390
                                        }
391
118080
                                    })
392
                                    .collect_vec();
393

            
394
59040
                                AbstractLiteral::Record(entries)
395
                            }
396
                            x => x,
397
59040
                        });
398
59040
                        updates.push((k, Literal::AbstractLiteral(record)));
399
                    }
400
                    Literal::AbstractLiteral(AbstractLiteral::Variant(entry)) => {
401
4672
                        let mut variant = AbstractLiteral::Variant(entry);
402
4672
                        variant = variant.transform(&move |x| match x {
403
                            AbstractLiteral::Variant(entry) => {
404
4672
                                let Field { name, value } = Moo::unwrap_or_clone(entry);
405
4672
                                let value = match value {
406
584
                                    Literal::Bool(false) => Literal::Int(0),
407
1752
                                    Literal::Bool(true) => Literal::Int(1),
408
2336
                                    value => value,
409
                                };
410
4672
                                AbstractLiteral::Variant(Moo::new(Field { name, value }))
411
                            }
412
                            value => value,
413
4672
                        });
414
4672
                        updates.push((k, Literal::AbstractLiteral(variant)));
415
                    }
416
                    Literal::AbstractLiteral(AbstractLiteral::Set(members)) => {
417
270938
                        let set = AbstractLiteral::Set(members).transform(&move |x| match x {
418
                            AbstractLiteral::Set(members) => {
419
207970
                                let members = members
420
207970
                                    .into_iter()
421
357374
                                    .map(|x| match x {
422
8
                                        Literal::Bool(false) => Literal::Int(0),
423
4
                                        Literal::Bool(true) => Literal::Int(1),
424
357362
                                        x => x,
425
357374
                                    })
426
                                    .collect_vec();
427

            
428
207970
                                AbstractLiteral::Set(members)
429
                            }
430
62968
                            x => x,
431
270938
                        });
432
135846
                        updates.push((k, Literal::AbstractLiteral(set)));
433
                    }
434
                    Literal::AbstractLiteral(AbstractLiteral::MSet(members)) => {
435
71004
                        let mset = AbstractLiteral::MSet(members).transform(&move |x| match x {
436
                            AbstractLiteral::MSet(members) => {
437
71004
                                let members = members
438
71004
                                    .into_iter()
439
169392
                                    .map(|x| match x {
440
                                        Literal::Bool(false) => Literal::Int(0),
441
                                        Literal::Bool(true) => Literal::Int(1),
442
169392
                                        x => x,
443
169392
                                    })
444
                                    .collect_vec();
445
71004
                                AbstractLiteral::MSet(members)
446
                            }
447
                            x => x,
448
71004
                        });
449
71004
                        updates.push((k, Literal::AbstractLiteral(mset)));
450
                    }
451
                    Literal::AbstractLiteral(AbstractLiteral::Sequence(elems)) => {
452
                        let sequence =
453
50320
                            AbstractLiteral::Sequence(elems).transform(&move |x| match x {
454
                                AbstractLiteral::Sequence(elems) => {
455
50320
                                    let elems = elems
456
50320
                                        .into_iter()
457
44672
                                        .map(|x| match x {
458
                                            Literal::Bool(false) => Literal::Int(0),
459
                                            Literal::Bool(true) => Literal::Int(1),
460
44672
                                            x => x,
461
44672
                                        })
462
                                        .collect_vec();
463
50320
                                    AbstractLiteral::Sequence(elems)
464
                                }
465
                                x => x,
466
50320
                            });
467
50320
                        updates.push((k, Literal::AbstractLiteral(sequence)));
468
                    }
469
                    Literal::AbstractLiteral(AbstractLiteral::Function(pairs)) => {
470
                        let function =
471
23548
                            AbstractLiteral::Function(pairs).transform(&move |x| match x {
472
                                AbstractLiteral::Function(pairs) => {
473
17356
                                    let pairs = pairs
474
17356
                                        .into_iter()
475
37872
                                        .map(|(key, value)| {
476
75744
                                            let normalize = |x| match x {
477
856
                                                Literal::Bool(false) => Literal::Int(0),
478
856
                                                Literal::Bool(true) => Literal::Int(1),
479
74032
                                                x => x,
480
75744
                                            };
481
37872
                                            (normalize(key), normalize(value))
482
37872
                                        })
483
                                        .collect_vec();
484
17356
                                    AbstractLiteral::Function(pairs)
485
                                }
486
6192
                                x => x,
487
23548
                            });
488
17356
                        updates.push((k, Literal::AbstractLiteral(function)));
489
                    }
490
                    Literal::AbstractLiteral(AbstractLiteral::Relation(tuples)) => {
491
                        let relation =
492
1496
                            AbstractLiteral::Relation(tuples).transform(&move |x| match x {
493
                                AbstractLiteral::Relation(tuples) => {
494
1496
                                    let tuples = tuples
495
1496
                                        .into_iter()
496
5512
                                        .map(|fields| {
497
5512
                                            fields
498
5512
                                                .into_iter()
499
11664
                                                .map(|x| match x {
500
                                                    Literal::Bool(false) => Literal::Int(0),
501
                                                    Literal::Bool(true) => Literal::Int(1),
502
11664
                                                    x => x,
503
11664
                                                })
504
                                                .collect_vec()
505
5512
                                        })
506
                                        .collect_vec();
507
1496
                                    AbstractLiteral::Relation(tuples)
508
                                }
509
                                x => x,
510
1496
                            });
511
1496
                        updates.push((k, Literal::AbstractLiteral(relation)));
512
                    }
513
                    Literal::AbstractLiteral(AbstractLiteral::Partition(parts)) => {
514
                        let partition =
515
                            AbstractLiteral::Partition(parts).transform(&move |x| match x {
516
                                AbstractLiteral::Partition(parts) => {
517
                                    let parts = parts
518
                                        .into_iter()
519
                                        .map(|part| {
520
                                            part.into_iter()
521
                                                .map(|x| match x {
522
                                                    Literal::Bool(false) => Literal::Int(0),
523
                                                    Literal::Bool(true) => Literal::Int(1),
524
                                                    x => x,
525
                                                })
526
                                                .collect_vec()
527
                                        })
528
                                        .collect_vec();
529
                                    AbstractLiteral::Partition(parts)
530
                                }
531
                                x => x,
532
                            });
533
                        updates.push((k, Literal::AbstractLiteral(partition)));
534
                    }
535
                    Literal::AbstractLiteral(AbstractLiteral::Permutation(cycles)) => {
536
                        let permutation =
537
2160
                            AbstractLiteral::Permutation(cycles).transform(&move |x| match x {
538
                                AbstractLiteral::Permutation(cycles) => {
539
2160
                                    let cycles = cycles
540
2160
                                        .into_iter()
541
2592
                                        .map(|cycle| {
542
2592
                                            cycle
543
2592
                                                .into_iter()
544
7488
                                                .map(|x| match x {
545
                                                    Literal::Bool(false) => Literal::Int(0),
546
                                                    Literal::Bool(true) => Literal::Int(1),
547
7488
                                                    x => x,
548
7488
                                                })
549
                                                .collect_vec()
550
2592
                                        })
551
                                        .collect_vec();
552
2160
                                    AbstractLiteral::Permutation(cycles)
553
                                }
554
                                x => x,
555
2160
                            });
556
2160
                        updates.push((k, Literal::AbstractLiteral(permutation)));
557
                    }
558
                }
559
            }
560
        }
561

            
562
1921970
        for (k, v) in updates {
563
1177194
            let v = match v {
564
                Literal::AbstractLiteral(value) => {
565
1158402
                    Literal::AbstractLiteral(normalize_set_literal_order(value))
566
                }
567
18792
                value => value,
568
            };
569
1177194
            solset.insert(k, v);
570
        }
571
    }
572

            
573
    // Remove duplicates and put solutions in a stable order for set-equality compares.
574
161698
    normalized = normalized.into_iter().unique().collect();
575
161698
    normalized.sort_by(solution_essence_cmp);
576
161698
    normalized
577
161698
}
578

            
579
9733800
fn solution_essence_cmp(
580
9733800
    lhs: &BTreeMap<Name, Literal>,
581
9733800
    rhs: &BTreeMap<Name, Literal>,
582
9733800
) -> std::cmp::Ordering {
583
9733800
    lhs.iter()
584
9733800
        .zip(rhs)
585
17675468
        .find_map(|((lhs_name, lhs_value), (rhs_name, rhs_value))| {
586
17675468
            let ordering = lhs_name.cmp(rhs_name);
587
17675468
            (ordering != std::cmp::Ordering::Equal)
588
17675468
                .then_some(ordering)
589
17675468
                .or_else(|| {
590
17675468
                    let ordering = lhs_value.essence_cmp(rhs_value);
591
17675468
                    (ordering != std::cmp::Ordering::Equal).then_some(ordering)
592
17675468
                })
593
17675468
        })
594
        .unwrap_or_else(|| lhs.len().cmp(&rhs.len()))
595
9733800
}
596

            
597
1158402
fn normalize_set_literal_order(value: AbstractLiteral<Literal>) -> AbstractLiteral<Literal> {
598
1807674
    value.transform(&|value| match value {
599
        AbstractLiteral::Set(mut members) => {
600
209170
            members.sort_by(Literal::essence_cmp);
601
209170
            AbstractLiteral::Set(members)
602
        }
603
        AbstractLiteral::MSet(mut members) => {
604
71004
            members.sort_by(Literal::essence_cmp);
605
71004
            AbstractLiteral::MSet(members)
606
        }
607
        AbstractLiteral::Function(mut pairs) => {
608
20796
            pairs.sort_by(|(k1, _), (k2, _)| Literal::essence_cmp(k1, k2));
609
17356
            AbstractLiteral::Function(pairs)
610
        }
611
        AbstractLiteral::Relation(mut tuples) => {
612
4064
            tuples.sort_by(|a, b| {
613
4064
                a.iter()
614
4064
                    .zip(b.iter())
615
5736
                    .map(|(x, y)| Literal::essence_cmp(x, y))
616
5736
                    .find(|ord| *ord != std::cmp::Ordering::Equal)
617
4064
                    .unwrap_or(std::cmp::Ordering::Equal)
618
4064
            });
619
1496
            AbstractLiteral::Relation(tuples)
620
        }
621
        AbstractLiteral::Partition(mut parts) => {
622
            for part in parts.iter_mut() {
623
                part.sort_by(Literal::essence_cmp);
624
            }
625
            parts.sort_by(|a, b| {
626
                a.iter()
627
                    .zip(b.iter())
628
                    .map(|(x, y)| Literal::essence_cmp(x, y))
629
                    .find(|ord| *ord != std::cmp::Ordering::Equal)
630
                    .unwrap_or_else(|| a.len().cmp(&b.len()))
631
            });
632
            AbstractLiteral::Partition(parts)
633
        }
634
1508648
        value => value,
635
1807674
    })
636
1158402
}
637

            
638
fn maybe_truncate_serialised_json(serialised: String, test_stage: &str) -> String {
639
    if test_stage == "rewrite" {
640
        truncate_to_first_lines(&serialised, REWRITE_SERIALISED_JSON_MAX_LINES)
641
    } else {
642
        serialised
643
    }
644
}
645

            
646
fn truncate_to_first_lines(content: &str, max_lines: usize) -> String {
647
    content.lines().take(max_lines).join("\n")
648
}
649

            
650
13976
pub fn truncate_to_first_chars(content: &str, max_chars: usize) -> String {
651
13976
    match content.char_indices().nth(max_chars) {
652
        Some((idx, _)) => content[..idx].to_owned(),
653
13976
        None => content.to_owned(),
654
    }
655
13976
}
656

            
657
fn read_first_n_lines<P: AsRef<Path>>(filename: P, n: usize) -> io::Result<String> {
658
    let reader = BufReader::new(File::open(&filename)?);
659
    let lines = reader
660
        .lines()
661
        .chunks(n)
662
        .into_iter()
663
        .next()
664
        .unwrap()
665
        .collect::<Result<Vec<_>, _>>()?;
666
    Ok(lines.join("\n"))
667
}
668

            
669
#[cfg(test)]
670
mod tests {
671
    use super::*;
672

            
673
6
    fn set(members: Vec<Literal>) -> Literal {
674
6
        Literal::AbstractLiteral(AbstractLiteral::Set(members))
675
6
    }
676

            
677
    #[test]
678
1
    fn solution_normalization_sorts_nested_set_members_by_essence_order() {
679
1
        let inner_one_two = set(vec![Literal::Int(2), Literal::Int(1)]);
680
1
        let inner_two = set(vec![Literal::Int(2)]);
681
1
        let mut oxide_solution = BTreeMap::new();
682
1
        oxide_solution.insert(
683
1
            Name::User("x".into()),
684
1
            set(vec![inner_two.clone(), inner_one_two.clone()]),
685
1
        );
686
1
        let mut conjure_solution = BTreeMap::new();
687
1
        conjure_solution.insert(
688
1
            Name::User("x".into()),
689
1
            set(vec![inner_one_two, inner_two.clone()]),
690
1
        );
691

            
692
1
        let normalized_oxide = normalize_solutions_for_comparison(&[oxide_solution]);
693
1
        let normalized_conjure = normalize_solutions_for_comparison(&[conjure_solution]);
694
1
        let expected = set(vec![inner_two, set(vec![Literal::Int(1), Literal::Int(2)])]);
695

            
696
1
        assert_eq!(normalized_oxide, normalized_conjure);
697
1
        assert_eq!(
698
1
            normalized_oxide[0].get(&Name::User("x".into())),
699
1
            Some(&expected)
700
        );
701
1
    }
702
}