00001
00002
00003
00004 from Options import Options
00005 options = Options()
00006
00007
00008
00009 import sys
00010 from Mixins import PrintOptions,_ParameterTypeBase,_SimpleParameterTypeBase, _Parameterizable, _ConfigureComponent, _TypedParameterizable, _Labelable, _Unlabelable, _ValidatingListBase
00011 from Mixins import *
00012 from Types import *
00013 from Modules import *
00014 from Modules import _Module
00015 from SequenceTypes import *
00016 from SequenceTypes import _ModuleSequenceType, _Sequenceable
00017 from SequenceVisitors import PathValidator, EndPathValidator
00018 from Utilities import *
00019 import DictTypes
00020
00021 from ExceptionHandling import *
00022
00023
00024 if sys.getrecursionlimit()<5000:
00025 sys.setrecursionlimit(5000)
00026
00027 def checkImportPermission(minLevel = 2, allowedPatterns = []):
00028 """
00029 Raise an exception if called by special config files. This checks
00030 the call or import stack for the importing file. An exception is raised if
00031 the importing module is not in allowedPatterns and if it is called too deeply:
00032 minLevel = 2: inclusion by top lvel cfg only
00033 minLevel = 1: No inclusion allowed
00034 allowedPatterns = ['Module1','Module2/SubModule1'] allows import
00035 by any module in Module1 or Submodule1
00036 """
00037
00038 import inspect
00039 import os
00040
00041 ignorePatterns = ['FWCore/ParameterSet/Config.py','<string>']
00042 CMSSWPath = [os.environ['CMSSW_BASE'],os.environ['CMSSW_RELEASE_BASE']]
00043
00044
00045 trueStack = []
00046 for item in inspect.stack():
00047 inPath = False
00048 ignore = False
00049
00050 for pattern in CMSSWPath:
00051 if item[1].find(pattern) != -1:
00052 inPath = True
00053 break
00054 if item[1].find('/') == -1:
00055 inPath = True
00056
00057 for pattern in ignorePatterns:
00058 if item[1].find(pattern) != -1:
00059 ignore = True
00060 break
00061
00062 if inPath and not ignore:
00063 trueStack.append(item[1])
00064
00065 importedFile = trueStack[0]
00066 importedBy = ''
00067 if len(trueStack) > 1:
00068 importedBy = trueStack[1]
00069
00070 for pattern in allowedPatterns:
00071 if importedBy.find(pattern) > -1:
00072 return True
00073
00074 if len(trueStack) <= minLevel:
00075 return True
00076
00077 raise ImportError("Inclusion of %s is allowed only by cfg or specified cfi files."
00078 % importedFile)
00079
00080 def findProcess(module):
00081 """Look inside the module and find the Processes it contains"""
00082 class Temp(object):
00083 pass
00084 process = None
00085 if isinstance(module,dict):
00086 if 'process' in module:
00087 p = module['process']
00088 module = Temp()
00089 module.process = p
00090 if hasattr(module,'process'):
00091 if isinstance(module.process,Process):
00092 process = module.process
00093 else:
00094 raise RuntimeError("The attribute named 'process' does not inherit from the Process class")
00095 else:
00096 raise RuntimeError("no 'process' attribute found in the module, please add one")
00097 return process
00098
00099
00100 class Process(object):
00101 """Root class for a CMS configuration process"""
00102 def __init__(self,name):
00103 self.__dict__['_Process__name'] = name
00104 self.__dict__['_Process__filters'] = {}
00105 self.__dict__['_Process__producers'] = {}
00106 self.__dict__['_Process__source'] = None
00107 self.__dict__['_Process__looper'] = None
00108 self.__dict__['_Process__schedule'] = None
00109 self.__dict__['_Process__analyzers'] = {}
00110 self.__dict__['_Process__outputmodules'] = {}
00111 self.__dict__['_Process__paths'] = DictTypes.SortedKeysDict()
00112 self.__dict__['_Process__endpaths'] = DictTypes.SortedKeysDict()
00113 self.__dict__['_Process__sequences'] = {}
00114 self.__dict__['_Process__services'] = {}
00115 self.__dict__['_Process__essources'] = {}
00116 self.__dict__['_Process__esproducers'] = {}
00117 self.__dict__['_Process__esprefers'] = {}
00118 self.__dict__['_Process__psets']={}
00119 self.__dict__['_Process__vpsets']={}
00120 self.__dict__['_cloneToObjectDict'] = {}
00121
00122 self.__dict__['_Process__InExtendCall'] = False
00123 self.__dict__['_Process__partialschedules'] = {}
00124 self.__isStrict = False
00125
00126 def setStrict(self, value):
00127 self.__isStrict = value
00128 _Module.__isStrict__ = True
00129
00130
00131 def producerNames(self):
00132 return ' '.join(self.producers_().keys())
00133 def analyzerNames(self):
00134 return ' '.join(self.analyzers_().keys())
00135 def filterNames(self):
00136 return ' '.join(self.filters_().keys())
00137 def pathNames(self):
00138 return ' '.join(self.paths_().keys())
00139
00140 def __setstate__(self, pkldict):
00141 """
00142 Unpickling hook.
00143
00144 Since cloneToObjectDict stores a hash of objects by their
00145 id() it needs to be updated when unpickling to use the
00146 new object id values instantiated during the unpickle.
00147
00148 """
00149 self.__dict__.update(pkldict)
00150 tmpDict = {}
00151 for value in self._cloneToObjectDict.values():
00152 tmpDict[id(value)] = value
00153 self.__dict__['_cloneToObjectDict'] = tmpDict
00154
00155
00156
00157 def filters_(self):
00158 """returns a dict of the filters which have been added to the Process"""
00159 return DictTypes.FixedKeysDict(self.__filters)
00160 filters = property(filters_, doc="dictionary containing the filters for the process")
00161 def name_(self):
00162 return self.__name
00163 def setName_(self,name):
00164 self.__dict__['_Process__name'] = name
00165 process = property(name_,setName_, doc="name of the process")
00166 def producers_(self):
00167 """returns a dict of the producers which have been added to the Process"""
00168 return DictTypes.FixedKeysDict(self.__producers)
00169 producers = property(producers_,doc="dictionary containing the producers for the process")
00170 def source_(self):
00171 """returns the source which has been added to the Process or None if none have been added"""
00172 return self.__source
00173 def setSource_(self,src):
00174 self._placeSource('source',src)
00175 source = property(source_,setSource_,doc='the main source or None if not set')
00176 def looper_(self):
00177 """returns the looper which has been added to the Process or None if none have been added"""
00178 return self.__looper
00179 def setLooper_(self,lpr):
00180 self._placeLooper('looper',lpr)
00181 looper = property(looper_,setLooper_,doc='the main looper or None if not set')
00182 def analyzers_(self):
00183 """returns a dict of the analyzers which have been added to the Process"""
00184 return DictTypes.FixedKeysDict(self.__analyzers)
00185 analyzers = property(analyzers_,doc="dictionary containing the analyzers for the process")
00186 def outputModules_(self):
00187 """returns a dict of the output modules which have been added to the Process"""
00188 return DictTypes.FixedKeysDict(self.__outputmodules)
00189 outputModules = property(outputModules_,doc="dictionary containing the output_modules for the process")
00190 def paths_(self):
00191 """returns a dict of the paths which have been added to the Process"""
00192 return DictTypes.SortedAndFixedKeysDict(self.__paths)
00193 paths = property(paths_,doc="dictionary containing the paths for the process")
00194 def endpaths_(self):
00195 """returns a dict of the endpaths which have been added to the Process"""
00196 return DictTypes.SortedAndFixedKeysDict(self.__endpaths)
00197 endpaths = property(endpaths_,doc="dictionary containing the endpaths for the process")
00198 def sequences_(self):
00199 """returns a dict of the sequences which have been added to the Process"""
00200 return DictTypes.FixedKeysDict(self.__sequences)
00201 sequences = property(sequences_,doc="dictionary containing the sequences for the process")
00202 def schedule_(self):
00203 """returns the schedule which has been added to the Process or None if none have been added"""
00204 return self.__schedule
00205 def setPartialSchedule_(self,sch,label):
00206 if label == "schedule":
00207 self.setSchedule_(sch)
00208 else:
00209 self._place(label, sch, self.__partialschedules)
00210 def setSchedule_(self,sch):
00211
00212 index = 0
00213 try:
00214 for p in sch:
00215 p.label_()
00216 index +=1
00217 except:
00218 raise RuntimeError("The path at index "+str(index)+" in the Schedule was not attached to the process.")
00219
00220 self.__dict__['_Process__schedule'] = sch
00221 schedule = property(schedule_,setSchedule_,doc='the schedule or None if not set')
00222 def services_(self):
00223 """returns a dict of the services which have been added to the Process"""
00224 return DictTypes.FixedKeysDict(self.__services)
00225 services = property(services_,doc="dictionary containing the services for the process")
00226 def es_producers_(self):
00227 """returns a dict of the esproducers which have been added to the Process"""
00228 return DictTypes.FixedKeysDict(self.__esproducers)
00229 es_producers = property(es_producers_,doc="dictionary containing the es_producers for the process")
00230 def es_sources_(self):
00231 """returns a the es_sources which have been added to the Process"""
00232 return DictTypes.FixedKeysDict(self.__essources)
00233 es_sources = property(es_sources_,doc="dictionary containing the es_sources for the process")
00234 def es_prefers_(self):
00235 """returns a dict of the es_prefers which have been added to the Process"""
00236 return DictTypes.FixedKeysDict(self.__esprefers)
00237 es_prefers = property(es_prefers_,doc="dictionary containing the es_prefers for the process")
00238 def psets_(self):
00239 """returns a dict of the PSets which have been added to the Process"""
00240 return DictTypes.FixedKeysDict(self.__psets)
00241 psets = property(psets_,doc="dictionary containing the PSets for the process")
00242 def vpsets_(self):
00243 """returns a dict of the VPSets which have been added to the Process"""
00244 return DictTypes.FixedKeysDict(self.__vpsets)
00245 vpsets = property(vpsets_,doc="dictionary containing the PSets for the process")
00246 def __setattr__(self,name,value):
00247
00248 if not name.replace('_','').isalnum():
00249 raise ValueError('The label '+name+' contains forbiden characters')
00250
00251
00252 if name.startswith('_Process__'):
00253 self.__dict__[name]=value
00254 return
00255 if not isinstance(value,_ConfigureComponent):
00256 raise TypeError("can only assign labels to an object which inherits from '_ConfigureComponent'\n"
00257 +"an instance of "+str(type(value))+" will not work")
00258 if not isinstance(value,_Labelable) and not isinstance(value,Source) and not isinstance(value,Looper) and not isinstance(value,Schedule):
00259 if name == value.type_():
00260 self.add_(value)
00261 return
00262 else:
00263 raise TypeError("an instance of "+str(type(value))+" can not be assigned the label '"+name+"'.\n"+
00264 "Please either use the label '"+value.type_()+" or use the 'add_' method instead.")
00265
00266 if self.__isStrict:
00267 newValue =value.copy()
00268 try:
00269 newValue._filename = value._filename
00270 except:
00271 pass
00272 value.setIsFrozen()
00273 else:
00274 newValue =value
00275 if not self._okToPlace(name, value, self.__dict__):
00276 msg = "Trying to override definition of process."+name
00277 msg += "\n new object defined in: "+value._filename
00278 msg += "\n existing object defined in: "+getattr(self,name)._filename
00279 raise ValueError(msg)
00280
00281 if hasattr(self,name) and not (getattr(self,name)==newValue):
00282
00283
00284
00285 if not self.__InExtendCall and isinstance(newValue, _Sequenceable):
00286 self._replaceInSequences(name, newValue)
00287 self.__delattr__(name)
00288 self.__dict__[name]=newValue
00289 if isinstance(newValue,_Labelable):
00290 newValue.setLabel(name)
00291 self._cloneToObjectDict[id(value)] = newValue
00292 self._cloneToObjectDict[id(newValue)] = newValue
00293
00294 newValue._place(name,self)
00295
00296 def __delattr__(self,name):
00297 if not hasattr(self,name):
00298 raise KeyError('process does not know about '+name)
00299 elif name.startswith('_Process__'):
00300 raise ValueError('this attribute cannot be deleted')
00301 else:
00302
00303 dicts = [item for item in self.__dict__.values() if (type(item)==dict or type(item)==DictTypes.SortedKeysDict)]
00304 for reg in dicts:
00305 if reg.has_key(name): del reg[name]
00306
00307 obj = getattr(self,name)
00308 if isinstance(obj,_Labelable):
00309 getattr(self,name).setLabel(None)
00310
00311 try:
00312 del self.__dict__[name]
00313 except:
00314 pass
00315
00316 def add_(self,value):
00317 """Allows addition of components which do not have to have a label, e.g. Services"""
00318 if not isinstance(value,_ConfigureComponent):
00319 raise TypeError
00320 if not isinstance(value,_Unlabelable):
00321 raise TypeError
00322
00323
00324 if self.__isStrict:
00325 newValue =value.copy()
00326 value.setIsFrozen()
00327 else:
00328 newValue =value
00329 newValue._place('',self)
00330
00331 def _okToPlace(self, name, mod, d):
00332 if not self.__InExtendCall:
00333
00334 return True
00335 elif not self.__isStrict:
00336 return True
00337 elif name in d:
00338
00339
00340
00341
00342 if mod._isModified:
00343 if d[name]._isModified:
00344 return False
00345 else:
00346 return True
00347 else:
00348 return True
00349 else:
00350 return True
00351
00352 def _place(self, name, mod, d):
00353 if self._okToPlace(name, mod, d):
00354 if self.__isStrict and isinstance(mod, _ModuleSequenceType):
00355 d[name] = mod._postProcessFixup(self._cloneToObjectDict)
00356 else:
00357 d[name] = mod
00358 if isinstance(mod,_Labelable):
00359 mod.setLabel(name)
00360 def _placeOutputModule(self,name,mod):
00361 self._place(name, mod, self.__outputmodules)
00362 def _placeProducer(self,name,mod):
00363 self._place(name, mod, self.__producers)
00364 def _placeFilter(self,name,mod):
00365 self._place(name, mod, self.__filters)
00366 def _placeAnalyzer(self,name,mod):
00367 self._place(name, mod, self.__analyzers)
00368 def _placePath(self,name,mod):
00369 self._validateSequence(mod, name)
00370 try:
00371 self._place(name, mod, self.__paths)
00372 except ModuleCloneError, msg:
00373 context = format_outerframe(4)
00374 raise Exception("%sThe module %s in path %s is unknown to the process %s." %(context, msg, name, self._Process__name))
00375 def _placeEndPath(self,name,mod):
00376 self._validateSequence(mod, name)
00377 try:
00378 self._place(name, mod, self.__endpaths)
00379 except ModuleCloneError, msg:
00380 context = format_outerframe(4)
00381 raise Exception("%sThe module %s in endpath %s is unknown to the process %s." %(context, msg, name, self._Process__name))
00382 def _placeSequence(self,name,mod):
00383 self._validateSequence(mod, name)
00384 self._place(name, mod, self.__sequences)
00385 def _placeESProducer(self,name,mod):
00386 self._place(name, mod, self.__esproducers)
00387 def _placeESPrefer(self,name,mod):
00388 self._place(name, mod, self.__esprefers)
00389 def _placeESSource(self,name,mod):
00390 self._place(name, mod, self.__essources)
00391 def _placePSet(self,name,mod):
00392 self._place(name, mod, self.__psets)
00393 def _placeVPSet(self,name,mod):
00394 self._place(name, mod, self.__vpsets)
00395 def _placeSource(self,name,mod):
00396 """Allow the source to be referenced by 'source' or by type name"""
00397 if name != 'source':
00398 raise ValueError("The label '"+name+"' can not be used for a Source. Only 'source' is allowed.")
00399 if self.__dict__['_Process__source'] is not None :
00400 del self.__dict__[self.__dict__['_Process__source'].type_()]
00401 self.__dict__['_Process__source'] = mod
00402 self.__dict__[mod.type_()] = mod
00403 def _placeLooper(self,name,mod):
00404 if name != 'looper':
00405 raise ValueError("The label '"+name+"' can not be used for a Looper. Only 'looper' is allowed.")
00406 self.__dict__['_Process__looper'] = mod
00407 self.__dict__[mod.type_()] = mod
00408 def _placeService(self,typeName,mod):
00409 self._place(typeName, mod, self.__services)
00410 self.__dict__[typeName]=mod
00411 def load(self, moduleName):
00412 moduleName = moduleName.replace("/",".")
00413 module = __import__(moduleName)
00414 self.extend(sys.modules[moduleName])
00415 def extend(self,other,items=()):
00416 """Look in other and find types which we can use"""
00417
00418 self.__dict__['_Process__InExtendCall'] = True
00419
00420 seqs = dict()
00421 labelled = dict()
00422 for name in dir(other):
00423
00424 if name.startswith('_'):
00425 continue
00426 item = getattr(other,name)
00427 if name == "source" or name == "looper":
00428 self.__setattr__(name,item)
00429 elif isinstance(item,_ModuleSequenceType):
00430 seqs[name]=item
00431 elif isinstance(item,_Labelable):
00432 self.__setattr__(name,item)
00433 labelled[name]=item
00434 try:
00435 item.label_()
00436 except:
00437 item.setLabel(name)
00438 continue
00439 elif isinstance(item,Schedule):
00440 self.__setattr__(name,item)
00441 elif isinstance(item,_Unlabelable):
00442 self.add_(item)
00443
00444
00445 for name in seqs.iterkeys():
00446 seq = seqs[name]
00447
00448
00449 if id(seq) not in self._cloneToObjectDict:
00450 self.__setattr__(name,seq)
00451 else:
00452 newSeq = self._cloneToObjectDict[id(seq)]
00453 self.__dict__[name]=newSeq
00454 newSeq.setLabel(name)
00455
00456 newSeq._place(name,self)
00457 self.__dict__['_Process__InExtendCall'] = False
00458 def _dumpConfigNamedList(self,items,typeName,options):
00459 returnValue = ''
00460 for name,item in items:
00461 returnValue +=options.indentation()+typeName+' '+name+' = '+item.dumpConfig(options)
00462 return returnValue
00463 def _dumpConfigUnnamedList(self,items,typeName,options):
00464 returnValue = ''
00465 for name,item in items:
00466 returnValue +=options.indentation()+typeName+' = '+item.dumpConfig(options)
00467 return returnValue
00468 def _dumpConfigOptionallyNamedList(self,items,typeName,options):
00469 returnValue = ''
00470 for name,item in items:
00471 if name == item.type_():
00472 name = ''
00473 returnValue +=options.indentation()+typeName+' '+name+' = '+item.dumpConfig(options)
00474 return returnValue
00475 def dumpConfig(self, options=PrintOptions()):
00476 """return a string containing the equivalent process defined using the configuration language"""
00477 config = "process "+self.__name+" = {\n"
00478 options.indent()
00479 if self.source_():
00480 config += options.indentation()+"source = "+self.source_().dumpConfig(options)
00481 if self.looper_():
00482 config += options.indentation()+"looper = "+self.looper_().dumpConfig(options)
00483 config+=self._dumpConfigNamedList(self.producers_().iteritems(),
00484 'module',
00485 options)
00486 config+=self._dumpConfigNamedList(self.filters_().iteritems(),
00487 'module',
00488 options)
00489 config+=self._dumpConfigNamedList(self.analyzers_().iteritems(),
00490 'module',
00491 options)
00492 config+=self._dumpConfigNamedList(self.outputModules_().iteritems(),
00493 'module',
00494 options)
00495 config+=self._dumpConfigNamedList(self.sequences_().iteritems(),
00496 'sequence',
00497 options)
00498 config+=self._dumpConfigNamedList(self.paths_().iteritems(),
00499 'path',
00500 options)
00501 config+=self._dumpConfigNamedList(self.endpaths_().iteritems(),
00502 'endpath',
00503 options)
00504 config+=self._dumpConfigUnnamedList(self.services_().iteritems(),
00505 'service',
00506 options)
00507 config+=self._dumpConfigOptionallyNamedList(
00508 self.es_producers_().iteritems(),
00509 'es_module',
00510 options)
00511 config+=self._dumpConfigOptionallyNamedList(
00512 self.es_sources_().iteritems(),
00513 'es_source',
00514 options)
00515 config += self._dumpConfigESPrefers(options)
00516 for name,item in self.psets.iteritems():
00517 config +=options.indentation()+item.configTypeName()+' '+name+' = '+item.configValue(options)
00518 for name,item in self.vpsets.iteritems():
00519 config +=options.indentation()+'VPSet '+name+' = '+item.configValue(options)
00520 if self.schedule:
00521 pathNames = [p.label_() for p in self.schedule]
00522 config +=options.indentation()+'schedule = {'+','.join(pathNames)+'}\n'
00523
00524
00525
00526
00527 config += "}\n"
00528 options.unindent()
00529 return config
00530 def _dumpConfigESPrefers(self, options):
00531 result = ''
00532 for item in self.es_prefers_().itervalues():
00533 result +=options.indentation()+'es_prefer '+item.targetLabel_()+' = '+item.dumpConfig(options)
00534 return result
00535 def _dumpPythonList(self, d, options):
00536 returnValue = ''
00537 if isinstance(d, DictTypes.SortedKeysDict):
00538 for name,item in d.items():
00539 returnValue +='process.'+name+' = '+item.dumpPython(options)+'\n\n'
00540 else:
00541 for name,item in sorted(d.items()):
00542 returnValue +='process.'+name+' = '+item.dumpPython(options)+'\n\n'
00543 return returnValue
00544 def _validateSequence(self, sequence, label):
00545
00546 try:
00547 l = set()
00548 nameVisitor = NodeNameVisitor(l)
00549 sequence.visit(nameVisitor)
00550 except:
00551 raise RuntimeError("An entry in sequence "+label + ' has no label')
00552 def _sequencesInDependencyOrder(self):
00553
00554 returnValue=DictTypes.SortedKeysDict()
00555 dependencies = {}
00556 for label,seq in self.sequences.iteritems():
00557 d = []
00558 v = SequenceVisitor(d)
00559 seq.visit(v)
00560 dependencies[label]=[dep.label_() for dep in d if dep.label_() != None]
00561 resolvedDependencies=True
00562
00563
00564 iterCount = 0
00565 while resolvedDependencies:
00566 iterCount += 1
00567 resolvedDependencies = (0 != len(dependencies))
00568 oldDeps = dict(dependencies)
00569 for label,deps in oldDeps.iteritems():
00570
00571 if len(deps)==0 or iterCount > 100:
00572 iterCount = 0
00573 resolvedDependencies=True
00574 returnValue[label]=self.sequences[label]
00575
00576 del dependencies[label]
00577 for lb2,deps2 in dependencies.iteritems():
00578 while deps2.count(label):
00579 deps2.remove(label)
00580 if len(dependencies):
00581 raise RuntimeError("circular sequence dependency discovered \n"+
00582 ",".join([label for label,junk in dependencies.iteritems()]))
00583 return returnValue
00584 def _dumpPython(self, d, options):
00585 result = ''
00586 for name, value in d.iteritems():
00587 result += value.dumpPythonAs(name,options)+'\n'
00588 return result
00589 def dumpPython(self, options=PrintOptions()):
00590 """return a string containing the equivalent process defined using the configuration language"""
00591 result = "import FWCore.ParameterSet.Config as cms\n\n"
00592 result += "process = cms.Process(\""+self.__name+"\")\n\n"
00593 if self.source_():
00594 result += "process.source = "+self.source_().dumpPython(options)
00595 if self.looper_():
00596 result += "process.looper = "+self.looper_().dumpPython()
00597 result+=self._dumpPythonList(self.producers_(), options)
00598 result+=self._dumpPythonList(self.filters_() , options)
00599 result+=self._dumpPythonList(self.analyzers_(), options)
00600 result+=self._dumpPythonList(self.outputModules_(), options)
00601 result+=self._dumpPythonList(self._sequencesInDependencyOrder(), options)
00602 result+=self._dumpPythonList(self.paths_(), options)
00603 result+=self._dumpPythonList(self.endpaths_(), options)
00604 result+=self._dumpPythonList(self.services_(), options)
00605 result+=self._dumpPythonList(self.es_producers_(), options)
00606 result+=self._dumpPythonList(self.es_sources_(), options)
00607 result+=self._dumpPython(self.es_prefers_(), options)
00608 result+=self._dumpPythonList(self.psets, options)
00609 result+=self._dumpPythonList(self.vpsets, options)
00610 if self.schedule:
00611 pathNames = ['process.'+p.label_() for p in self.schedule]
00612 result +='process.schedule = cms.Schedule('+','.join(pathNames)+')\n'
00613 return result
00614 def _replaceInSequences(self, label, new):
00615 old = getattr(self,label)
00616
00617 for sequenceable in self.sequences.itervalues():
00618 sequenceable.replace(old,new)
00619 for sequenceable in self.paths.itervalues():
00620 sequenceable.replace(old,new)
00621 for sequenceable in self.endpaths.itervalues():
00622 sequenceable.replace(old,new)
00623 def globalReplace(self,label,new):
00624 """ Replace the item with label 'label' by object 'new' in the process and all sequences/paths"""
00625 if not hasattr(self,label):
00626 raise LookupError("process has no item of label "+label)
00627 self._replaceInSequences(label, new)
00628 setattr(self,label,new)
00629 def _insertInto(self, parameterSet, itemDict):
00630 for name,value in itemDict.iteritems():
00631 value.insertInto(parameterSet, name)
00632 def _insertOneInto(self, parameterSet, label, item):
00633 vitems = []
00634 if not item == None:
00635 newlabel = item.nameInProcessDesc_(label)
00636 vitems = [newlabel]
00637 item.insertInto(parameterSet, newlabel)
00638 parameterSet.addVString(True, label, vitems)
00639 def _insertManyInto(self, parameterSet, label, itemDict):
00640 l = []
00641 for name,value in itemDict.iteritems():
00642 newLabel = value.nameInProcessDesc_(name)
00643 l.append(newLabel)
00644 value.insertInto(parameterSet, name)
00645
00646 l.sort()
00647 parameterSet.addVString(True, label, l)
00648 def _insertServices(self, processDesc, itemDict):
00649 for name,value in itemDict.iteritems():
00650 value.insertInto(processDesc)
00651 def _insertPaths(self, processDesc, processPSet):
00652 scheduledPaths = []
00653 triggerPaths = []
00654 endpaths = []
00655 if self.schedule_() == None:
00656
00657 for name,value in self.paths_().iteritems():
00658 scheduledPaths.append(name)
00659 triggerPaths.append(name)
00660 for name,value in self.endpaths_().iteritems():
00661 scheduledPaths.append(name)
00662 endpaths.append(name)
00663 else:
00664 for path in self.schedule_():
00665 pathname = path.label_()
00666 scheduledPaths.append(pathname)
00667 if self.endpaths_().has_key(pathname):
00668 endpaths.append(pathname)
00669 else:
00670 triggerPaths.append(pathname)
00671 processPSet.addVString(True, "@end_paths", endpaths)
00672 processPSet.addVString(True, "@paths", scheduledPaths)
00673
00674 p = processDesc.newPSet()
00675 p.addVString(True, "@trigger_paths", triggerPaths)
00676 processPSet.addPSet(True, "@trigger_paths", p)
00677
00678 pathValidator = PathValidator()
00679 endpathValidator = EndPathValidator()
00680 for triggername in triggerPaths:
00681
00682 self.paths_()[triggername].visit(pathValidator)
00683 self.paths_()[triggername].insertInto(processPSet, triggername, self.__dict__)
00684 for endpathname in endpaths:
00685
00686 self.endpaths_()[endpathname].visit(endpathValidator)
00687 self.endpaths_()[endpathname].insertInto(processPSet, endpathname, self.__dict__)
00688 processPSet.addVString(False, "@filters_on_endpaths", endpathValidator.filtersOnEndpaths)
00689
00690 def prune(self):
00691 """ Remove clutter from the process which we think is unnecessary:
00692 PSets, and unused modules. Not working yet, because I need to remove
00693 all unneeded sequences and paths that contain removed modules """
00694 for name in self.psets_():
00695 delattr(self, name)
00696 for name in self.vpsets_():
00697 delattr(self, name)
00698
00699 if self.schedule_():
00700 self.pruneSequences()
00701 scheduledNames = self.schedule_().moduleNames()
00702 self.pruneModules(self.producers_(), scheduledNames)
00703 self.pruneModules(self.filters_(), scheduledNames)
00704 self.pruneModules(self.analyzers_(), scheduledNames)
00705
00706 def pruneSequences(self):
00707 scheduledSequences = []
00708 visitor = SequenceVisitor(scheduledSequences)
00709
00710
00711
00712
00713
00714
00715
00716 def pruneModules(self, d, scheduledNames):
00717 moduleNames = set(d.keys())
00718 junk = moduleNames - scheduledNames
00719 for name in junk:
00720 delattr(self, name)
00721
00722 def fillProcessDesc(self, processDesc, processPSet):
00723 self.validate()
00724 processPSet.addString(True, "@process_name", self.name_())
00725 all_modules = self.producers_().copy()
00726 all_modules.update(self.filters_())
00727 all_modules.update(self.analyzers_())
00728 all_modules.update(self.outputModules_())
00729 self._insertInto(processPSet, self.psets_())
00730 self._insertInto(processPSet, self.vpsets_())
00731 self._insertManyInto(processPSet, "@all_modules", all_modules)
00732 self._insertOneInto(processPSet, "@all_sources", self.source_())
00733 self._insertOneInto(processPSet, "@all_loopers", self.looper_())
00734 self._insertManyInto(processPSet, "@all_esmodules", self.es_producers_())
00735 self._insertManyInto(processPSet, "@all_essources", self.es_sources_())
00736 self._insertManyInto(processPSet, "@all_esprefers", self.es_prefers_())
00737 self._insertPaths(processDesc, processPSet)
00738 self._insertServices(processDesc, self.services_())
00739 return processDesc
00740
00741 def validate(self):
00742
00743
00744
00745
00746 pass
00747
00748 def prefer(self, esmodule,*args,**kargs):
00749 """Prefer this ES source or producer. The argument can
00750 either be an object label, e.g.,
00751 process.prefer(process.juicerProducer) (not supported yet)
00752 or a name of an ESSource or ESProducer
00753 process.prefer("juicer")
00754 or a type of unnamed ESSource or ESProducer
00755 process.prefer("JuicerProducer")
00756 In addition, you can pass as a labelled arguments the name of the Record you wish to
00757 prefer where the type passed is a cms.vstring and that vstring can contain the
00758 name of the C++ types in the Record which are being preferred, e.g.,
00759 #prefer all data in record 'OrangeRecord' from 'juicer'
00760 process.prefer("juicer", OrangeRecord=cms.vstring())
00761 or
00762 #prefer only "Orange" data in "OrangeRecord" from "juicer"
00763 process.prefer("juicer", OrangeRecord=cms.vstring("Orange"))
00764 or
00765 #prefer only "Orange" data with label "ExtraPulp" in "OrangeRecord" from "juicer"
00766 ESPrefer("ESJuicerProd", OrangeRecord=cms.vstring("Orange/ExtraPulp"))
00767 """
00768
00769 if isinstance(esmodule, ESSource) or isinstance(esmodule, ESProducer):
00770 raise RuntimeError("Syntax of process.prefer(process.esmodule) not supported yet")
00771 elif self._findPreferred(esmodule, self.es_producers_(),*args,**kargs) or \
00772 self._findPreferred(esmodule, self.es_sources_(),*args,**kargs):
00773 pass
00774 else:
00775 raise RuntimeError("Cannot resolve prefer for "+repr(esmodule))
00776
00777 def _findPreferred(self, esname, d,*args,**kargs):
00778
00779 if esname in d:
00780 typ = d[esname].type_()
00781 if typ == esname:
00782 self.__setattr__( esname+"_prefer", ESPrefer(typ,*args,**kargs) )
00783 else:
00784 self.__setattr__( esname+"_prefer", ESPrefer(typ, esname,*args,**kargs) )
00785 return True
00786 else:
00787
00788 found = False
00789 for name, value in d.iteritems():
00790 if value.type_() == esname:
00791 if found:
00792 raise RuntimeError("More than one ES module for "+esname)
00793 found = True
00794 self.__setattr__(esname+"_prefer", ESPrefer(d[esname].type_()) )
00795 return found
00796
00797 class FilteredStream(dict):
00798 """a dictionary with fixed keys"""
00799 def _blocked_attribute(obj):
00800 raise AttributeError, "An FilteredStream defintion cannot be modified after creation."
00801 _blocked_attribute = property(_blocked_attribute)
00802 __setattr__ = __delitem__ = __setitem__ = clear = _blocked_attribute
00803 pop = popitem = setdefault = update = _blocked_attribute
00804 def __new__(cls, *args, **kw):
00805 new = dict.__new__(cls)
00806 dict.__init__(new, *args, **kw)
00807 keys = kw.keys()
00808 keys.sort()
00809 if keys != ['content', 'dataTier', 'name', 'paths', 'responsible', 'selectEvents']:
00810 raise ValueError("The needed parameters are: content, dataTier, name, paths, responsible, selectEvents")
00811 if not isinstance(kw['name'],str):
00812 raise ValueError("name must be of type string")
00813 if not isinstance(kw['content'], vstring) and not isinstance(kw['content'],str):
00814 raise ValueError("content must be of type vstring or string")
00815 if not isinstance(kw['dataTier'], string):
00816 raise ValueError("dataTier must be of type string")
00817 if not isinstance(kw['selectEvents'], PSet):
00818 raise ValueError("selectEvents must be of type PSet")
00819 if not isinstance(kw['paths'],(tuple, Path)):
00820 raise ValueError("'paths' must be a tuple of paths")
00821 return new
00822 def __init__(self, *args, **kw):
00823 pass
00824 def __repr__(self):
00825 return "FilteredStream object: %s" %self["name"]
00826 def __getattr__(self,attr):
00827 return self[attr]
00828
00829
00830 if __name__=="__main__":
00831 import unittest
00832 class TestModuleCommand(unittest.TestCase):
00833 def setUp(self):
00834 """Nothing to do """
00835 None
00836 def testParameterizable(self):
00837 p = _Parameterizable()
00838 self.assertEqual(len(p.parameterNames_()),0)
00839 p.a = int32(1)
00840 self.assert_('a' in p.parameterNames_())
00841 self.assertEqual(p.a.value(), 1)
00842 p.a = 10
00843 self.assertEqual(p.a.value(), 10)
00844 p.a = untracked(int32(1))
00845 self.assertEqual(p.a.value(), 1)
00846 self.failIf(p.a.isTracked())
00847 p.a = untracked.int32(1)
00848 self.assertEqual(p.a.value(), 1)
00849 self.failIf(p.a.isTracked())
00850 p = _Parameterizable(foo=int32(10), bar = untracked(double(1.0)))
00851 self.assertEqual(p.foo.value(), 10)
00852 self.assertEqual(p.bar.value(),1.0)
00853 self.failIf(p.bar.isTracked())
00854 self.assertRaises(TypeError,setattr,(p,'c',1))
00855 p = _Parameterizable(a=PSet(foo=int32(10), bar = untracked(double(1.0))))
00856 self.assertEqual(p.a.foo.value(),10)
00857 self.assertEqual(p.a.bar.value(),1.0)
00858 p.b = untracked(PSet(fii = int32(1)))
00859 self.assertEqual(p.b.fii.value(),1)
00860 self.failIf(p.b.isTracked())
00861
00862 v = int32(10)
00863 p=_Parameterizable(a=v)
00864 v.setValue(11)
00865 self.assertEqual(p.a.value(),11)
00866 p.a = 12
00867 self.assertEqual(p.a.value(),12)
00868 self.assertEqual(v.value(),12)
00869 def testTypedParameterizable(self):
00870 p = _TypedParameterizable("blah", b=int32(1))
00871
00872 other = p.copy()
00873 other.b = 2
00874 self.assertNotEqual(p.b,other.b)
00875
00876 def testProcessInsertion(self):
00877 p = Process("test")
00878 p.a = EDAnalyzer("MyAnalyzer")
00879 self.assert_( 'a' in p.analyzers_() )
00880 self.assert_( 'a' in p.analyzers)
00881 p.add_(Service("MessageLogger"))
00882 self.assert_('MessageLogger' in p.services_())
00883 self.assertEqual(p.MessageLogger.type_(), "MessageLogger")
00884 p.Tracer = Service("Tracer")
00885 self.assert_('Tracer' in p.services_())
00886 self.assertRaises(TypeError, setattr, *(p,'b',"this should fail"))
00887 self.assertRaises(TypeError, setattr, *(p,'bad',Service("MessageLogger")))
00888 self.assertRaises(ValueError, setattr, *(p,'bad',Source("PoolSource")))
00889 p.out = OutputModule("Outer")
00890 self.assertEqual(p.out.type_(), 'Outer')
00891 self.assert_( 'out' in p.outputModules_() )
00892
00893 p.geom = ESSource("GeomProd")
00894 self.assert_('geom' in p.es_sources_())
00895 p.add_(ESSource("ConfigDB"))
00896 self.assert_('ConfigDB' in p.es_sources_())
00897
00898 def testProcessExtend(self):
00899 class FromArg(object):
00900 def __init__(self,*arg,**args):
00901 for name in args.iterkeys():
00902 self.__dict__[name]=args[name]
00903
00904 a=EDAnalyzer("MyAnalyzer")
00905 s1 = Sequence(a)
00906 s2 = Sequence(s1)
00907 s3 = Sequence(s2)
00908 d = FromArg(
00909 a=a,
00910 b=Service("Full"),
00911 c=Path(a),
00912 d=s2,
00913 e=s1,
00914 f=s3,
00915 g=Sequence(s1+s2+s3)
00916 )
00917 p = Process("Test")
00918 p.extend(d)
00919 self.assertEqual(p.a.type_(),"MyAnalyzer")
00920 self.assertRaises(AttributeError,getattr,p,'b')
00921 self.assertEqual(p.Full.type_(),"Full")
00922 self.assertEqual(str(p.c),'a')
00923 self.assertEqual(str(p.d),'a')
00924
00925 def testProcessDumpConfig(self):
00926 p = Process("test")
00927 p.a = EDAnalyzer("MyAnalyzer")
00928 p.p = Path(p.a)
00929 p.s = Sequence(p.a)
00930 p.r = Sequence(p.s)
00931 p.p2 = Path(p.s)
00932 p.schedule = Schedule(p.p2,p.p)
00933 d=p.dumpPython()
00934 self.assertEqual(d,
00935 """import FWCore.ParameterSet.Config as cms
00936
00937 process = cms.Process("test")
00938
00939 process.a = cms.EDAnalyzer("MyAnalyzer")
00940
00941
00942 process.s = cms.Sequence(process.a)
00943
00944
00945 process.r = cms.Sequence(process.s)
00946
00947
00948 process.p = cms.Path(process.a)
00949
00950
00951 process.p2 = cms.Path(process.s)
00952
00953
00954 process.schedule = cms.Schedule(process.p2,process.p)
00955 """)
00956
00957 p = Process("test")
00958 p.a = EDAnalyzer("MyAnalyzer")
00959 p.p = Path(p.a)
00960 p.r = Sequence(p.a)
00961 p.s = Sequence(p.r)
00962 p.p2 = Path(p.r)
00963 p.schedule = Schedule(p.p2,p.p)
00964 p.b = EDAnalyzer("YourAnalyzer")
00965 d=p.dumpPython()
00966 self.assertEqual(d,
00967 """import FWCore.ParameterSet.Config as cms
00968
00969 process = cms.Process("test")
00970
00971 process.a = cms.EDAnalyzer("MyAnalyzer")
00972
00973
00974 process.b = cms.EDAnalyzer("YourAnalyzer")
00975
00976
00977 process.r = cms.Sequence(process.a)
00978
00979
00980 process.s = cms.Sequence(process.r)
00981
00982
00983 process.p = cms.Path(process.a)
00984
00985
00986 process.p2 = cms.Path(process.r)
00987
00988
00989 process.schedule = cms.Schedule(process.p2,process.p)
00990 """)
00991 def testSecSource(self):
00992 p = Process('test')
00993 p.a = SecSource("MySecSource")
00994 self.assertEqual(p.dumpPython().replace('\n',''),'import FWCore.ParameterSet.Config as cmsprocess = cms.Process("test")process.a = cms.SecSource("MySecSource")')
00995
00996 def testGlobalReplace(self):
00997 p = Process('test')
00998 p.a = EDAnalyzer("MyAnalyzer")
00999 p.b = EDAnalyzer("YourAnalyzer")
01000 p.c = EDAnalyzer("OurAnalyzer")
01001 p.s = Sequence(p.a*p.b)
01002 p.p = Path(p.c+p.s+p.a)
01003 new = EDAnalyzer("NewAnalyzer")
01004 p.globalReplace("a",new)
01005
01006 def testSequence(self):
01007 p = Process('test')
01008 p.a = EDAnalyzer("MyAnalyzer")
01009 p.b = EDAnalyzer("YourAnalyzer")
01010 p.c = EDAnalyzer("OurAnalyzer")
01011 p.s = Sequence(p.a*p.b)
01012 self.assertEqual(str(p.s),'a+b')
01013 self.assertEqual(p.s.label_(),'s')
01014 path = Path(p.c+p.s)
01015 self.assertEqual(str(path),'c+a+b')
01016 p._validateSequence(path, 'p1')
01017 notInProcess = EDAnalyzer('NotInProcess')
01018 p2 = Path(p.c+p.s*notInProcess)
01019 self.assertRaises(RuntimeError, p._validateSequence, p2, 'p2')
01020
01021 def testPath(self):
01022 p = Process("test")
01023 p.a = EDAnalyzer("MyAnalyzer")
01024 p.b = EDAnalyzer("YourAnalyzer")
01025 p.c = EDAnalyzer("OurAnalyzer")
01026 path = Path(p.a)
01027 path *= p.b
01028 path += p.c
01029 self.assertEqual(str(path),'a+b+c')
01030 path = Path(p.a*p.b+p.c)
01031 self.assertEqual(str(path),'a+b+c')
01032
01033
01034 path = Path(p.a+ p.b*p.c)
01035 self.assertEqual(str(path),'a+b+c')
01036 path = Path(p.a*(p.b+p.c))
01037 self.assertEqual(str(path),'a+b+c')
01038 path = Path(p.a*(p.b+~p.c))
01039 self.assertEqual(str(path),'a+b+~c')
01040 p.es = ESProducer("AnESProducer")
01041 self.assertRaises(TypeError,Path,p.es)
01042
01043 def testCloneSequence(self):
01044 p = Process("test")
01045 a = EDAnalyzer("MyAnalyzer")
01046 p.a = a
01047 a.setLabel("a")
01048 b = EDAnalyzer("YOurAnalyzer")
01049 p.b = b
01050 b.setLabel("b")
01051 path = Path(a * b)
01052 p.path = Path(p.a*p.b)
01053 lookuptable = {id(a): p.a, id(b): p.b}
01054
01055
01056
01057 self.assertEqual(str(path),str(p.path))
01058
01059 def testSchedule(self):
01060 p = Process("test")
01061 p.a = EDAnalyzer("MyAnalyzer")
01062 p.b = EDAnalyzer("YourAnalyzer")
01063 p.c = EDAnalyzer("OurAnalyzer")
01064 p.path1 = Path(p.a)
01065 p.path2 = Path(p.b)
01066
01067 s = Schedule(p.path1,p.path2)
01068 self.assertEqual(s[0],p.path1)
01069 self.assertEqual(s[1],p.path2)
01070 p.schedule = s
01071 self.assert_('b' in p.schedule.moduleNames())
01072 self.assert_(hasattr(p, 'b'))
01073 self.assert_(hasattr(p, 'c'))
01074 p.prune()
01075 self.assert_('b' in p.schedule.moduleNames())
01076 self.assert_(hasattr(p, 'b'))
01077 self.assert_(not hasattr(p, 'c'))
01078
01079
01080 p = Process("test")
01081 p.a = EDAnalyzer("MyAnalyzer")
01082 path1 = Path(p.a)
01083 s = Schedule(path1)
01084 self.assertRaises(RuntimeError, lambda : p.setSchedule_(s) )
01085
01086
01087 p = Process("test")
01088 p.a = EDAnalyzer("MyAnalyzer")
01089 p.b = EDAnalyzer("MyOtherAnalyzer")
01090 p.c = EDProducer("MyProd")
01091 path1 = Path(p.c*Sequence(p.a+p.b))
01092 s = Schedule(path1)
01093 self.assert_('a' in s.moduleNames())
01094 self.assert_('b' in s.moduleNames())
01095 self.assert_('c' in s.moduleNames())
01096
01097
01098 def testImplicitSchedule(self):
01099 p = Process("test")
01100 p.a = EDAnalyzer("MyAnalyzer")
01101 p.b = EDAnalyzer("YourAnalyzer")
01102 p.c = EDAnalyzer("OurAnalyzer")
01103 p.path1 = Path(p.a)
01104 p.path2 = Path(p.b)
01105 self.assert_(p.schedule is None)
01106 pths = p.paths
01107 keys = pths.keys()
01108 self.assertEqual(pths[keys[0]],p.path1)
01109 self.assertEqual(pths[keys[1]],p.path2)
01110
01111 p = Process("test")
01112 p.a = EDAnalyzer("MyAnalyzer")
01113 p.b = EDAnalyzer("YourAnalyzer")
01114 p.c = EDAnalyzer("OurAnalyzer")
01115 p.path2 = Path(p.b)
01116 p.path1 = Path(p.a)
01117 self.assert_(p.schedule is None)
01118 pths = p.paths
01119 keys = pths.keys()
01120 self.assertEqual(pths[keys[1]],p.path1)
01121 self.assertEqual(pths[keys[0]],p.path2)
01122
01123
01124 def testUsing(self):
01125 p = Process('test')
01126 p.block = PSet(a = int32(1))
01127 p.modu = EDAnalyzer('Analyzer', p.block, b = int32(2))
01128 self.assertEqual(p.modu.a.value(),1)
01129 self.assertEqual(p.modu.b.value(),2)
01130
01131 def testOverride(self):
01132 p = Process('test')
01133 a = EDProducer("A", a1=int32(0))
01134 self.assert_(not a.isModified())
01135 a.a1 = 1
01136 self.assert_(a.isModified())
01137 p.a = a
01138 self.assertEqual(p.a.a1.value(), 1)
01139
01140
01141 p.a = EDProducer("A", a1=int32(2))
01142 self.assertEqual(p.a.a1.value(), 2)
01143
01144
01145
01146 b = EDProducer("A", a1=int32(3))
01147 b.a1 = 4
01148
01149 ps1 = PSet(a = int32(1))
01150 ps2 = PSet(a = int32(2))
01151 self.assertRaises(ValueError, EDProducer, 'C', ps1, ps2)
01152 self.assertRaises(ValueError, EDProducer, 'C', ps1, a=int32(3))
01153
01154 def testExamples(self):
01155 p = Process("Test")
01156 p.source = Source("PoolSource",fileNames = untracked(string("file:reco.root")))
01157 p.foos = EDProducer("FooProducer")
01158 p.bars = EDProducer("BarProducer", foos=InputTag("foos"))
01159 p.out = OutputModule("PoolOutputModule",fileName=untracked(string("file:foos.root")))
01160 p.bars.foos = 'Foosball'
01161 self.assertEqual(p.bars.foos, InputTag('Foosball'))
01162 p.p = Path(p.foos*p.bars)
01163 p.e = EndPath(p.out)
01164 p.add_(Service("MessageLogger"))
01165
01166 def testPrefers(self):
01167 p = Process("Test")
01168 p.add_(ESSource("ForceSource"))
01169 p.juicer = ESProducer("JuicerProducer")
01170 p.prefer("ForceSource")
01171 p.prefer("juicer")
01172 self.assertEqual(p.dumpConfig(),
01173 """process Test = {
01174 es_module juicer = JuicerProducer {
01175 }
01176 es_source = ForceSource {
01177 }
01178 es_prefer = ForceSource {
01179 }
01180 es_prefer juicer = JuicerProducer {
01181 }
01182 }
01183 """)
01184 p.prefer("juicer",fooRcd=vstring("Foo"))
01185 self.assertEqual(p.dumpConfig(),
01186 """process Test = {
01187 es_module juicer = JuicerProducer {
01188 }
01189 es_source = ForceSource {
01190 }
01191 es_prefer = ForceSource {
01192 }
01193 es_prefer juicer = JuicerProducer {
01194 vstring fooRcd = {
01195 'Foo'
01196 }
01197
01198 }
01199 }
01200 """)
01201 self.assertEqual(p.dumpPython(),
01202 """import FWCore.ParameterSet.Config as cms
01203
01204 process = cms.Process("Test")
01205
01206 process.juicer = cms.ESProducer("JuicerProducer")
01207
01208
01209 process.ForceSource = cms.ESSource("ForceSource")
01210
01211
01212 process.prefer("ForceSource")
01213
01214 process.prefer("juicer",
01215 fooRcd = cms.vstring('Foo')
01216 )
01217
01218 """)
01219
01220 def testFreeze(self):
01221 process = Process("Freeze")
01222 m = EDProducer("M", p=PSet(i = int32(1)))
01223 m.p.i = 2
01224 process.m = m
01225
01226
01227
01228
01229
01230
01231 process.m.p.i = 4
01232 self.assertEqual(process.m.p.i.value(), 4)
01233 process.m.p = PSet(j=int32(1))
01234
01235 m2 = m.clone(p = PSet(i = int32(5)), j = int32(8))
01236 m2.p.i = 6
01237 m2.j = 8
01238
01239
01240 unittest.main()