hyf

Context-aware query service for Radroots
git clone https://radroots.dev/git/hyf.git
Log | Files | Refs | README | LICENSE

fixture_validator.mojo (12679B)


      1 from std.collections import List
      2 
      3 from std.os.path import exists
      4 from std.pathlib import Path
      5 from json import Value, loads
      6 
      7 
      8 @fieldwise_init
      9 struct FixtureValidationIssue(Copyable, Movable):
     10     var case_id: String
     11     var rule: String
     12     var detail: String
     13 
     14 
     15 def registered_fixture_operators() -> List[String]:
     16     var operators = List[String]()
     17     operators.append("equals")
     18     operators.append("absent")
     19     operators.append("present")
     20     operators.append("contains")
     21     operators.append("not_equals")
     22     return operators^
     23 
     24 
     25 def _has_key(value: Value, key: String) -> Bool:
     26     for candidate in value.object_keys():
     27         if candidate == key:
     28             return True
     29     return False
     30 
     31 
     32 def _is_registered_operator(operator: String, registered: List[String]) -> Bool:
     33     for candidate in registered:
     34         if candidate == operator:
     35             return True
     36     return False
     37 
     38 
     39 def validate_fixture_corpus(
     40     directory: String,
     41 ) raises -> List[FixtureValidationIssue]:
     42     var issues = List[FixtureValidationIssue]()
     43     var base = Path(directory)
     44     var manifest = loads((base / "manifest.json").read_text())
     45     if not _has_key(manifest, "cases"):
     46         issues.append(
     47             FixtureValidationIssue(
     48                 case_id="", rule="missing_cases", detail="manifest has no cases"
     49             )
     50         )
     51         return issues^
     52 
     53     var cases = manifest["cases"].array_items()
     54     if len(cases) == 0:
     55         issues.append(
     56             FixtureValidationIssue(
     57                 case_id="",
     58                 rule="empty_corpus",
     59                 detail="manifest declares no cases",
     60             )
     61         )
     62 
     63     var seen = List[String]()
     64     var registered = registered_fixture_operators()
     65     for entry in cases:
     66         var case_id = entry["case_id"].string_value()
     67 
     68         var duplicate = False
     69         for prior in seen:
     70             if prior == case_id:
     71                 duplicate = True
     72         if duplicate:
     73             issues.append(
     74                 FixtureValidationIssue(
     75                     case_id=case_id,
     76                     rule="duplicate_case_id",
     77                     detail="case_id appears more than once",
     78                 )
     79             )
     80         seen.append(String(case_id))
     81 
     82         var relative_path = entry["path"].string_value()
     83         var case_path = base / relative_path
     84         if not exists(case_path):
     85             issues.append(
     86                 FixtureValidationIssue(
     87                     case_id=case_id,
     88                     rule="dangling_path",
     89                     detail=relative_path,
     90                 )
     91             )
     92             continue
     93 
     94         var doc = loads(case_path.read_text())
     95         if (
     96             not _has_key(doc, "case_id")
     97             or doc["case_id"].string_value() != case_id
     98         ):
     99             issues.append(
    100                 FixtureValidationIssue(
    101                     case_id=case_id,
    102                     rule="case_id_mismatch",
    103                     detail="case_id does not match manifest",
    104                 )
    105             )
    106         if (
    107             not _has_key(doc, "requirements")
    108             or len(doc["requirements"].array_items()) == 0
    109         ):
    110             issues.append(
    111                 FixtureValidationIssue(
    112                     case_id=case_id,
    113                     rule="empty_requirements",
    114                     detail="no requirement references",
    115                 )
    116             )
    117         if (
    118             not _has_key(doc, "required_from_step")
    119             or doc["required_from_step"].string_value() == ""
    120         ):
    121             issues.append(
    122                 FixtureValidationIssue(
    123                     case_id=case_id,
    124                     rule="missing_activation_step",
    125                     detail="required_from_step is empty",
    126                 )
    127             )
    128         elif _has_key(entry, "required_from_step") and (
    129             doc["required_from_step"].string_value()
    130             != entry["required_from_step"].string_value()
    131         ):
    132             issues.append(
    133                 FixtureValidationIssue(
    134                     case_id=case_id,
    135                     rule="activation_step_mismatch",
    136                     detail="required_from_step does not match manifest",
    137                 )
    138             )
    139         if not _has_key(doc, "then") or len(doc["then"].array_items()) == 0:
    140             issues.append(
    141                 FixtureValidationIssue(
    142                     case_id=case_id,
    143                     rule="empty_expectations",
    144                     detail="no expected assertions",
    145                 )
    146             )
    147         else:
    148             for assertion in doc["then"].array_items():
    149                 if not _has_key(assertion, "operator"):
    150                     issues.append(
    151                         FixtureValidationIssue(
    152                             case_id=case_id,
    153                             rule="missing_operator",
    154                             detail="assertion has no operator",
    155                         )
    156                     )
    157                     continue
    158                 var operator = assertion["operator"].string_value()
    159                 if not _is_registered_operator(operator, registered):
    160                     issues.append(
    161                         FixtureValidationIssue(
    162                             case_id=case_id,
    163                             rule="unknown_operator",
    164                             detail=operator,
    165                         )
    166                     )
    167         if not _has_key(doc, "provenance"):
    168             issues.append(
    169                 FixtureValidationIssue(
    170                     case_id=case_id,
    171                     rule="missing_provenance",
    172                     detail="provenance is required",
    173                 )
    174             )
    175         if (
    176             not _has_key(doc, "implementation_status")
    177             or doc["implementation_status"].string_value() != "planned"
    178         ):
    179             issues.append(
    180                 FixtureValidationIssue(
    181                     case_id=case_id,
    182                     rule="unexpected_status",
    183                     detail="new corpus cases must be planned until activated",
    184                 )
    185             )
    186 
    187     if _has_key(manifest, "raw_files"):
    188         for raw in manifest["raw_files"].array_items():
    189             if not exists(base / raw.string_value()):
    190                 issues.append(
    191                     FixtureValidationIssue(
    192                         case_id="",
    193                         rule="dangling_raw",
    194                         detail=raw.string_value(),
    195                     )
    196                 )
    197 
    198     return issues^
    199 
    200 
    201 def validate_requirement_traceability(
    202     registry_path: String, manifest_path: String, steps_path: String
    203 ) raises -> List[FixtureValidationIssue]:
    204     var issues = List[FixtureValidationIssue]()
    205     var registry = loads(Path(registry_path).read_text())
    206     var manifest = loads(Path(manifest_path).read_text())
    207     var steps = loads(Path(steps_path).read_text())
    208 
    209     var step_ids = List[String]()
    210     for step in steps["steps"].array_items():
    211         step_ids.append(step["id"].string_value())
    212 
    213     var case_ids = List[String]()
    214     for case_entry in manifest["cases"].array_items():
    215         case_ids.append(case_entry["case_id"].string_value())
    216 
    217     var requirement_ids = List[String]()
    218     var requirements = registry["requirements"].array_items()
    219     if len(requirements) == 0:
    220         issues.append(
    221             FixtureValidationIssue(
    222                 case_id="", rule="empty_registry", detail="no requirements"
    223             )
    224         )
    225     for requirement in requirements:
    226         var rid = requirement["id"].string_value()
    227         var duplicate = False
    228         for prior in requirement_ids:
    229             if prior == rid:
    230                 duplicate = True
    231         if duplicate:
    232             issues.append(
    233                 FixtureValidationIssue(
    234                     case_id=rid,
    235                     rule="duplicate_requirement",
    236                     detail="duplicate requirement id",
    237                 )
    238             )
    239         requirement_ids.append(String(rid))
    240 
    241         var method = requirement["verification_method"].string_value()
    242         if method == "":
    243             issues.append(
    244                 FixtureValidationIssue(
    245                     case_id=rid,
    246                     rule="missing_verification_method",
    247                     detail="verification_method is empty",
    248                 )
    249             )
    250         var impl_steps = requirement["implementation_steps"].array_items()
    251         if len(impl_steps) == 0:
    252             issues.append(
    253                 FixtureValidationIssue(
    254                     case_id=rid,
    255                     rule="uncovered_requirement",
    256                     detail="no implementation steps",
    257                 )
    258             )
    259         else:
    260             for step in impl_steps:
    261                 var sid = step.string_value()
    262                 var found = False
    263                 for known in step_ids:
    264                     if known == sid:
    265                         found = True
    266                 if not found:
    267                     issues.append(
    268                         FixtureValidationIssue(
    269                             case_id=rid,
    270                             rule="unknown_step",
    271                             detail=sid,
    272                         )
    273                     )
    274         var fixtures = requirement["fixture_ids"].array_items()
    275         for fixture in fixtures:
    276             var fid = fixture.string_value()
    277             var found = False
    278             for known in case_ids:
    279                 if known == fid:
    280                     found = True
    281             if not found:
    282                 issues.append(
    283                     FixtureValidationIssue(
    284                         case_id=rid,
    285                         rule="dangling_fixture",
    286                         detail=fid,
    287                     )
    288                 )
    289 
    290     var step_seen = List[String]()
    291     for step in steps["steps"].array_items():
    292         var sid = step["id"].string_value()
    293         var duplicate = False
    294         for prior in step_seen:
    295             if prior == sid:
    296                 duplicate = True
    297         if duplicate:
    298             issues.append(
    299                 FixtureValidationIssue(
    300                     case_id=sid,
    301                     rule="duplicate_step",
    302                     detail="duplicate step id",
    303                 )
    304             )
    305         step_seen.append(String(sid))
    306         for dependency in step["dependencies"].array_items():
    307             var did = dependency.string_value()
    308             var found = False
    309             for known in step_ids:
    310                 if known == did:
    311                     found = True
    312             if not found:
    313                 issues.append(
    314                     FixtureValidationIssue(
    315                         case_id=sid,
    316                         rule="dangling_dependency",
    317                         detail=did,
    318                     )
    319                 )
    320 
    321     return issues^
    322 
    323 
    324 def validate_step_states(path: String) raises -> List[FixtureValidationIssue]:
    325     var issues = List[FixtureValidationIssue]()
    326     var doc = loads(Path(path).read_text())
    327     if not _has_key(doc, "states"):
    328         issues.append(
    329             FixtureValidationIssue(
    330                 case_id="", rule="missing_states", detail="no states declared"
    331             )
    332         )
    333         return issues^
    334 
    335     var states = List[String]()
    336     for entry in doc["states"].array_items():
    337         var state = entry.string_value()
    338         var duplicate = False
    339         for prior in states:
    340             if prior == state:
    341                 duplicate = True
    342         if duplicate:
    343             issues.append(
    344                 FixtureValidationIssue(
    345                     case_id="", rule="duplicate_state", detail=state
    346                 )
    347             )
    348         states.append(String(state))
    349 
    350     for required in ["PASSED", "NOT_RUN", "NOT_APPLICABLE"]:
    351         var found = False
    352         for state in states:
    353             if state == required:
    354                 found = True
    355         if not found:
    356             issues.append(
    357                 FixtureValidationIssue(
    358                     case_id="", rule="missing_state", detail=required
    359                 )
    360             )
    361 
    362     if (
    363         not _has_key(doc, "passed_requires_executed_evidence")
    364         or not doc["passed_requires_executed_evidence"].bool_value()
    365     ):
    366         issues.append(
    367             FixtureValidationIssue(
    368                 case_id="",
    369                 rule="passed_without_evidence",
    370                 detail="PASSED must require executed evidence",
    371             )
    372         )
    373 
    374     if not _has_key(doc, "rules") or len(doc["rules"].array_items()) == 0:
    375         issues.append(
    376             FixtureValidationIssue(
    377                 case_id="",
    378                 rule="missing_state_rules",
    379                 detail="state rules must be documented",
    380             )
    381         )
    382 
    383     return issues^