#[cfg(not(target_family = "wasm"))]
use rudof_generate::config::OutputFormat;
#[cfg(not(target_family = "wasm"))]
use rudof_generate::{DataGenerator, GeneratorConfig};
#[cfg(not(target_family = "wasm"))]
use std::collections::HashMap;
#[cfg(not(target_family = "wasm"))]
use std::io::Write;
#[cfg(not(target_family = "wasm"))]
use tempfile::NamedTempFile;
#[cfg(not(target_family = "wasm"))]
#[tokio::test]
async fn test_property_fill_probability() {
let shacl_schema = r#"
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://example.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:PersonShape a sh:NodeShape ;
sh:targetClass ex:Person ;
sh:property [ sh:path ex:p1 ; sh:datatype xsd:integer ; sh:minCount 0 ; ] ;
sh:property [ sh:path ex:p2 ; sh:datatype xsd:integer ; sh:minCount 0 ; ] ;
sh:property [ sh:path ex:p3 ; sh:datatype xsd:integer ; sh:minCount 0 ; ] ;
sh:property [ sh:path ex:p4 ; sh:datatype xsd:integer ; sh:minCount 0 ; ] ;
sh:property [ sh:path ex:p5 ; sh:datatype xsd:integer ; sh:minCount 0 ; ] ;
sh:property [ sh:path ex:p6 ; sh:datatype xsd:integer ; sh:minCount 0 ; ] ;
sh:property [ sh:path ex:p7 ; sh:datatype xsd:integer ; sh:minCount 0 ; ] ;
sh:property [ sh:path ex:p8 ; sh:datatype xsd:integer ; sh:minCount 0 ; ] ;
sh:property [ sh:path ex:p9 ; sh:datatype xsd:integer ; sh:minCount 0 ; ] ;
sh:property [ sh:path ex:p10 ; sh:datatype xsd:integer ; sh:minCount 0 ; ] .
"#;
let mut schema_file = NamedTempFile::new().unwrap();
writeln!(schema_file, "{}", shacl_schema).unwrap();
let output_file = NamedTempFile::new().unwrap();
let mut config = GeneratorConfig::default();
config.generation.entity_count = 100;
config.generation.property_fill_probability = 0.5;
config.generation.cardinality_strategy = rudof_generate::config::CardinalityStrategy::Maximum;
config.output.path = output_file.path().to_path_buf();
config.output.format = OutputFormat::NTriples;
let mut generator = DataGenerator::new(config).unwrap();
generator.load_shacl_schema(schema_file.path()).await.unwrap();
generator.generate().await.unwrap();
let output_content = std::fs::read_to_string(output_file.path()).unwrap();
let total_triples = output_content.lines().count();
let type_triples = output_content
.lines()
.filter(|l| l.contains("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"))
.count();
let property_triples = total_triples - type_triples;
println!("Total property triples generated: {}", property_triples);
assert!(
property_triples >= 400,
"Too few properties generated: {}",
property_triples
);
assert!(
property_triples <= 600,
"Too many properties generated: {}",
property_triples
);
}
#[cfg(not(target_family = "wasm"))]
#[tokio::test]
async fn test_ignore_min_cardinality() {
let shacl_schema = r#"
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://example.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:RequiredShape a sh:NodeShape ;
sh:targetClass ex:Required ;
sh:property [ sh:path ex:req1 ; sh:datatype xsd:integer ; sh:minCount 1 ; ] ;
sh:property [ sh:path ex:req2 ; sh:datatype xsd:integer ; sh:minCount 1 ; ] .
"#;
let mut schema_file = NamedTempFile::new().unwrap();
writeln!(schema_file, "{}", shacl_schema).unwrap();
let output_file = NamedTempFile::new().unwrap();
let mut config = GeneratorConfig::default();
config.generation.entity_count = 100;
config.generation.property_fill_probability = 0.5;
config.generation.ignore_min_cardinality = true;
config.generation.cardinality_strategy = rudof_generate::config::CardinalityStrategy::Maximum;
config.output.path = output_file.path().to_path_buf();
config.output.format = OutputFormat::NTriples;
let mut generator = DataGenerator::new(config).unwrap();
generator.load_shacl_schema(schema_file.path()).await.unwrap();
generator.generate().await.unwrap();
let output_content = std::fs::read_to_string(output_file.path()).unwrap();
let total_triples = output_content.lines().count();
let type_triples = output_content
.lines()
.filter(|l| l.contains("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"))
.count();
let property_triples = total_triples - type_triples;
println!("Total property triples (ignore_min=true): {}", property_triples);
assert!(
property_triples < 180,
"Should have skipped many required properties. Got: {}",
property_triples
);
}
#[cfg(not(target_family = "wasm"))]
#[tokio::test]
async fn test_max_properties_per_instance() {
let mut shacl_schema = String::from(
r#"
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://example.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:ManyPropsShape a sh:NodeShape ;
sh:targetClass ex:ManyProps ;
"#,
);
for i in 1..=20 {
shacl_schema.push_str(&format!(
" sh:property [ sh:path ex:p{} ; sh:datatype xsd:integer ; sh:minCount 0 ; ] ;\n",
i
));
}
shacl_schema.push_str(" .\n");
let mut schema_file = NamedTempFile::new().unwrap();
writeln!(schema_file, "{}", shacl_schema).unwrap();
let output_file = NamedTempFile::new().unwrap();
let mut config = GeneratorConfig::default();
config.generation.entity_count = 10;
config.generation.max_properties_per_instance = 5;
config.generation.property_fill_probability = 1.0;
config.generation.cardinality_strategy = rudof_generate::config::CardinalityStrategy::Maximum;
config.output.path = output_file.path().to_path_buf();
config.output.format = OutputFormat::NTriples;
let mut generator = DataGenerator::new(config).unwrap();
generator.load_shacl_schema(schema_file.path()).await.unwrap();
generator.generate().await.unwrap();
let output_content = std::fs::read_to_string(output_file.path()).unwrap();
let total_triples = output_content.lines().count();
let type_triples = output_content
.lines()
.filter(|l| l.contains("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"))
.count();
let property_triples = total_triples - type_triples;
println!("Total property triples (max=5): {}", property_triples);
assert_eq!(
property_triples, 50,
"Should have exactly 50 properties (5 per instance)"
);
}
#[cfg(not(target_family = "wasm"))]
#[tokio::test]
async fn test_property_selection_strategy_random() {
let mut shacl_schema = String::from(
r#"
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://example.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:RandomPropsShape a sh:NodeShape ;
sh:targetClass ex:RandomProps ;
"#,
);
for i in 1..=10 {
shacl_schema.push_str(&format!(
" sh:property [ sh:path ex:p{} ; sh:datatype xsd:integer ; sh:minCount 0 ; ] ;\n",
i
));
}
shacl_schema.push_str(" .\n");
let mut schema_file = NamedTempFile::new().unwrap();
writeln!(schema_file, "{}", shacl_schema).unwrap();
let output_file = NamedTempFile::new().unwrap();
let mut config = GeneratorConfig::default();
config.generation.entity_count = 20;
config.generation.property_fill_probability = 0.5;
config.generation.property_selection_strategy = rudof_generate::config::PropertySelectionStrategy::Random;
config.generation.cardinality_strategy = rudof_generate::config::CardinalityStrategy::Maximum;
config.output.path = output_file.path().to_path_buf();
config.output.format = OutputFormat::NTriples;
let mut generator = DataGenerator::new(config).unwrap();
generator.load_shacl_schema(schema_file.path()).await.unwrap();
generator.generate().await.unwrap();
let output_content = std::fs::read_to_string(output_file.path()).unwrap();
let total_triples = output_content.lines().count();
let type_triples = output_content
.lines()
.filter(|l| l.contains("http://www.w3.org/1999/02/22-rdf-syntax-ns#type"))
.count();
let property_triples = total_triples - type_triples;
println!("Total property triples (Random Strategy): {}", property_triples);
assert_eq!(
property_triples, 100,
"Should have exactly 100 properties (5 per instance)"
);
for i in 1..=10 {
let prop = format!("http://example.org/p{}", i);
assert!(
output_content.contains(&prop),
"Property {} should appear at least once in 20 entities",
prop
);
}
}
#[cfg(not(target_family = "wasm"))]
#[tokio::test]
async fn test_property_count_variance() {
let mut shacl_schema = String::from(
r#"
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://example.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:VariancePropsShape a sh:NodeShape ;
sh:targetClass ex:VarianceProps ;
"#,
);
for i in 1..=20 {
shacl_schema.push_str(&format!(
" sh:property [ sh:path ex:p{} ; sh:datatype xsd:integer ; sh:minCount 0 ; ] ;\n",
i
));
}
shacl_schema.push_str(" .\n");
let mut schema_file = NamedTempFile::new().unwrap();
writeln!(schema_file, "{}", shacl_schema).unwrap();
let output_file = NamedTempFile::new().unwrap();
let mut config = GeneratorConfig::default();
config.generation.entity_count = 50;
config.generation.property_fill_probability = 0.5;
config.generation.property_count_variance = 0.8;
config.generation.property_selection_strategy = rudof_generate::config::PropertySelectionStrategy::Random;
config.generation.cardinality_strategy = rudof_generate::config::CardinalityStrategy::Maximum;
config.output.path = output_file.path().to_path_buf();
config.output.format = OutputFormat::NTriples;
let mut generator = DataGenerator::new(config).unwrap();
generator.load_shacl_schema(schema_file.path()).await.unwrap();
generator.generate().await.unwrap();
let output_content = std::fs::read_to_string(output_file.path()).unwrap();
let mut counts_per_subject: HashMap<String, usize> = HashMap::new();
for line in output_content.lines() {
if line.is_empty() || line.starts_with("#") {
continue;
}
if line.contains("http://www.w3.org/1999/02/22-rdf-syntax-ns#type") {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 3 {
let subject = parts[0].to_string();
*counts_per_subject.entry(subject).or_insert(0) += 1;
}
}
let counts: Vec<usize> = counts_per_subject.values().cloned().collect();
println!("Property counts per instance: {:?}", counts);
assert!(!counts.is_empty(), "Should have generated entities");
let min_count = *counts.iter().min().unwrap();
let max_count = *counts.iter().max().unwrap();
println!("Min count: {}, Max count: {}", min_count, max_count);
assert!(
max_count - min_count >= 3,
"Should have variance in property counts. Range got: {}-{}",
min_count,
max_count
);
}
#[cfg(not(target_family = "wasm"))]
#[tokio::test]
async fn test_excluded_properties() {
let shacl_schema = r#"
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://example.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:ExcludeShape a sh:NodeShape ;
sh:targetClass ex:Exclude ;
sh:property [ sh:path ex:keep ; sh:datatype xsd:integer ; sh:minCount 1 ; ] ;
sh:property [ sh:path ex:skip ; sh:datatype xsd:integer ; sh:minCount 1 ; ] .
"#;
let mut schema_file = NamedTempFile::new().unwrap();
writeln!(schema_file, "{}", shacl_schema).unwrap();
let output_file = NamedTempFile::new().unwrap();
let mut config = GeneratorConfig::default();
config.generation.entity_count = 10;
config.generation.excluded_properties = vec!["http://example.org/skip".to_string()];
config.output.path = output_file.path().to_path_buf();
config.output.format = OutputFormat::NTriples;
let mut generator = DataGenerator::new(config).unwrap();
generator.load_shacl_schema(schema_file.path()).await.unwrap();
generator.generate().await.unwrap();
let output_content = std::fs::read_to_string(output_file.path()).unwrap();
assert!(
output_content.contains("http://example.org/keep"),
"Should contain non-excluded property"
);
assert!(
!output_content.contains("http://example.org/skip"),
"Should NOT contain excluded property"
);
}
#[cfg(not(target_family = "wasm"))]
#[tokio::test]
async fn test_type_overrides() {
let shacl_schema = r#"
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <http://example.org/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
ex:ShapeHigh a sh:NodeShape ;
sh:targetClass ex:High ;
sh:property [ sh:path ex:p1 ; sh:datatype xsd:integer ; sh:minCount 0 ; ] .
ex:ShapeLow a sh:NodeShape ;
sh:targetClass ex:Low ;
sh:property [ sh:path ex:p1 ; sh:datatype xsd:integer ; sh:minCount 0 ; ] .
"#;
let mut schema_file = NamedTempFile::new().unwrap();
writeln!(schema_file, "{}", shacl_schema).unwrap();
let output_file = NamedTempFile::new().unwrap();
use rudof_generate::config::TypeOverrideConfig;
let mut config = GeneratorConfig::default();
config.generation.entity_count = 20; config.generation.property_fill_probability = 1.0;
config.generation.entity_distribution = rudof_generate::config::EntityDistribution::Equal;
let mut overrides = HashMap::new();
overrides.insert(
"http://example.org/ShapeLow".to_string(),
TypeOverrideConfig {
property_fill_probability: Some(0.0), ignore_min_cardinality: None,
max_properties_per_instance: None,
property_selection_strategy: None,
property_count_variance: None,
excluded_properties: None,
},
);
config.generation.type_overrides = overrides;
config.output.path = output_file.path().to_path_buf();
config.output.format = OutputFormat::NTriples;
let mut generator = DataGenerator::new(config).unwrap();
generator.load_shacl_schema(schema_file.path()).await.unwrap();
generator.generate().await.unwrap();
let output_content = std::fs::read_to_string(output_file.path()).unwrap();
let mut high_has_prop = false;
let mut low_has_prop = false;
for line in output_content.lines() {
if line.contains("http://example.org/p1") {
if line.contains("http://example.org/ShapeHigh") {
high_has_prop = true;
} else if line.contains("http://example.org/ShapeLow") {
low_has_prop = true;
}
}
}
assert!(high_has_prop, "ShapeHigh should have properties (prob=1.0)");
assert!(!low_has_prop, "ShapeLow should NOT have properties (prob=0.0 override)");
}