Skip to content
Closed
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
33 changes: 15 additions & 18 deletions core/codegen/src/testgen/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,8 @@ pub struct TestGenerator<'a, T> {
signature_fn: Option<String>,
/// CLI entrypoints for contract test generation: (tool_name, entrypoints).
cli_entrypoints: Option<(String, Vec<crate::cli_gen::CliEntrypoint>)>,
/// Optional type registry for contract-derived witness values.
type_registry: Option<TypeRegistry>,
/// Type registry for contract-derived witness values.
type_registry: TypeRegistry,
}

struct ProbeObserverBundle {
Expand All @@ -257,7 +257,7 @@ impl<'a, T: Clone> TestGenerator<'a, T> {
mock_spec_fn: None,
signature_fn: None,
cli_entrypoints: None,
type_registry: Some(TypeRegistry::with_core_types()),
type_registry: TypeRegistry::with_core_types(),
}
}

Expand Down Expand Up @@ -303,7 +303,7 @@ impl<'a, T: Clone> TestGenerator<'a, T> {

/// Set a type registry for contract-derived witness values.
pub fn with_type_registry(mut self, registry: TypeRegistry) -> Self {
self.type_registry = Some(registry);
self.type_registry = registry;
self
}

Expand Down Expand Up @@ -2000,7 +2000,7 @@ impl<'a, T: Clone> TestGenerator<'a, T> {
type_id,
*cardinality,
count,
self.type_registry.as_ref(),
&self.type_registry,
);
let mocks_expr = self.dryrun_mocks_expr(analysis, "cardinality coverage tests");

Expand Down Expand Up @@ -3468,9 +3468,9 @@ impl<'a, T: Clone> TestGenerator<'a, T> {
}

let value = if port.has_guard() {
select_guard_value(port, self.type_registry.as_ref())
select_guard_value(port, &self.type_registry)
} else {
required_value_for_port(port, self.type_registry.as_ref())
required_value_for_port(port, &self.type_registry)
};

match value {
Expand Down Expand Up @@ -5330,9 +5330,8 @@ fn witness_value_for_count(
type_id: &str,
cardinality: Cardinality,
count: u32,
registry: Option<&TypeRegistry>,
registry: &TypeRegistry,
) -> Option<Value> {
let registry = registry?;
let type_dag = registry.get_by_name(type_id)?;
let witnesses = contract::witnesses(type_dag);

Expand Down Expand Up @@ -5365,7 +5364,7 @@ fn try_mock_value_for_count(
type_id: &str,
cardinality: Cardinality,
count: u32,
registry: Option<&TypeRegistry>,
registry: &TypeRegistry,
) -> Option<Value> {
if let Some(value) = witness_value_for_count(type_id, cardinality, count, registry) {
return Some(value);
Expand Down Expand Up @@ -5403,7 +5402,7 @@ fn required_count_for_port(port: &gunbc_ir::Port) -> Option<u32> {

fn candidate_values_for_guard(
port: &gunbc_ir::Port,
registry: Option<&TypeRegistry>,
registry: &TypeRegistry,
) -> Vec<Value> {
let Some(count) = required_count_for_port(port) else {
return Vec::new();
Expand Down Expand Up @@ -5435,15 +5434,15 @@ fn candidate_values_for_guard(
values
}

fn select_guard_value(port: &gunbc_ir::Port, registry: Option<&TypeRegistry>) -> Option<Value> {
fn select_guard_value(port: &gunbc_ir::Port, registry: &TypeRegistry) -> Option<Value> {
candidate_values_for_guard(port, registry)
.into_iter()
.find(|candidate| port.check_guard(candidate))
}

fn required_value_for_port(
port: &gunbc_ir::Port,
registry: Option<&TypeRegistry>,
registry: &TypeRegistry,
) -> Option<Value> {
let count = required_count_for_port(port)?;
try_mock_value_for_count(port.type_id.0.as_str(), port.cardinality, count, registry)
Expand All @@ -5461,7 +5460,7 @@ fn mock_value_expr_for_count(
type_id: &str,
cardinality: Cardinality,
count: u32,
registry: Option<&TypeRegistry>,
registry: &TypeRegistry,
) -> ValueExpr {
if let Some(value) = witness_value_for_count(type_id, cardinality, count, registry) {
return ValueExpr::from(&value);
Expand Down Expand Up @@ -6146,15 +6145,13 @@ mod tests {
#[test]
fn test_mock_value_respects_cardinality() {
let registry = TypeRegistry::with_core_types();
let list_expr =
mock_value_expr_for_count("String", Cardinality::ZERO_OR_MORE, 1, Some(&registry));
let list_expr = mock_value_expr_for_count("String", Cardinality::ZERO_OR_MORE, 1, &registry);
assert_eq!(
list_expr,
ValueExpr::List(vec![ValueExpr::Str("example".to_string())])
);

let opt_zero =
mock_value_expr_for_count("String", Cardinality::ZERO_OR_ONE, 0, Some(&registry));
let opt_zero = mock_value_expr_for_count("String", Cardinality::ZERO_OR_ONE, 0, &registry);
assert_eq!(opt_zero, ValueExpr::Unit);
}

Expand Down
42 changes: 39 additions & 3 deletions core/daglang/daglang-emit/src/test_gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::fmt::Write;

use daglang_derive::TestObligations;
use daglang_lower::LoweredOp;
use gunbc_ir::Dag;
use gunbc_ir::{value_backing_for_type_id, Dag, ValueBacking};

use crate::computation::{classify_computation, Computation};
use crate::EmittedFile;
Expand Down Expand Up @@ -152,7 +152,24 @@ fn typed_mock_for_response(response_type: &str) -> &'static str {
"FileResponse" => r#"{"path":"/tmp/test.txt","success":true,"operation":"read"}"#,
"RestResponse" => r#"{"status":200,"body":{"ok":true}}"#,
"ShellResponse" => r#"{"exit_code":0,"stdout":"output"}"#,
_ => "mock-response",
"StringList" | "NonEmptyStringList" | "List<String>" => r#"["mock-item"]"#,
"IntList" | "List<Int>" => r#"[1]"#,
"BoolList" | "List<Bool>" => r#"[true]"#,
"JsonList" | "List<Json>" => r#"[{"mock":true}]"#,
_ => mock_for_backing_type(response_type),
}
}

fn mock_for_backing_type(type_id: &str) -> &'static str {
match value_backing_for_type_id(type_id) {
ValueBacking::String => "mock-response",
ValueBacking::Bool => "true",
ValueBacking::Int | ValueBacking::Float => "1",
ValueBacking::Json => r#"{"ok":true}"#,
ValueBacking::Map => r#"{"mock":"value"}"#,
ValueBacking::List | ValueBacking::Set => r#"["mock-item"]"#,
ValueBacking::Unit => "null",
ValueBacking::Bytes => "[0]",
}
}

Expand All @@ -178,7 +195,20 @@ pub fn witness_mock_responses(response_type: &str) -> Vec<String> {
r#"{"exit_code":0,"stdout":"ok"}"#.to_string(),
r#"{"exit_code":1,"stderr":"error"}"#.to_string(),
],
_ => vec!["mock-response".to_string()],
_ => {
let one = typed_mock_for_response(response_type).to_string();
let alternate = match value_backing_for_type_id(response_type) {
ValueBacking::String => "alt-mock-response".to_string(),
ValueBacking::Bool => "false".to_string(),
ValueBacking::Int | ValueBacking::Float => "2".to_string(),
ValueBacking::Json => r#"{"ok":false}"#.to_string(),
ValueBacking::Map => r#"{"mock":"alternate"}"#.to_string(),
ValueBacking::List | ValueBacking::Set => r#"["mock-item","alt-item"]"#.to_string(),
ValueBacking::Unit => "null".to_string(),
ValueBacking::Bytes => "[1]".to_string(),
};
vec![one, alternate]
}
}
}

Expand Down Expand Up @@ -631,6 +661,12 @@ mod tests {
assert!(emit_transport_mock_tests("c", &dag_without_transport_nodes()).is_none());
}

#[test]
fn typed_mock_for_response_emits_list_shape_for_string_list() {
assert_eq!(typed_mock_for_response("StringList"), r#"["mock-item"]"#);
assert_eq!(typed_mock_for_response("List<String>"), r#"["mock-item"]"#);
}

// ===== E3.4-E3.6: TestSpec-based generation tests =====

fn sample_spec() -> TestSpec {
Expand Down
31 changes: 20 additions & 11 deletions core/daglang/daglang-lower/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};

use daglang_syntax::ast::{Annotation, Expr, Item, Literal, OperationDef, ServiceDef, Stmt};
use daglang_syntax::ast_utils::{
canonical_resource_type_name as canonical_type_name, resource_type_name,
service_call_lookup_keys, should_track_call_name as should_track_call, type_expr_to_string,
walk_stmts,
resource_type_name, service_call_lookup_keys,
should_track_call_name as should_track_call, type_expr_to_string, walk_stmts,
};
use daglang_typecheck::{TypedCallableSignature, TypedItemSignature, TypedProject};
use gunbc_ir::patterns::branch::IfBuilder;
Expand Down Expand Up @@ -615,8 +614,18 @@ fn provider_hint_from_resource_properties(properties: &[(String, Expr)]) -> Opti
None
}

fn normalize_type_id_key(name: &str) -> String {
let trimmed = name.trim();
let base_without_config = trimmed.split('(').next().unwrap_or(trimmed).trim();
let base_without_annotations = base_without_config
.split_whitespace()
.next()
.unwrap_or(base_without_config);
gunbc_ir::TypeId::from(base_without_annotations).0
}

fn insert_canonical_names(set: &mut HashSet<String>, name: &str) {
let canonical = canonical_type_name(name);
let canonical = normalize_type_id_key(name);
let short = canonical
.rsplit('.')
.next()
Expand All @@ -627,7 +636,7 @@ fn insert_canonical_names(set: &mut HashSet<String>, name: &str) {
}

fn is_known_uses_type(set: &HashSet<String>, name: &str) -> bool {
let canonical = canonical_type_name(name);
let canonical = normalize_type_id_key(name);
set.contains(&canonical)
|| set.contains(canonical.rsplit('.').next().unwrap_or(canonical.as_str()))
}
Expand Down Expand Up @@ -4608,7 +4617,7 @@ fn resolve_interface_resource_endpoint(
project: &TypedProject,
registry: &ResourceLifecycleRegistry,
) -> ResourceEndpointResolution {
let target_canonical = canonical_type_name(resource_type);
let target_canonical = normalize_type_id_key(resource_type);
let target_short = target_canonical
.rsplit('.')
.next()
Expand All @@ -4624,7 +4633,7 @@ fn resolve_interface_resource_endpoint(
let Some(implemented) = &resource.implements else {
continue;
};
let implemented_canonical = canonical_type_name(implemented);
let implemented_canonical = normalize_type_id_key(implemented);
let implemented_short = implemented_canonical
.rsplit('.')
.next()
Expand Down Expand Up @@ -4817,7 +4826,7 @@ fn add_interface_contract_verification_nodes(
sanitize_identifier(&format!(
"{module_name}_{}_{}_{}",
resource.name,
canonical_type_name(interface_name),
normalize_type_id_key(interface_name),
index
))
);
Expand All @@ -4831,7 +4840,7 @@ fn add_interface_contract_verification_nodes(
name: format!(
"interface_contract::{}::{}::{}",
resource.name,
canonical_type_name(interface_name),
normalize_type_id_key(interface_name),
index
),
obligation: ObligationCategory::InterfaceContractVerification,
Expand All @@ -4857,7 +4866,7 @@ fn add_interface_contract_verification_nodes(
}

fn resolve_interface_contract_count(project: &TypedProject, interface_name: &str) -> usize {
let target = canonical_type_name(interface_name);
let target = normalize_type_id_key(interface_name);
let target_short = target.rsplit('.').next().unwrap_or(target.as_str());
let mut counts = Vec::new();
for module in &project.modules {
Expand All @@ -4867,7 +4876,7 @@ fn resolve_interface_contract_count(project: &TypedProject, interface_name: &str
continue;
};
let qualified = format!("{module_name}.{}", interface.name);
let qualified_canonical = canonical_type_name(&qualified);
let qualified_canonical = normalize_type_id_key(&qualified);
let interface_short = interface
.name
.rsplit('.')
Expand Down
6 changes: 1 addition & 5 deletions core/daglang/daglang-syntax/src/ast_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,13 @@ pub fn type_expr_to_string(expr: &TypeExpr) -> String {
}
}

pub fn canonical_type_name(name: &str) -> String {
name.split('<').next().unwrap_or(name).trim().to_string()
}

pub fn canonical_resource_type_name(name: &str) -> String {
let base_without_config = name.split('(').next().unwrap_or(name).trim();
let base_without_annotations = base_without_config
.split_whitespace()
.next()
.unwrap_or(base_without_config);
canonical_type_name(base_without_annotations)
base_without_annotations.to_string()
}

pub fn resource_type_name(resource_type: &TypeExpr) -> String {
Expand Down
Loading
Loading