Skip to content

Commit f8f82ce

Browse files
authored
test(routines): exercise pinned G36 TrimAndRespond pipeline (#100)
Exercise the public typed compiler pipeline against the real pinned TrimAndRespond source in CI, with source provenance and full inventory validation retained in the preceding gate.
1 parent 45995ff commit f8f82ce

2 files changed

Lines changed: 267 additions & 0 deletions

File tree

.github/workflows/verify.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,13 @@ jobs:
8787
working-directory: cxf-library
8888
run: cargo test --locked --manifest-path tools/routine-compiler/Cargo.toml
8989

90+
- name: Test G36 TrimAndRespond compiler integration
91+
working-directory: cxf-library
92+
env:
93+
OCL_G36_RELEASE_ROOT: ../../../modelica-buildings-release
94+
OCL_G36_DEVELOPMENT_ROOT: ../../../modelica-buildings-development
95+
run: cargo test --locked --manifest-path tools/routine-compiler/Cargo.toml --test g36_trim_and_respond -- --include-ignored
96+
9097
- name: Verify G36 release declarations
9198
working-directory: cxf-library
9299
run: cargo run --manifest-path tools/verify/Cargo.toml -- --g36-declarations ../modelica-buildings-release
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
use std::env;
2+
use std::path::PathBuf;
3+
4+
use cap_std::{ambient_authority, fs::Dir};
5+
use num_bigint::BigInt;
6+
use ocl_routine_compiler::bound_scalars::{BoundScalarProjection, BoundSourceClaim};
7+
use ocl_routine_compiler::compiler_pipeline::compile_validated_from_roots;
8+
use ocl_routine_compiler::declaration_pipeline::DeclarationRootPipelineLimits;
9+
use ocl_routine_compiler::declaration_source::DeclarationSourceRoots;
10+
use ocl_routine_compiler::resolution::{
11+
ConnectorDefinition, ConnectorDirection, ConnectorPresence, FiniteReal, ParameterDefinition,
12+
ParameterSource, ParameterValue, PrimitiveType, ResolutionLimits, ScalarValue, Shape, TypeUse,
13+
ValidatedResolutionInput,
14+
};
15+
use ocl_routine_compiler::scalar_abi::{EnumAbiMapping, ScalarAbiType, ScalarAbiValue};
16+
use ocl_routine_compiler::scalar_source_claims::{
17+
SourceClassClaim, SourceFileLocator, SourceInventory, SourceInventoryFile,
18+
SourceInventoryLicense, SourceInventorySnapshot, SourceMemberBinding, SourceOwnerKind,
19+
SourcePin, SourceSnapshotRole,
20+
};
21+
22+
const RELEASE_ROOT_ENV: &str = "OCL_G36_RELEASE_ROOT";
23+
const DEVELOPMENT_ROOT_ENV: &str = "OCL_G36_DEVELOPMENT_ROOT";
24+
const RELEASE_REVISION: &str = "55abf579598ca81cae0a82f337350375958e6722";
25+
const DEVELOPMENT_REVISION: &str = "eccb40b3974bb10eef120c5670a6454e43ca36e3";
26+
const RELEASE_ROOT_TREE: &str = "sha1:8bc765b009ed0dcf6907021f073ea77160088bd8";
27+
const DEVELOPMENT_ROOT_TREE: &str = "sha1:6286a3d534846a187444d3b4bee72a1ae944a458";
28+
const TRIM_AND_RESPOND_CLASS: &str = "Buildings.Controls.OBC.ASHRAE.G36.Generic.TrimAndRespond";
29+
const TRIM_AND_RESPOND_PATH: &str = "Buildings/Controls/OBC/ASHRAE/G36/Generic/TrimAndRespond.mo";
30+
const TRIM_AND_RESPOND_BYTES: usize = 25_452;
31+
const TRIM_AND_RESPOND_BLOB: &str = "sha1:028439a4fb478fc041d703a092d5186f5861eb03";
32+
const TRIM_AND_RESPOND_SHA256: &str =
33+
"sha256:1bf9ab68904baa00553d6b43ecd0d04411e6106d196881ad67c9885827507981";
34+
const TEST_CANONICAL_ID: &str = "TEST-ONLY-G36-TRIM-AND-RESPOND";
35+
36+
#[derive(Clone, Debug, PartialEq)]
37+
struct IntegrationInputs {
38+
resolution: ValidatedResolutionInput,
39+
enum_mappings: Vec<EnumAbiMapping>,
40+
inventory: SourceInventory,
41+
pins: Vec<SourcePin>,
42+
claims: Vec<SourceClassClaim>,
43+
bindings: Vec<SourceMemberBinding>,
44+
}
45+
46+
fn finite(value: f64) -> FiniteReal {
47+
FiniteReal::new(value).expect("test value is finite")
48+
}
49+
50+
fn source_file() -> SourceInventoryFile {
51+
SourceInventoryFile {
52+
path: TRIM_AND_RESPOND_PATH.to_owned(),
53+
mode: "100644".to_owned(),
54+
bytes: BigInt::from(TRIM_AND_RESPOND_BYTES),
55+
git_blob_sha1: TRIM_AND_RESPOND_BLOB.to_owned(),
56+
sha256: TRIM_AND_RESPOND_SHA256.to_owned(),
57+
}
58+
}
59+
60+
fn snapshot(
61+
role: SourceSnapshotRole,
62+
revision: &str,
63+
root_tree_sha1: &str,
64+
) -> SourceInventorySnapshot {
65+
SourceInventorySnapshot {
66+
role,
67+
revision: revision.to_owned(),
68+
root_tree_sha1: root_tree_sha1.to_owned(),
69+
file_count: BigInt::from(1_u8),
70+
total_bytes: BigInt::from(TRIM_AND_RESPOND_BYTES),
71+
modelica_file_count: BigInt::from(1_u8),
72+
package_order_count: BigInt::from(0_u8),
73+
files: vec![source_file()],
74+
}
75+
}
76+
77+
fn integration_inputs() -> IntegrationInputs {
78+
IntegrationInputs {
79+
resolution: ValidatedResolutionInput {
80+
canonical_id: TEST_CANONICAL_ID.to_owned(),
81+
revision: BigInt::from(1_u8),
82+
types: Vec::new(),
83+
dimensions: Vec::new(),
84+
parameters: vec![ParameterDefinition {
85+
parameter_id: "sample_period_s".to_owned(),
86+
type_use: TypeUse::Primitive(PrimitiveType::Real),
87+
shape: Shape::Scalar,
88+
source: ParameterSource::Default,
89+
value: ParameterValue::Scalar(ScalarValue::Real(finite(60.0))),
90+
}],
91+
connectors: vec![ConnectorDefinition {
92+
connector_id: "num_of_req".to_owned(),
93+
direction: ConnectorDirection::Input,
94+
type_use: TypeUse::Primitive(PrimitiveType::Integer),
95+
shape: Shape::Scalar,
96+
presence: ConnectorPresence::Always,
97+
}],
98+
},
99+
enum_mappings: Vec::new(),
100+
// This one-file subset drives the integration. The preceding Python CI gate
101+
// validates checkout provenance and every row in the checked-in inventory.
102+
inventory: SourceInventory {
103+
schema: "cxf-library/g36-source-inventory/v1".to_owned(),
104+
repository: "https://github.com/lbl-srg/modelica-buildings.git".to_owned(),
105+
source_root: "Buildings/Controls/OBC/ASHRAE/G36".to_owned(),
106+
inventory_scope: "source-root-regular-files".to_owned(),
107+
dependency_closure: "not-inventoried".to_owned(),
108+
license: SourceInventoryLicense {
109+
upstream_path: "Buildings/legal.html".to_owned(),
110+
retained_path: "routines/g36/LICENSE-BUILDINGS.html".to_owned(),
111+
git_blob_sha1: "sha1:b542af56c10d3769d42a79ad45d78329ad2dbd5f".to_owned(),
112+
sha256: "sha256:79ad0f2d053b92d93a7e7b200b9d7ef4c1bb2097aad695cda31ad9a23a57e921"
113+
.to_owned(),
114+
},
115+
snapshots: vec![
116+
snapshot(
117+
SourceSnapshotRole::Release,
118+
RELEASE_REVISION,
119+
RELEASE_ROOT_TREE,
120+
),
121+
snapshot(
122+
SourceSnapshotRole::Development,
123+
DEVELOPMENT_REVISION,
124+
DEVELOPMENT_ROOT_TREE,
125+
),
126+
],
127+
},
128+
pins: vec![
129+
SourcePin {
130+
role: SourceSnapshotRole::Release,
131+
revision: RELEASE_REVISION.to_owned(),
132+
},
133+
SourcePin {
134+
role: SourceSnapshotRole::Development,
135+
revision: DEVELOPMENT_REVISION.to_owned(),
136+
},
137+
],
138+
claims: vec![SourceClassClaim {
139+
canonical_class_path: TRIM_AND_RESPOND_CLASS.to_owned(),
140+
snapshot: SourceSnapshotRole::Release,
141+
revision: RELEASE_REVISION.to_owned(),
142+
file: SourceFileLocator {
143+
path: TRIM_AND_RESPOND_PATH.to_owned(),
144+
git_blob_sha1: TRIM_AND_RESPOND_BLOB.to_owned(),
145+
},
146+
}],
147+
bindings: vec![
148+
SourceMemberBinding {
149+
owner_kind: SourceOwnerKind::Parameter,
150+
owner_id: "sample_period_s".to_owned(),
151+
canonical_class_path: TRIM_AND_RESPOND_CLASS.to_owned(),
152+
source_member: "samplePeriod".to_owned(),
153+
},
154+
SourceMemberBinding {
155+
owner_kind: SourceOwnerKind::Connector,
156+
owner_id: "num_of_req".to_owned(),
157+
canonical_class_path: TRIM_AND_RESPOND_CLASS.to_owned(),
158+
source_member: "numOfReq".to_owned(),
159+
},
160+
],
161+
}
162+
}
163+
164+
fn open_root(variable: &str) -> Dir {
165+
let value = env::var_os(variable)
166+
.unwrap_or_else(|| panic!("{variable} must be set for this integration test"));
167+
assert!(
168+
!value.is_empty(),
169+
"{variable} must not be empty for this integration test"
170+
);
171+
Dir::open_ambient_dir(PathBuf::from(value), ambient_authority())
172+
.unwrap_or_else(|error| panic!("{variable} must name an openable directory: {error}"))
173+
}
174+
175+
fn compile(inputs: &IntegrationInputs, roots: DeclarationSourceRoots<'_>) -> BoundScalarProjection {
176+
compile_validated_from_roots(
177+
&inputs.resolution,
178+
ResolutionLimits {
179+
max_guard_depth: 0,
180+
max_guard_nodes: 0,
181+
max_scalar_leaves: 2,
182+
},
183+
&inputs.enum_mappings,
184+
&inputs.inventory,
185+
&inputs.pins,
186+
&inputs.claims,
187+
&inputs.bindings,
188+
roots,
189+
DeclarationRootPipelineLimits {
190+
max_documents: 1,
191+
max_requirements: 2,
192+
max_source_bytes: TRIM_AND_RESPOND_BYTES,
193+
max_total_source_bytes: TRIM_AND_RESPOND_BYTES,
194+
max_direct_members: 64,
195+
},
196+
)
197+
.expect("real pinned TrimAndRespond compiles through the typed pipeline")
198+
}
199+
200+
fn assert_release_claim(claim: &BoundSourceClaim, source_member: &str) {
201+
assert_eq!(claim.canonical_class_path, TRIM_AND_RESPOND_CLASS);
202+
assert_eq!(claim.source_member, source_member);
203+
assert_eq!(claim.snapshot, SourceSnapshotRole::Release);
204+
assert_eq!(claim.revision, RELEASE_REVISION);
205+
assert_eq!(claim.file.path, TRIM_AND_RESPOND_PATH);
206+
assert_eq!(claim.file.git_blob_sha1, TRIM_AND_RESPOND_BLOB);
207+
}
208+
209+
#[test]
210+
#[ignore = "requires caller-supplied pinned Modelica Buildings roots"]
211+
fn real_pinned_trim_and_respond_compiles_through_public_pipeline() {
212+
let release = open_root(RELEASE_ROOT_ENV);
213+
let development = open_root(DEVELOPMENT_ROOT_ENV);
214+
let roots = DeclarationSourceRoots::new(&release, &development);
215+
let inputs = integration_inputs();
216+
let before = inputs.clone();
217+
218+
// Generic syntax checks direct identity and public members, not declared types,
219+
// connector direction, inheritance, dependencies, compilation, or behavior.
220+
// The legacy verifier separately checks exact declared member types.
221+
let first = compile(&inputs, roots);
222+
let second = compile(&inputs, roots);
223+
224+
assert_eq!(first, second);
225+
assert_eq!(inputs, before);
226+
assert_eq!(first.canonical_id, TEST_CANONICAL_ID);
227+
assert_eq!(first.revision, BigInt::from(1_u8));
228+
assert_eq!(first.parameters.len(), 1);
229+
assert_eq!(first.connectors.len(), 1);
230+
231+
let parameter = &first.parameters[0];
232+
assert_eq!(parameter.parameter_id, "sample_period_s");
233+
assert!(parameter.coordinates.is_empty());
234+
assert_eq!(parameter.scalar_name, "p_73616d706c655f706572696f645f73");
235+
assert_eq!(
236+
parameter.abi_type,
237+
ScalarAbiType::Primitive(PrimitiveType::Real)
238+
);
239+
assert_eq!(parameter.source, ParameterSource::Default);
240+
assert_eq!(parameter.value, ScalarAbiValue::Real(finite(60.0)));
241+
assert_release_claim(&parameter.source_claim, "samplePeriod");
242+
243+
let connector = &first.connectors[0];
244+
assert_eq!(connector.connector_id, "num_of_req");
245+
assert!(connector.coordinates.is_empty());
246+
assert_eq!(connector.scalar_name, "c_6e756d5f6f665f726571");
247+
assert_eq!(
248+
connector.abi_type,
249+
ScalarAbiType::Primitive(PrimitiveType::Integer)
250+
);
251+
assert_eq!(connector.direction, ConnectorDirection::Input);
252+
assert_release_claim(&connector.source_claim, "numOfReq");
253+
254+
assert_eq!(inputs.claims.len(), 1);
255+
assert_eq!(inputs.claims[0].snapshot, SourceSnapshotRole::Release);
256+
assert_eq!(
257+
inputs.inventory.snapshots[1].role,
258+
SourceSnapshotRole::Development
259+
);
260+
}

0 commit comments

Comments
 (0)