Skip to content

Commit

Permalink
Model example library for testing (#157)
Browse files Browse the repository at this point in the history
* Eliminated example/ dir to integrate with conjure_oxide/tests/integration dir

* init attempt for generate_custom.rs for example custom string input

* Added comprehension and enumerate essence file examples

* generate_custom.rs temporary fix for walkdir filter filename [DOESN'T COMPILE]

* added finite given [set] tests

* fix for generate_custom.rs get_example_model: only filename and correct walkdir filter

* added 'interesting tests' to integration test dir

* fix dependencies in main.rs for custom_example

* (ignore) playing around with code coverage sample yml file

* final changes custom return model function

* changed main.rs cfg tests to xyz instead of bool (no current support)

* eliminated yaml code-coverage file for PR test passing

* debug boolean 01/02/03 integration test with cargo test

* removed conjure-output for basic/01 bool tests folder
  • Loading branch information
PedroGGBM authored Feb 21, 2024
1 parent 426b082 commit fb407f3
Show file tree
Hide file tree
Showing 32 changed files with 563 additions and 64 deletions.
3 changes: 2 additions & 1 deletion conjure_oxide/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ clap = { version = "4.5.1", features = ["derive"] }
strum_macros = "0.26.1"
strum = "0.26.1"
versions = "6.1.0"
linkme = "0.3.23"
linkme = "0.3.22"
walkdir = "2.4.0"

[features]

Expand Down
160 changes: 160 additions & 0 deletions conjure_oxide/src/generate_custom.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// generate_custom.rs with get_example_model function

// dependencies
use crate::parse::model_from_json;
use conjure_core::ast::Model;
use std::env;
use std::error::Error;
use std::fs::{copy, read_to_string, File};
use std::io::Write;
use std::path::PathBuf;
use walkdir::WalkDir;

use serde_json::Value;

/// Searches recursively in `../tests/integration` folder for an `.essence` file matching the given filename,
/// then uses conjure to process it into astjson, and returns the parsed model.
///
/// # Arguments
///
/// * `filename` - A string slice that holds filename without extension
///
/// # Returns
///
/// Function returns a `Result<Value, Box<dyn Error>>`, where `Value` is the parsed model
pub fn get_example_model(filename: &str) -> Result<Model, Box<dyn Error>> {
// define relative path -> integration tests dir
let base_dir = "tests/integration";
let mut essence_path = PathBuf::new();

// walk through directory tree recursively starting at base
for entry in WalkDir::new(base_dir).into_iter().filter_map(|e| e.ok()) {
let path = entry.path();
if path.is_file()
&& path.extension().map_or(false, |e| e == "essence")
&& path.file_stem() == Some(std::ffi::OsStr::new(filename))
{
essence_path = path.to_path_buf();
break;
}
}

println!("PATH TO FILE: {}", essence_path.display());

// return error if file not found
if essence_path.as_os_str().is_empty() {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::NotFound,
"ERROR: File not found in any subdirectory",
)));
}

// let path = PathBuf::from(format!("../tests/integration/basic/comprehension{}.essence", filename));
let mut cmd = std::process::Command::new("conjure");
let output = cmd
.arg("pretty")
.arg("--output-format=astjson")
.arg(essence_path)
.output()?;

// convert Conjure's stdout from bytes to string
let astjson = String::from_utf8(output.stdout)?;

println!("ASTJSON: {}", astjson);

// parse AST JSON from desired Model format
let generated_mdl = model_from_json(&astjson)?;

Ok(generated_mdl)
}

/// Searches for an `.essence` file at the given filepath,
/// then uses conjure to process it into astjson, and returns the parsed model.
///
/// # Arguments
///
/// * `filepath` - A string slice that holds the full file path
///
/// # Returns
///
/// Function returns a `Result<Value, Box<dyn Error>>`, where `Value` is the parsed model
pub fn get_example_model_by_path(filepath: &str) -> Result<Model, Box<dyn Error>> {
let essence_path = PathBuf::from(filepath);

// return error if file not found
if essence_path.as_os_str().is_empty() {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::NotFound,
"ERROR: File not found in any subdirectory",
)));
}

println!("PATH TO FILE: {}", essence_path.display());

// Command execution using 'conjure' CLI tool with provided path
let mut cmd = std::process::Command::new("conjure");
let output = cmd
.arg("pretty")
.arg("--output-format=astjson")
.arg(&essence_path)
.output()?;

// convert Conjure's stdout from bytes to string
let astjson = String::from_utf8(output.stdout)?;

println!("ASTJSON: {}", astjson);

// parse AST JSON into the desired Model format
let generated_model = model_from_json(&astjson)?;

Ok(generated_model)
}

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

Value::Object(ordered.into_iter().collect())
}
Value::Array(arr) => Value::Array(arr.iter().map(sort_json_object).collect()),
_ => value.clone(),
}
}

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

let b_obj = &b.as_array().unwrap()[0];
let b_name: crate::ast::Name = serde_json::from_value(b_obj.clone()).unwrap();

a_name.cmp(&b_name)
});
Value::Array(vars_sorted)
}
_ => value.clone(),
}
}
1 change: 1 addition & 0 deletions conjure_oxide/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pub mod error;
pub mod find_conjure;
pub mod generate_custom;
pub mod parse;
pub mod rule_engine;
mod rules;
Expand Down
30 changes: 30 additions & 0 deletions conjure_oxide/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use std::sync::Mutex;
use anyhow::Result as AnyhowResult;
use clap::{arg, command, Parser};
use conjure_oxide::find_conjure::conjure_executable;
use conjure_oxide::generate_custom::{get_example_model, get_example_model_by_path};
use conjure_oxide::parse::model_from_json;
use conjure_oxide::rule_engine::resolve_rules::{
get_rule_priorities, get_rules_vec, resolve_rule_sets,
Expand Down Expand Up @@ -121,3 +122,32 @@ pub fn main() -> AnyhowResult<()> {

Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_get_example_model_success() {
let filename = "input";
get_example_model(filename).unwrap();
}

#[test]
fn test_get_example_model_by_filepath() {
let filepath = "tests/integration/xyz/input.essence";
get_example_model_by_path(filepath).unwrap();
}

#[test]
fn test_get_example_model_fail_empty_filename() {
let filename = "";
get_example_model(filename).unwrap_err();
}

#[test]
fn test_get_example_model_fail_empty_filepath() {
let filepath = "";
get_example_model_by_path(filepath).unwrap_err();
}
}
2 changes: 1 addition & 1 deletion conjure_oxide/tests/generated_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ fn main() {
}
}

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

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language Essence 1.3

find x : int(0..1000)
such that x = sum([ 1 | i : set (size 2) of int(7..9) ])
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language Essence 1.3

find x : int(0..1000)
find y : int(7,8)
such that x = sum([ 1 | i : set (size 2) of int(7..9)
, y in i
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language Essence 1.3

find x : int(0..1000)
such that x = sum([ j | i : set (minSize 1, maxSize 2) of int(7..8), j <- i ])
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
language Essence 1.3

find x : int(0..1000)
find y : int(7,8)
such that x = sum([ j | i : set (minSize 1, maxSize 2) of int(7..8)
, y in i
, j <- i
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language Essence 1.3

find x : int(0..1000)
such that x = sum([ 1 | i : set (minSize 1, maxSize 2) of int(7..9) ])
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language Essence 1.3

find x : int(0..1000)
find y : int(7,8)
such that x = sum([ 1 | i : set (minSize 1, maxSize 2) of int(7..9)
, y in i
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language Essence 1.3

find x : int(0..1000)
such that x = sum([ 1 | i : set (minSize 1, maxSize 2) of (int(7..9), bool) ])
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
language Essence 1.3

find x : int(0..1000)
find y : int(7,8)
find z : bool
such that x = sum([ 1 | i : set (minSize 1, maxSize 2) of (int(7..9), bool)
, (y,z) in i
])
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
language Essence 1.3

given E1 new type enum
find x : E1

letting E2 be new type enum {a,b,c,d}
find y : E2
find z : E2(a..c)
find t : E2(b,d)

such that y = z, z = t
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
language Essence 1.3

given E1 new type enum
letting E2 be new type enum {a,b,c,d}

find x : (E1, E2)

such that x[2] = a
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
language Essence 1.3

given E1 new type enum
find x : E1
such that forAll i : E1 . x <= i

letting E2 be new type enum {a,b,c,d}
find y : E2
such that forAll j : E2 . y <= j
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language Essence 1.3

given a : set of int
find x,y,z : int(0..100)
such that x = |a|
such that y = min(a)
such that z = max(a)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
language Essence 1.3

given a : set of int
find x : int(|a|)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
language Essence 1.3

given a : set of int
find x,y,z,t : int(0..100)
such that x = |a|
such that y = min(a)
such that z = max(a)
such that allDiff([x,y,z,t])
such that t in a $ refer to a
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language Essence 1.3

given a : set of set of int
find x : int(-1000..1000)
such that x = max([ max(i) | i <- a ])
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
language Essence 1.3

given s: set of set (minSize 0) of int(2..5, 4)
find x : int(0..9)
such that exists s' in s . x in s'
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
language Essence 1.3

letting n be 5
letting g be relation
( (1,2)
, (2,1)
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
language Essence 1.3

letting n be 5
letting g be relation
( (1,2)
, (2,3)
, (3,1)
)
Loading

0 comments on commit fb407f3

Please sign in to comment.