1use crate::error::WorkshopError;
9use crate::semantic::{IncompletenessKind, ResidualClassification, SemanticIssue};
10
11pub const REAL_PROJECT_EXPECTATION_SCHEMA_VERSION: u32 = 1;
13
14pub const REAL_PROJECT_CORPUS_ID: &str = "raw-workshop-real-projects/v1";
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum RealProjectStage {
20 CanonicalValidation,
22 Emission,
24 LocaleConversion,
26}
27
28impl RealProjectStage {
29 pub const fn as_str(self) -> &'static str {
31 match self {
32 Self::CanonicalValidation => "canonical-validation",
33 Self::Emission => "emission",
34 Self::LocaleConversion => "locale-conversion",
35 }
36 }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
41pub enum RealProjectGapKind {
42 UnknownAction,
44}
45
46impl RealProjectGapKind {
47 pub const fn as_str(self) -> &'static str {
49 match self {
50 Self::UnknownAction => "action",
51 }
52 }
53}
54
55#[derive(Debug, Clone, Copy, PartialEq, Eq)]
57pub struct RealProjectResidualExpectation {
58 pub kind: IncompletenessKind,
60 pub identity: &'static str,
62 pub classification: ResidualClassification,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub struct RealProjectGapExpectation {
69 pub stage: RealProjectStage,
71 pub kind: RealProjectGapKind,
73 pub identity: &'static str,
75 pub classification: ResidualClassification,
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub struct RealProjectCaseExpectation {
82 pub id: &'static str,
84 pub locale: &'static str,
86 pub source_fixture: &'static str,
88 pub source_sha256: &'static str,
90 pub residuals: &'static [RealProjectResidualExpectation],
92 pub gaps: &'static [RealProjectGapExpectation],
94}
95
96impl RealProjectCaseExpectation {
97 pub fn admits_residual(&self, issue: &SemanticIssue) -> bool {
99 self.residuals.iter().any(|expected| {
100 expected.kind == issue.kind
101 && expected.identity == issue.name
102 && expected.classification == issue.classification
103 })
104 }
105
106 pub fn admits_gap(&self, stage: RealProjectStage, error: &WorkshopError) -> bool {
108 self.gaps.iter().any(|expected| {
109 expected.stage == stage
110 && match (expected.kind, error) {
111 (
112 RealProjectGapKind::UnknownAction,
113 WorkshopError::Unknown { kind, spelling, .. },
114 ) => *kind == expected.kind.as_str() && spelling == expected.identity,
115 _ => false,
116 }
117 })
118 }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub struct RealProjectExpectation {
125 pub schema_version: u32,
127 pub corpus_id: &'static str,
129 pub cases: &'static [RealProjectCaseExpectation],
131}
132
133const NO_RESIDUALS: &[RealProjectResidualExpectation] = &[];
134const NO_GAPS: &[RealProjectGapExpectation] = &[];
135const DEFEND_RESIDUALS: &[RealProjectResidualExpectation] = &[RealProjectResidualExpectation {
136 kind: IncompletenessKind::OpaqueAction,
137 identity: "rawWorkshopAction",
138 classification: ResidualClassification::LegacyOpaque,
139}];
140const DEFEND_GAPS: &[RealProjectGapExpectation] = &[
141 RealProjectGapExpectation {
142 stage: RealProjectStage::CanonicalValidation,
143 kind: RealProjectGapKind::UnknownAction,
144 identity: "rawWorkshopAction",
145 classification: ResidualClassification::LegacyOpaque,
146 },
147 RealProjectGapExpectation {
148 stage: RealProjectStage::Emission,
149 kind: RealProjectGapKind::UnknownAction,
150 identity: "rawWorkshopAction",
151 classification: ResidualClassification::LegacyOpaque,
152 },
153 RealProjectGapExpectation {
154 stage: RealProjectStage::LocaleConversion,
155 kind: RealProjectGapKind::UnknownAction,
156 identity: "rawWorkshopAction",
157 classification: ResidualClassification::LegacyOpaque,
158 },
159];
160
161pub const REAL_PROJECT_EXPECTATION: RealProjectExpectation = RealProjectExpectation {
163 schema_version: REAL_PROJECT_EXPECTATION_SCHEMA_VERSION,
164 corpus_id: REAL_PROJECT_CORPUS_ID,
165 cases: &[
166 RealProjectCaseExpectation {
167 id: "ai-pve",
168 locale: "zh-CN",
169 source_fixture: "tests/fixtures/real-projects/ai-pve.ow",
170 source_sha256: "d9c6460ca550e40083efcc2b57de16360088631970824599a22c0aa2cb7f11f9",
171 residuals: NO_RESIDUALS,
172 gaps: NO_GAPS,
173 },
174 RealProjectCaseExpectation {
175 id: "bastion",
176 locale: "en-US",
177 source_fixture: "tests/fixtures/real-projects/bastion.ow",
178 source_sha256: "44e453ddf7f373be65aea82d019abd45dd60f5ecb57c8d1607d3576a8bc60259",
179 residuals: NO_RESIDUALS,
180 gaps: NO_GAPS,
181 },
182 RealProjectCaseExpectation {
183 id: "defend",
184 locale: "en-US",
185 source_fixture: "tests/fixtures/real-projects/defend.ow",
186 source_sha256: "06a956b650313ee2d6e24ec989f907244dc4444579bdba27c580b031de97b268",
187 residuals: DEFEND_RESIDUALS,
188 gaps: DEFEND_GAPS,
189 },
190 RealProjectCaseExpectation {
191 id: "illari",
192 locale: "zh-CN",
193 source_fixture: "tests/fixtures/real-projects/illari.ow",
194 source_sha256: "f3aff73b9e677730bddc9c85b04c2bd38439bb7a4ba4fa2e80dc28db2e4a0860",
195 residuals: NO_RESIDUALS,
196 gaps: NO_GAPS,
197 },
198 RealProjectCaseExpectation {
199 id: "rework",
200 locale: "en-US",
201 source_fixture: "tests/fixtures/real-projects/rework.ow",
202 source_sha256: "aa32cda640dba41fd99245a7d425d9897b53875d15cf071862197a8e6840258c",
203 residuals: NO_RESIDUALS,
204 gaps: NO_GAPS,
205 },
206 ],
207};
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn real_projects_expectation_has_unique_pinned_case_identities() {
215 assert_eq!(
216 REAL_PROJECT_EXPECTATION.schema_version,
217 REAL_PROJECT_EXPECTATION_SCHEMA_VERSION
218 );
219 assert_eq!(REAL_PROJECT_EXPECTATION.corpus_id, REAL_PROJECT_CORPUS_ID);
220
221 for (index, case) in REAL_PROJECT_EXPECTATION.cases.iter().enumerate() {
222 assert!(!case.id.is_empty());
223 assert!(case.locale == "en-US" || case.locale == "zh-CN");
224 assert!(
225 case.source_fixture
226 .starts_with("tests/fixtures/real-projects/")
227 );
228 assert_eq!(case.source_sha256.len(), 64);
229 assert!(
230 REAL_PROJECT_EXPECTATION.cases[index + 1..]
231 .iter()
232 .all(|other| other.id != case.id),
233 "duplicate real-project case identity: {}",
234 case.id
235 );
236 }
237 }
238
239 #[test]
240 fn real_projects_expectation_keeps_the_admitted_gap_identity_and_classification() {
241 let defend = REAL_PROJECT_EXPECTATION
242 .cases
243 .iter()
244 .find(|case| case.id == "defend")
245 .expect("defend case");
246 assert_eq!(defend.residuals, DEFEND_RESIDUALS);
247 assert_eq!(defend.gaps.len(), 3);
248 assert!(defend.gaps.iter().all(|gap| {
249 gap.kind == RealProjectGapKind::UnknownAction
250 && gap.identity == "rawWorkshopAction"
251 && gap.classification == ResidualClassification::LegacyOpaque
252 }));
253
254 let error = WorkshopError::Unknown {
255 kind: "action",
256 spelling: "rawWorkshopAction".to_string(),
257 locale: crate::catalog::Locale::new("en-US"),
258 span: None,
259 };
260 assert!(defend.admits_gap(RealProjectStage::Emission, &error));
261 assert!(!defend.admits_gap(
262 RealProjectStage::CanonicalValidation,
263 &WorkshopError::Unknown {
264 kind: "value",
265 spelling: "rawWorkshopAction".to_string(),
266 locale: crate::catalog::Locale::new("en-US"),
267 span: None,
268 }
269 ));
270 }
271}