1
use conjure_oxide::ast::Model;
2
use conjure_oxide::parse::model_from_json;
3
use conjure_oxide::solvers::minion::MinionModel;
4
use conjure_oxide::solvers::FromConjureModel;
5
use serde_json::Value;
6
use std::collections::HashMap;
7
use std::env;
8
use std::error::Error;
9
use std::fs::File;
10
use std::io::prelude::*;
11

            
12
use conjure_oxide::rewrite::rewrite_model;
13
use std::path::Path;
14

            
15
fn main() {
16
    let file_path = Path::new("/path/to/your/file.txt");
17
    let base_name = file_path.file_stem().and_then(|stem| stem.to_str());
18

            
19
    match base_name {
20
        Some(name) => println!("Base name: {}", name),
21
        None => println!("Could not extract the base name"),
22
    }
23
}
24

            
25
pub fn integration_test(path: &str, essence_base: &str) -> Result<(), Box<dyn Error>> {
26
    // --------------------------------------------------------------------------------
27
    // -- parsing the essence file
28

            
29
    // calling conjure to convert Essence to astjson
30
    eprintln!("RUN: model parse check");
31
    let mut cmd = std::process::Command::new("conjure");
32
    let output = cmd
33
        .arg("pretty")
34
        .arg("--output-format=astjson")
35
        .arg(format!("{path}/{essence_base}.essence"))
36
        .output()?;
37
    let stderr_string = String::from_utf8(output.stderr)?;
38
    assert!(
39
        stderr_string.is_empty(),
40
        "conjure's stderr is not empty: {}",
41
        stderr_string
42
    );
43
    let astjson = String::from_utf8(output.stdout)?;
44

            
45
    // "parsing" astjson as Model
46
    let parsed_model = model_from_json(&astjson)?;
47

            
48
    // a consistent sorting of the keys of json objects
49
    // only required for the generated version
50
    // since the expected version will already be sorted
51
    let generated_json = sort_json_object(&serde_json::to_value(parsed_model.clone())?);
52

            
53
    // serialise to file
54
    let generated_json_str = serde_json::to_string_pretty(&generated_json)?;
55
    File::create(format!(
56
        "{path}/{essence_base}.generated-parse.serialised.json"
57
    ))?
58
    .write_all(generated_json_str.as_bytes())?;
59

            
60
    if std::env::var("ACCEPT").map_or(false, |v| v == "true") {
61
        std::fs::copy(
62
            format!("{path}/{essence_base}.generated-parse.serialised.json"),
63
            format!("{path}/{essence_base}.expected-parse.serialised.json"),
64
        )?;
65
    }
66

            
67
    // --------------------------------------------------------------------------------
68
    // -- reading the expected version from the filesystem
69

            
70
    let expected_parse_str = std::fs::read_to_string(format!(
71
        "{path}/{essence_base}.expected-parse.serialised.json"
72
    ))?;
73

            
74
    let expected_parse_mdl: Model = serde_json::from_str(&expected_parse_str)?;
75

            
76
    // --------------------------------------------------------------------------------
77
    // assert that they are the same model
78

            
79
    assert_eq!(parsed_model, expected_parse_mdl);
80

            
81
    // --------------------------------------------------------------------------------
82
    eprintln!("RUN: model rewrite check");
83

            
84
    // ToDo: There is A LOT of duplication here. We should refactor this to a function. In my defense, it was midnight when I wrote this.
85

            
86
    let rewritten_model = rewrite_model(&parsed_model);
87

            
88
    let rewritten_json = sort_json_object(&serde_json::to_value(rewritten_model.clone())?);
89
    let rewritten_json_str = serde_json::to_string_pretty(&rewritten_json)?;
90
    File::create(format!(
91
        "{path}/{essence_base}.generated-rewrite.serialised.json"
92
    ))?
93
    .write_all(rewritten_json_str.as_bytes())?;
94

            
95
    if std::env::var("ACCEPT").map_or(false, |v| v == "true") {
96
        std::fs::copy(
97
            format!("{path}/{essence_base}.generated-rewrite.serialised.json"),
98
            format!("{path}/{essence_base}.expected-rewrite.serialised.json"),
99
        )?;
100
    }
101

            
102
    // --------------------------------------------------------------------------------
103
    // -- reading the expected version from the filesystem
104

            
105
    let expected_rewrite_str = std::fs::read_to_string(format!(
106
        "{path}/{essence_base}.expected-rewrite.serialised.json"
107
    ))?;
108

            
109
    let expected_rewrite_mdl: Model = serde_json::from_str(&expected_rewrite_str)?;
110

            
111
    // --------------------------------------------------------------------------------
112
    // assert that they are the same model
113

            
114
    assert_eq!(rewritten_model, expected_rewrite_mdl);
115

            
116
    eprintln!("RUN: minion solve check");
117
    let minion_model = MinionModel::from_conjure(rewritten_model)?;
118
    minion_rs::run_minion(minion_model, dummy_callback)?;
119

            
120
    Ok(())
121
}
122

            
123
fn dummy_callback(_: HashMap<minion_rs::ast::VarName, minion_rs::ast::Constant>) -> bool {
124
    true
125
}
126

            
127
/// Recursively sorts the keys of all JSON objects within the provided JSON value.
128
///
129
/// serde_json will output JSON objects in an arbitrary key order.
130
/// this is normally fine, except in our use case we wouldn't want to update the expected output again and again.
131
/// so a consistent (sorted) ordering of the keys is desirable.
132
fn sort_json_object(value: &Value) -> Value {
133
    match value {
134
        Value::Object(obj) => {
135
            let mut ordered: Vec<(String, Value)> = obj
136
                .iter()
137
                .map(|(k, v)| {
138
                    if k == "variables" {
139
                        (k.clone(), sort_json_variables(v))
140
                    } else {
141
                        (k.clone(), sort_json_object(v))
142
                    }
143
                })
144
                // .map(|(k, v)| (k.clone(), sort_json_object(v)))
145
                .collect();
146
            ordered.sort_by(|a, b| a.0.cmp(&b.0));
147

            
148
            Value::Object(ordered.into_iter().collect())
149
        }
150
        Value::Array(arr) => Value::Array(arr.iter().map(sort_json_object).collect()),
151
        _ => value.clone(),
152
    }
153
}
154

            
155
/// Sort the "variables" field by name.
156
/// We have to do this separately becasue that field is not a JSON object, instead it's an array of tuples.
157
fn sort_json_variables(value: &Value) -> Value {
158
    match value {
159
        Value::Array(vars) => {
160
            let mut vars_sorted = vars.clone();
161
            vars_sorted.sort_by(|a, b| {
162
                let a_obj = &a.as_array().unwrap()[0];
163
                let a_name: conjure_oxide::ast::Name =
164
                    serde_json::from_value(a_obj.clone()).unwrap();
165

            
166
                let b_obj = &b.as_array().unwrap()[0];
167
                let b_name: conjure_oxide::ast::Name =
168
                    serde_json::from_value(b_obj.clone()).unwrap();
169

            
170
                a_name.cmp(&b_name)
171
            });
172
            Value::Array(vars_sorted)
173
        }
174
        _ => value.clone(),
175
    }
176
}
177

            
178
#[test]
179
fn assert_conjure_present() {
180
    conjure_oxide::find_conjure::conjure_executable().unwrap();
181
}
182

            
183
include!(concat!(env!("OUT_DIR"), "/gen_tests.rs"));