Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions crates/conjure-cp-cli/src/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{path::PathBuf, sync::Arc};
use anyhow::anyhow;
use clap::ValueHint;

use conjure_cp_cli::utils::testing::serialize_model;
use conjure_cp_cli::utils::testing::{serialize_domains, serialize_model};

use crate::cli::{GlobalArgs, LOGGING_HELP_HEADING};
use crate::solve::{init_context, parse};
Expand All @@ -28,10 +28,11 @@ pub fn run_pretty_command(global_args: GlobalArgs, pretty_args: Args) -> anyhow:
// Running the correct method to acquire pretty string
let output = match pretty_args.output_format.as_str() {
"ast-json" => serialize_model(&model),
"expression-domains" => serialize_domains(&model),
// "add_new_flag" => method(),
_ => {
return Err(anyhow!(
"Unknown output format {}; supports [ast-json]",
"Unknown output format {}; supports [ast-json, expression-domains]",
&pretty_args.output_format
));
}
Expand Down
7 changes: 6 additions & 1 deletion crates/conjure-cp-cli/src/utils/testing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std::io::{BufRead, BufReader, Write};
use std::sync::{Arc, RwLock};
use uniplate::Uniplate;

use conjure_cp::ast::{AbstractLiteral, GroundDomain, Moo, SerdeModel};
use conjure_cp::ast::{AbstractLiteral, ExprInfo, GroundDomain, Moo, SerdeModel};
use conjure_cp::context::Context;
use serde_json::{Error as JsonError, Value as JsonValue};

Expand Down Expand Up @@ -115,6 +115,11 @@ pub fn serialize_model(model: &ConjureModel) -> Result<String, JsonError> {
serde_json::to_string_pretty(&sorted_json)
}

pub fn serialize_domains(model: &ConjureModel) -> Result<String, JsonError> {
let exprs: Vec<ExprInfo> = model.constraints().iter().map(ExprInfo::create).collect();
serde_json::to_string_pretty(&exprs)
}

pub fn save_model_json(
model: &ConjureModel,
path: &str,
Expand Down
24 changes: 24 additions & 0 deletions crates/conjure-cp-core/src/ast/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::fmt::{Debug, Display};
use std::hash::Hash;
use std::sync::{Arc, RwLock};

use crate::ast::Domain;
use crate::context::Context;
use crate::{bug, into_matrix_expr};
use derivative::Derivative;
Expand Down Expand Up @@ -549,3 +550,26 @@ impl SerdeModel {
model.collect_stable_id_mapping()
}
}

/// A struct for the information about expressions
#[serde_as]
#[derive(Serialize, Deserialize)]
pub struct ExprInfo {
pretty: String,
domain: Option<Moo<Domain>>,
children: Vec<ExprInfo>,
}

impl ExprInfo {
pub fn create(expr: &Expression) -> ExprInfo {
let pretty = expr.to_string();
let domain = expr.domain_of();
let children = expr.children().iter().map(Self::create).collect();

ExprInfo {
pretty,
domain,
children,
}
}
}
13 changes: 13 additions & 0 deletions tests-integration/tests/domain_tightening/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# Domain Tightening Tests
These tests are designed to check whether or not the pruning stage reduces the domains of expressions without making them invalid.

They will work by running the same underlying functions used in the `pretty <> --output-format=expression-domains` command.

The general steps of the algorithm (TBC) will likely involve:
1. Parse essence file
2. Get the domains of the expressions

We can then add subsequent steps that will run some ac-style algorithm and recheck the domains.

## How to Run
cargo test tests_domain_tightening
114 changes: 114 additions & 0 deletions tests-integration/tests/domain_tightening_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use std::{
env,
error::Error,
fs,
sync::{Arc, RwLock},
};

use conjure_cp::parse::tree_sitter::{parse_essence_file, parse_essence_file_native};
use conjure_cp::settings::Parser;
use conjure_cp::{
Model, ast::ExprInfo, context::Context, parse::tree_sitter::errors::ParseErrorCollection,
};
use tests_integration::TestConfig;

/// Parser function used by expression domain tests.
type ParseFn = fn(&str, Arc<RwLock<Context<'static>>>) -> Result<Model, Box<ParseErrorCollection>>;

/// Runs a test for one model using each configured parser
fn expression_domain_test(
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function is completely copied from roundtrip testing. With the changes from #1548 , I can see:

  1. get accept env var
  2. get testconfig
  3. maybe clean the test dir
  4. run some test for every parser

as very standard in any testing framework. Should this be generalised?
All you would need to add would be a function pointer to the function signature so that it could run whatever function for each test (e.g. expression_domain_test_inner).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same is also true for the clean_test_dir_for_accept function, except that wouldn't need any modification

path: &str,
filename: &str,
extension: &str,
) -> Result<(), Box<dyn Error>> {
let accept = env::var("ACCEPT").unwrap_or("false".to_string()) == "true";

let file_config: TestConfig =
if let Ok(config_contents) = fs::read_to_string(format!("{path}/config.toml")) {
toml::from_str(&config_contents).unwrap()
} else {
Default::default()
};

if accept {
clean_test_dir_for_accept(path)?;
}

let parsers = file_config
.configured_parsers()
.map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidInput, err))?;

for parser in parsers {
let case_name = parser.to_string();
let parse = match parser {
Parser::TreeSitter => parse_essence_file_native,
Parser::ViaConjure => parse_essence_file,
};
expression_domain_test_inner(path, filename, &case_name, extension, parse)?;
}

Ok(())
}

fn expression_domain_test_inner(
path: &str,
input_filename: &str,
case_name: &str,
extension: &str,
parse: ParseFn,
) -> Result<(), Box<dyn Error>> {
unimplemented!()
}

fn clean_test_dir_for_accept(path: &str) -> Result<(), std::io::Error> {
for entry in std::fs::read_dir(path)? {
let entry = entry?;
let file_name = entry.file_name();
let file_name = file_name.to_string_lossy();
let entry_path = entry.path();

if entry_path.is_dir() {
continue;
}

let keep = if file_name == "config.toml" {
true
} else {
let is_model_file = entry_path
.extension()
.and_then(|ext| ext.to_str())
.is_some_and(|ext| ext == "essence");
let is_generated_or_expected =
file_name.contains(".generated") || file_name.contains(".expected");
is_model_file && !is_generated_or_expected
};

if keep {
continue;
}

std::fs::remove_file(entry_path)?;
}

Ok(())
}

/// Returns the saved expression JSON path
fn expression_domains_json_path(path: &str, case_name: &str, file_type: &str) -> String {
format!("{path}/{case_name}.{file_type}.serialised.json")
}

/// Reads and initialises a saved expression domains snapshot.
fn read_expression_domains_json(
context: &Arc<RwLock<Context<'static>>>,
path: &str,
case_name: &str,
file_type: &str,
) -> Result<Vec<ExprInfo>, std::io::Error> {
let serialised = fs::read_to_string(expression_domains_json_path(path, case_name, file_type))?;
let exprs: Vec<ExprInfo> = serde_json::from_str(&serialised).map_err(std::io::Error::other)?;

Ok(exprs)
}

// include!(concat!(env!("OUT_DIR"), "/gen_tests_domain_tightening.rs"));
Loading