@@ -105,47 +105,96 @@ def detect_supported_functions():
105105 return set (ALL_ACVP_FUNCTIONS )
106106
107107
108+ ENCAP_DECAP_FUNCTIONS = {
109+ "encapsulation" ,
110+ "encapsulationKeyCheck" ,
111+ "decapsulation" ,
112+ "decapsulationKeyCheck" ,
113+ }
114+
115+
108116def loadDefaultAcvpData (version , supported_functions = None ):
109117 if supported_functions is None :
110118 supported_functions = set (ALL_ACVP_FUNCTIONS )
111119
112- # The ACVP encapDecap file bundles encapsulation, decapsulation, and both
113- # *KeyCheck helpers. To keep result comparison byte-identical against the
114- # published expectedResults.json, we only load the file if all four
115- # sub-functions are compiled in.
116- encapDecap_functions = {
117- "encapsulation" ,
118- "encapsulationKeyCheck" ,
119- "decapsulation" ,
120- "decapsulationKeyCheck" ,
121- }
122- encapDecap_supported = encapDecap_functions .issubset (supported_functions )
123- keyGen_supported = "keyGen" in supported_functions
120+ # Load a prompt whenever any of its sub-functions is compiled in.
121+ # Unsupported sub-functions are filtered out per test case (see
122+ # filter_test_cases) so result comparison stays byte-identical.
123+ keyGen_any = "keyGen" in supported_functions
124+ encapDecap_any = bool (ENCAP_DECAP_FUNCTIONS & supported_functions )
124125
125126 data_dir = f"test/acvp/.acvp-data/{ version } /files"
126127 acvp_jsons_for_version = [
127128 (
128129 "keyGen" ,
129- keyGen_supported ,
130+ keyGen_any ,
130131 f"{ data_dir } /ML-KEM-keyGen-FIPS203/prompt.json" ,
131132 f"{ data_dir } /ML-KEM-keyGen-FIPS203/expectedResults.json" ,
132133 ),
133134 (
134135 "encapDecap" ,
135- encapDecap_supported ,
136+ encapDecap_any ,
136137 f"{ data_dir } /ML-KEM-encapDecap-FIPS203/prompt.json" ,
137138 f"{ data_dir } /ML-KEM-encapDecap-FIPS203/expectedResults.json" ,
138139 ),
139140 ]
140141 acvp_data = []
141142 for mode , is_supported , prompt , expectedResults in acvp_jsons_for_version :
142143 if not is_supported :
143- info (f"Skipping { mode } tests (mode not supported in this build)" )
144+ warn (
145+ f"WARNING: Skipped { mode } ACVP prompt "
146+ f"(no sub-function supported by this build)."
147+ )
144148 continue
145149 acvp_data .append (loadAcvpData (prompt , expectedResults ))
146150 return acvp_data
147151
148152
153+ def unwrap_acvts (data ):
154+ # ACVTS files wrap the payload as [{"acvVersion": ...}, {...}].
155+ return data [1 ] if isinstance (data , list ) else data
156+
157+
158+ def filter_test_cases (acvp_data , should_drop ):
159+ """Drop cases for which should_drop(tg, tc) returns a reason (None keeps).
160+ Reasons are computed from the prompt but applied consistently to both
161+ prompt and expected data, so downstream byte-identical result comparison
162+ still works. Returns the list of drop reasons for reporting."""
163+ reasons = []
164+ for _ , promptData , _ , expectedData in acvp_data :
165+ drop = {}
166+ for tg in unwrap_acvts (promptData ).get ("testGroups" , []):
167+ for tc in tg ["tests" ]:
168+ reason = should_drop (tg , tc )
169+ if reason is not None :
170+ drop [tg ["tgId" ], tc ["tcId" ]] = reason
171+ for data in (promptData , expectedData ):
172+ if data is None :
173+ continue
174+ for tg in unwrap_acvts (data ).get ("testGroups" , []):
175+ tg ["tests" ] = [
176+ tc for tc in tg ["tests" ] if (tg ["tgId" ], tc ["tcId" ]) not in drop
177+ ]
178+ reasons += drop .values ()
179+ return reasons
180+
181+
182+ def unsupported_function (supported_functions ):
183+ """Return a should_drop predicate that skips encapDecap test cases whose
184+ tg['function'] is not in supported_functions. keyGen prompts have no
185+ tg['function'] field, so they are never dropped by this predicate.
186+ The predicate returns the bare function name so the caller can format
187+ a compact 'Unsupported functions: A, B, C' summary."""
188+
189+ def should_drop (tg , tc ):
190+ fn = tg .get ("function" )
191+ if fn is None or fn in supported_functions :
192+ return None
193+ return fn
194+
195+ return should_drop
196+
197+
149198def err (msg , ** kwargs ):
150199 print (msg , file = sys .stderr , ** kwargs )
151200
@@ -154,6 +203,23 @@ def info(msg, **kwargs):
154203 print (msg , ** kwargs )
155204
156205
206+ # Warnings deferred to the end of the run so they stand out after all the
207+ # per-test-case chatter. Collected by warn(), emitted once by flush_warnings().
208+ _warnings = []
209+
210+
211+ def warn (msg ):
212+ _warnings .append (msg )
213+
214+
215+ def flush_warnings ():
216+ if not _warnings :
217+ return
218+ err ("" )
219+ for msg in _warnings :
220+ err (msg )
221+
222+
157223def get_acvp_binary (tg ):
158224 """Convert JSON dict for ACVP test group to suitable ACVP binary."""
159225 parameterSetToLevel = {
@@ -374,11 +440,23 @@ def test(prompt, expected, output, version, supported_functions=None):
374440 data = loadDefaultAcvpData (version , supported_functions )
375441
376442 if not data :
377- info ( " No ACVP tests supported by this build" )
443+ warn ( "WARNING: No ACVP tests supported by this build; no cases were run. " )
378444 info ("ALL GOOD!" )
445+ flush_warnings ()
379446 return
380447
448+ # Filter out test cases whose ACVP function isn't compiled in.
449+ if supported_functions is not None :
450+ reasons = filter_test_cases (data , unsupported_function (supported_functions ))
451+ if reasons :
452+ fns = ", " .join (sorted (set (reasons )))
453+ warn (
454+ f"WARNING: Dropped { len (reasons )} ACVP test case(s). "
455+ f"Unsupported functions: { fns } ."
456+ )
457+
381458 runTest (data , output )
459+ flush_warnings ()
382460
383461
384462parser = argparse .ArgumentParser ()
0 commit comments