11#!/usr/bin/python3
22"""
3- FMF scheduler for nosetests
3+ FMF scheduler via unittest testcases.
4+ Can be used as cli or as an argument to external scheduler like nosetest.
45Found test via fmf files and creates dynamic testclassed based on that.
56"""
67
1011import imp
1112import logging
1213import unittest
14+ import click
1315
1416from colin .core .target import Target , is_compatible
1517from colin .core .checks .fmf_check import ExtendedTree , FMFAbstractCheck
16- from colin .core .constant import PASSED
17- from colin .core .ruleset .ruleset import get_checks_path
18+ from colin .core .constant import PASSED , COLIN_CHECKS_PATH
19+ from colin .core .ruleset .ruleset import get_checks_paths
1820
1921from colin .core .checks .dockerfile import DockerfileAbstractCheck , DockerfileLabelAbstractCheck ,\
2022 InstructionCountAbstractCheck , InstructionAbstractCheck
@@ -97,41 +99,46 @@ def make_wrapped_fmf_class(node, target):
9799 'backendclass' : inernalclass
98100 })
99101
100- def nosetests_class_fmf_generator (fmfpath , target_name , log_level , ruleset_tree_path = None , filter_names = None , filters = None ):
102+
103+ def unittests_class_fmf_generator (fmfpathes , target_name , log_level , ruleset_tree_path = None , filter_names = None , filters = None ):
101104 """
102- generates dynamic test classes for nosetest or unittest scheduler based on FMF metadata.
105+ generates dynamic test unittest classes based on FMF metadata.
103106
104- :param fmfpath : path to checks
107+ :param fmfpathes : path to checks
105108 :param target_name: what is the target object
106109 :param log_level:
107110 :param ruleset_tree_path:
108111 :return:
109112 """
110113 target = Target (target_name , log_level )
111114 test_classes = {}
112- if not ruleset_tree_path :
113- ruleset_tree_path = fmfpath
114- ruleset_metadatatree = ExtendedTree (ruleset_tree_path )
115- metadatatree = ExtendedTree (fmfpath )
116- ruleset_metadatatree .references (metadatatree )
117- for node in ruleset_metadatatree .prune (names = filter_names , filters = filters ):
118- if node .data .get ("class" ) or node .data .get ("test" ):
119- logger .debug ("node (%s) contains test and class item" , node .name )
120-
121- if node .data .get ("class" ) in CLASS_MAPPING :
122- logger .debug ("Using pure FMF metadata for %s (class %s)" , node .name , node .data .get ("class" ))
123- test_class = make_base_fmf_class_abstract (node = node , target = target )
124- else :
125- logger .debug ("searching for %s" , node .name )
126- test_class = make_wrapped_fmf_class (node = node , target = target )
127- if is_compatible (target_type = target .target_type , check_instance = test_class .backendclass ()):
128- test_classes [test_class .__name__ ] = test_class
129- logger .debug ("Test added: %s" , node .name )
115+ metadata_forest = []
116+ if ruleset_tree_path :
117+ ruleset_mtd = ExtendedTree (ruleset_tree_path )
118+ source_metadata_trees = [ExtendedTree (fmfpath ) for fmfpath in fmfpathes ]
119+ ruleset_mtd .references (datatrees = source_metadata_trees )
120+ metadata_forest = [ruleset_mtd ]
121+ else :
122+ metadata_forest = [ExtendedTree (fmfpath ) for fmfpath in fmfpathes ]
123+ for metadata_item in metadata_forest :
124+ for node in metadata_item .prune (names = filter_names , filters = filters ):
125+ if node .data .get ("class" ) or node .data .get ("test" ):
126+ logger .debug ("node (%s) contains test and class item" , node .name )
127+
128+ if node .data .get ("class" ) in CLASS_MAPPING :
129+ logger .debug ("Using pure FMF metadata for %s (class %s)" , node .name , node .data .get ("class" ))
130+ test_class = make_base_fmf_class_abstract (node = node , target = target )
131+ else :
132+ logger .debug ("searching for %s" , node .name )
133+ test_class = make_wrapped_fmf_class (node = node , target = target )
134+ if is_compatible (target_type = target .target_type , check_instance = test_class .backendclass ()):
135+ test_classes [test_class .__name__ ] = test_class
136+ logger .debug ("Test added: %s" , node .name )
137+ else :
138+ logger .debug ("Test (not target): %s" , node .name )
130139 else :
131- logger .debug ("Test (not target): %s" , node .name )
132- else :
133- if "__pycache__" not in node .name :
134- logger .warning ("error in fmf config for node (missing test and class items): %s (data: %s) " , node .name , node .data )
140+ if "__pycache__" not in node .name :
141+ logger .warning ("error in fmf config for node (missing test and class items): %s (data: %s) " , node .name , node .data )
135142 return test_classes
136143
137144
@@ -154,7 +161,7 @@ def scheduler_opts(target_name=None, checks=None, ruleset_path=None,
154161 if not target_name :
155162 raise EnvironmentError ("TARGET envvar is not set." )
156163 if not checks :
157- checks = get_checks_path ()
164+ checks = get_checks_paths ()
158165 if not ruleset_path :
159166 ruleset_path = os .environ .get ("RULESETPATH" )
160167 if not filters :
@@ -163,28 +170,49 @@ def scheduler_opts(target_name=None, checks=None, ruleset_path=None,
163170 filter_names = os .environ .get ("NAMES" , "" ).split (";" )
164171 if not log_level :
165172 log_level = get_log_level ()
166- output = nosetests_class_fmf_generator (checks , target_name ,
173+ output = unittests_class_fmf_generator (checks , target_name ,
167174 ruleset_tree_path = ruleset_path ,
168175 filter_names = filter_names ,
169176 filters = filters ,
170177 log_level = log_level )
171178 return output
172179
180+ @click .command ()
181+ @click .argument ('target' , type = click .STRING )
182+ @click .option ('checks_paths' , '-c' , '--checks-paths' ,
183+ type = click .Path (exists = True , dir_okay = True , file_okay = False ),
184+ multiple = True , envvar = COLIN_CHECKS_PATH , default = get_checks_paths (),
185+ help = "Path to directory containing checks (default {})." .format (get_checks_paths ()))
186+ @click .option ('ruleset_dir' , '--ruleset-dir' , '-r' , type = click .Path (),
187+ help = "Path to a directory with rulesets" )
188+ @click .option ('--name' , '-n' , multiple = True , type = click .STRING ,
189+ help = "Select cases by key names" )
190+ @click .option ('filter_opts' ,'--filter' , '-f' , multiple = True , type = click .STRING ,
191+ help = "Filter cases based on FMF filter rules" )
192+ @click .option ('-v' , '--verbose' , type = click .INT , default = 0 ,
193+ help = "change verbosity of unittest scheduler" )
194+ @click .option ('--debug' , default = False , is_flag = True ,
195+ help = "Enable debugging mode (debugging logs, full tracebacks)." )
196+ def cmdline (target , checks_paths , ruleset_dir , name , filter_opts , verbose , debug ):
197+ log_level = logging .INFO
198+ if debug :
199+ log_level = logging .DEBUG
200+ logging .basicConfig (stream = sys .stdout , level = log_level )
201+ test_classes = scheduler_opts (target_name = target , checks = checks_paths , ruleset_path = ruleset_dir ,
202+ filter_names = name , filters = filter_opts , log_level = log_level )
203+ loader = unittest .TestLoader ()
204+ tests = [loader .loadTestsFromTestCase (test ) for test in test_classes .values ()]
205+ suite = unittest .TestSuite (tests )
206+
207+ runner = unittest .TextTestRunner (verbosity = verbose )
208+ runner .run (suite )
173209
174- if __name__ == "__main__" :
175- logging .basicConfig (stream = sys .stdout , level = get_log_level ())
176- classes = scheduler_opts ()
177- for item in classes :
178- globals ()[item ] = classes [item ]
179210
180- # try to schedule it via nosetests in case of direct schedule
181- import nose
182- logger .info ("number of test classes: %s" , len (classes ))
183- module_name = sys .modules [__name__ ].__file__
184- logging .debug ("running nose for package: %s" , module_name )
185- result = nose .run (argv = [sys .argv [0 ], module_name , '-v' ])
186- logging .info ("all tests ok: %s" , result )
211+ if __name__ == "__main__" :
212+ cmdline ()
187213else :
214+ # when used as an testsuite set all classes globals, you can then invoke it eg:
215+ # TARGET=tests/data/Dockerfile nosetests fmf_scheduler.py
188216 classes = scheduler_opts ()
189217 for item in classes :
190218 globals ()[item ] = classes [item ]
0 commit comments