55#
66# (c) Eduard Itrich <eduard@itrich.net>
77# (c) Kurt Garloff <kurt@garloff.de>
8- # (c) Matthias Büchse <matthias.buechse@cloudandheat.com >
8+ # (c) Matthias Büchse <matthias.buechse@alasca.cloud >
99# SPDX-License-Identifier: Apache-2.0
1010
1111"""Main SCS compliance checker
2626import getopt
2727import datetime
2828import subprocess
29- from collections import defaultdict
3029from itertools import chain
3130import logging
3231import yaml
@@ -153,10 +152,6 @@ def select_valid(versions: list) -> list:
153152 return [version for version in versions if version ['_explicit_validity' ]]
154153
155154
156- def suppress (* args , ** kwargs ):
157- return
158-
159-
160155def invoke_check_tool (exe , args , env , cwd ):
161156 """run check tool and return invokation dict to use in the report"""
162157 try :
@@ -182,7 +177,7 @@ def invoke_check_tool(exe, args, env, cwd):
182177 return invokation
183178
184179
185- def compute_results (stdout ):
180+ def compute_results (stdout , permissible_ids = () ):
186181 """pick out test results from stdout lines"""
187182 result = {}
188183 for line in stdout :
@@ -192,15 +187,18 @@ def compute_results(stdout):
192187 value = TESTCASE_VERDICTS .get (parts [1 ].strip ().upper ())
193188 if value is None :
194189 continue
195- result [parts [0 ].strip ()] = value
190+ testcase_id = parts [0 ].strip ()
191+ if permissible_ids and testcase_id not in permissible_ids :
192+ logger .warning (f"ignoring invalid result id: { testcase_id } " )
193+ continue
194+ result [testcase_id ] = value
196195 return result
197196
198197
199198class CheckRunner :
200199 def __init__ (self , cwd , assignment , verbosity = 0 ):
201200 self .cwd = cwd
202201 self .assignment = assignment
203- self .memo = {}
204202 self .num_abort = 0
205203 self .num_error = 0
206204 self .verbosity = verbosity
@@ -212,61 +210,30 @@ def run(self, check, testcases=()):
212210 args = check .get ('args' , '' ).format (** assignment )
213211 env = {key : value .format (** assignment ) for key , value in check .get ('env' , {}).items ()}
214212 env_str = " " .join (f"{ key } ={ value } " for key , value in env .items ())
215- memo_key = f"{ env_str } { check ['executable' ]} { args } " .strip ()
216- logger .debug (f"running { memo_key !r} ..." )
217- invocation = self .memo .get (memo_key )
218- if invocation is None :
219- check_env = {** os .environ , ** env }
220- invocation = invoke_check_tool (check ["executable" ], args , check_env , self .cwd )
221- invocation = {
222- 'id' : str (uuid .uuid4 ()),
223- 'cmd' : memo_key ,
224- 'result' : 0 , # keep this for backwards compatibility
225- 'results' : compute_results (invocation ['stdout' ]),
226- ** invocation
227- }
228- if self .verbosity > 1 and invocation ["stdout" ]:
229- print ("\n " .join (invocation ["stdout" ]))
230- self .spamminess += 1
231- # the following check used to be "> 0", but this is quite verbose...
232- if invocation ['rc' ] or self .verbosity > 1 and invocation ["stderr" ]:
233- print ("\n " .join (invocation ["stderr" ]))
234- self .spamminess += 1
235- self .memo [memo_key ] = invocation
213+ cmd = f"{ env_str } { check ['executable' ]} { args } " .strip ()
214+ logger .debug (f"running { cmd !r} ..." )
215+ check_env = {** os .environ , ** env }
216+ invocation = invoke_check_tool (check ["executable" ], args , check_env , self .cwd )
217+ invocation = {
218+ 'id' : str (uuid .uuid4 ()),
219+ 'cmd' : cmd ,
220+ 'results' : compute_results (invocation ['stdout' ], permissible_ids = testcases ),
221+ ** invocation
222+ }
223+ if self .verbosity > 1 and invocation ["stdout" ]:
224+ print ("\n " .join (invocation ["stdout" ]))
225+ self .spamminess += 1
226+ # the following check used to be "> 0", but this is quite verbose...
227+ if invocation ['rc' ] or self .verbosity > 1 and invocation ["stderr" ]:
228+ print ("\n " .join (invocation ["stderr" ]))
229+ self .spamminess += 1
236230 logger .debug (f".. rc { invocation ['rc' ]} , { invocation ['critical' ]} critical, { invocation ['error' ]} error" )
237231 self .num_abort += invocation ["critical" ]
238232 self .num_error += invocation ["error" ]
239233 # count failed testcases because they need not be reported redundantly on the error channel
240234 self .num_error + len ([value for value in invocation ['results' ].values () if value < 0 ])
241235 return invocation
242236
243- def get_invocations (self ):
244- return {invocation ['id' ]: invocation for invocation in self .memo .values ()}
245-
246-
247- class ResultBuilder :
248- def __init__ (self , name ):
249- self .name = name
250- self ._raw = defaultdict (list )
251-
252- def record (self , id_ , ** kwargs ):
253- self ._raw [id_ ].append (kwargs )
254-
255- def finalize (self , permissible_ids = None ):
256- final = {}
257- for id_ , ls in self ._raw .items ():
258- if permissible_ids is not None and id_ not in permissible_ids :
259- logger .warning (f"ignoring invalid result id: { id_ } " )
260- continue
261- # just in case: sort by value (worst first)
262- ls .sort (key = lambda item : item ['result' ])
263- winner , runnerups = ls [0 ], ls [1 :]
264- if runnerups :
265- logger .warning (f"multiple result values for { id_ } " )
266- winner = {** winner , 'runnerups' : runnerups }
267- final [id_ ] = winner
268- return final
269-
270237
271238def print_report (subject : str , title : str , testcase_lookup : dict , targets : dict , results : dict , partial = False , verbose = False ):
272239 print (f"{ subject } { title } :" )
@@ -294,12 +261,12 @@ def print_report(subject: str, title: str, testcase_lookup: dict, targets: dict,
294261 print (f" - { category } :" )
295262 for tc_id in offenders :
296263 print (f" - { tc_id } :" )
297- _ , testcase = testcase_lookup [tc_id ]
264+ testcase = testcase_lookup [tc_id ]
298265 if 'description' in testcase : # used to be `verbose and ...`, but users need the URL!
299266 print (f" > { testcase ['description' ].strip ()} " )
300267
301268
302- def create_report (argv , config , spec , results , invocations ):
269+ def create_report (argv , config , spec , invocations ):
303270 return {
304271 # these fields are essential:
305272 # results are no longer specific to version!
@@ -320,7 +287,7 @@ def create_report(argv, config, spec, results, invocations):
320287 "sections" : config .sections ,
321288 "forced_version" : config .version or None ,
322289 "forced_tests" : None if config .tests is None else config .tests .pattern ,
323- "invocations" : invocations ,
290+ "invocations" : { invocation [ 'id' ]: invocation for invocation in invocations } ,
324291 },
325292 }
326293
@@ -358,15 +325,16 @@ def main(argv):
358325 title += f" [tests: '{ config .tests .pattern } ']"
359326 partial = True
360327 # collect all testcases we need
361- all_testcases = set ()
328+ all_testcase_ids = set ()
362329 for version in versions :
363- for target , testcase_ids in version ['targets' ].items ():
364- all_testcases .update (testcase_ids )
330+ for testcase_ids in version ['targets' ].values ():
331+ all_testcase_ids .update (testcase_ids )
365332 # collect scripts to be run
366333 testcase_lookup = spec ['testcases' ]
334+ tc_script_lookup = spec ['tc_scripts' ]
367335 script_info = {}
368- for tc_id in all_testcases :
369- script , testcase = testcase_lookup [tc_id ]
336+ for tc_id in all_testcase_ids :
337+ script = tc_script_lookup [tc_id ]
370338 if 'executable' not in script :
371339 continue # manual check
372340 if config .sections and script .get ('section' ) not in config .sections :
@@ -375,18 +343,18 @@ def main(argv):
375343 continue
376344 item = script_info .get (id (script ))
377345 if item is None :
378- _ , testcases = script_info [id (script )] = (script , [])
346+ _ , testcase_ids = script_info [id (script )] = (script , [])
379347 else :
380- _ , testcases = item
381- testcases .append (tc_id )
348+ _ , testcase_ids = item
349+ testcase_ids .append (tc_id )
382350 # run scripts
383- builder = ResultBuilder ( '(dummy)' )
384- for item in script_info . values ():
385- script , testcases = item
386- invocation = runner . run ( script , testcases = sorted ( testcases ))
387- for id_ , value in invocation [ "results" ]. items ():
388- builder . record ( id_ , result = value , invocation = invocation [ 'id' ])
389- results = builder . finalize ( permissible_ids = all_testcases )
351+ invocations = [
352+ runner . run ( script , testcases = sorted ( testcases ))
353+ for script , testcases in script_info . values ()
354+ ]
355+ results = {}
356+ for invocation in invocations :
357+ results . update ( invocation [ 'results' ] )
390358 # now report: to console if requested, and likewise for yaml output
391359 if not config .quiet :
392360 # print a horizontal line if we had any script output
@@ -395,7 +363,7 @@ def main(argv):
395363 for version in versions :
396364 print_report (config .subject , f"{ title } { version ['version' ]} " , testcase_lookup , version ['targets' ], results , partial , config .verbose )
397365 if config .output :
398- report = create_report (argv , config , spec , results , runner . get_invocations () )
366+ report = create_report (argv , config , spec , invocations )
399367 with open (config .output , 'w' , encoding = 'UTF-8' ) as fileobj :
400368 yaml .safe_dump (report , fileobj , default_flow_style = False , sort_keys = False , explicit_start = True )
401369 return min (127 , runner .num_abort + (0 if config .critical_only else runner .num_error ))
0 commit comments