├── extconfigobj └── _version.py ├── Simulator ├── __init__.py ├── userSimCheck.py ├── simCheck.py ├── simulatorFactory.py ├── incisiveInterface.py ├── xceliumInterface.py ├── vcsInterface.py └── simulatorInterface.py ├── .travis.yml ├── .gitignore ├── hashing.py ├── __init__.py ├── utils.py ├── exceptions.py ├── lsfCli.py ├── about.py ├── globals.py ├── color_printer.py ├── test_list.py ├── userCli.py ├── tbInfo.py ├── database.py ├── baseCfg.py ├── testCaseSuite.py ├── groupCfg.py ├── readCfgFile.py ├── README.md ├── yasaCli.py ├── yasaTop.py ├── ostools.py ├── LICENSE ├── test_report.py ├── test_runner.py └── compileBuild.py /extconfigobj/_version.py: -------------------------------------------------------------------------------- 1 | """Project version""" 2 | __version__ = '5.1.0' 3 | -------------------------------------------------------------------------------- /Simulator/__init__.py: -------------------------------------------------------------------------------- 1 | __api__ = [ 2 | 'simulatorFactory', 3 | 'simulatorInterface', 4 | 'vcsInterface', 5 | 'incisiveInterface', 6 | 'simCheck', 7 | 'vcsSimCheck', 8 | 'irunSimCheck' 9 | ] 10 | __all__ = __api__ 11 | -------------------------------------------------------------------------------- /Simulator/userSimCheck.py: -------------------------------------------------------------------------------- 1 | import re 2 | from Simulator.vcsInterface import vcsSimCheck 3 | class userSimCheck(vcsSimCheck): 4 | regModelWarnPattern = r'UVM_WARNTNG .+: reporter \[UVM\/RSRC\/NOREGEX\] a resource with meta characters in the field name has been created.*' 5 | cevaTestFailPattern = r'^TEST FAILED' 6 | cevaReportPattern = r'.*SIMULATION SUMMARY.*' 7 | 8 | def __init__(self): 9 | super (userSimCheck, self).__init__() 10 | self.setExcludeWarnPatterns(userSimCheck.regModelWarnPattern) 11 | self.setErrPatterns(userSimCheck.cevaTestFailPattern) 12 | self.setEndFlagPatterns(userSimCheck.cevaReportPattern) 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.6" 4 | 5 | # command to install dependencies 6 | install: 7 | - pip3 install psutil 8 | 9 | before_script: 10 | - git clone https://github.com/zhajio1988/uvm_candy_lover.git 11 | - cd uvm_candy_lover/bin 12 | - git clone https://github.com/zhajio1988/YASA.git 13 | - cd ../ 14 | - touch bin/vcs 15 | - chmod +x bin/vcs 16 | - export PATH="./bin":$PATH 17 | - export TEMP_ROOT=`pwd` 18 | - source bin/bootenv.sh 19 | 20 | # command to run tests 21 | script: 22 | - python3 $PRJ_HOME/bin/YASA/yasaTop.py -h 23 | - python3 $PRJ_HOME/bin/YASA/yasaTop.py -doc 24 | - python3 $PRJ_HOME/bin/YASA/yasaTop.py -show test 25 | - python3 $PRJ_HOME/bin/YASA/yasaTop.py -show build 26 | - python3 $PRJ_HOME/bin/YASA/yasaTop.py -show group 27 | 28 | after_script: 29 | - rm -f bin/vcs 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | venv/ 48 | .python-version 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | 57 | # Sphinx documentation 58 | docs/_build/ 59 | 60 | # PyBuilder 61 | target/ 62 | 63 | #Ipython Notebook 64 | .ipynb_checkpoints 65 | 66 | # Intellij IDEA files 67 | .idea/* 68 | *.iml 69 | .vscode 70 | -------------------------------------------------------------------------------- /hashing.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | # Copyright (c) 2014-2018, Lars Asplund lars.anders.asplund@gmail.com 19 | 20 | """ 21 | Wrapper arround selected hash method 22 | """ 23 | 24 | import hashlib 25 | 26 | 27 | def hash_string(string): 28 | """ 29 | returns hash of bytes 30 | """ 31 | return hashlib.sha1(string.encode(encoding="utf-8")).hexdigest() 32 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | # Copyright (c) 2014-2018, Lars Asplund lars.anders.asplund@gmail.com 19 | """ 20 | Public YASA interface 21 | """ 22 | from os.path import dirname, join, abspath 23 | from .about import version, doc 24 | 25 | # Repository root 26 | ROOT = abspath(join(dirname(__file__), "..")) 27 | 28 | __version__ = version() 29 | __doc__ = doc() # pylint: disable=redefined-builtin 30 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | import time 4 | import argparse 5 | 6 | """ 7 | utils functions 8 | """ 9 | 10 | def createDir(path, force = False): 11 | if force and os.path.exists(path): 12 | shutil.rmtree(path, True) 13 | if not os.path.exists(path): 14 | os.makedirs(path) 15 | 16 | def expandDirVar(dir): 17 | dir = os.path.expandvars(dir) 18 | if '$' in dir: 19 | raise EnvironmentError('Environment Var in %s is unknown' % dir) 20 | return dir 21 | 22 | def parseKwargs(obj, key ,default, **kwargs): 23 | if key in kwargs and kwargs[key] != 'default': 24 | setattr(obj, '_' + key, kwargs[key]) 25 | else: 26 | setattr(obj, '_' + key, default) 27 | 28 | def appendAttr(obj, k, v): 29 | if hasattr(obj, k): 30 | if isinstance(v, list) and isinstance(getattr(obj, k), list): 31 | setattr(obj, k, getattr(obj, k)+v) 32 | elif isinstance(getattr(obj, k), list): 33 | getattr(obj, k).append(v) 34 | elif isinstance(v, list): 35 | setattr(obj, k, v.insert(0, getattr(obj, k))) 36 | else: 37 | setattr(obj, k, [getattr(obj, k), v]) 38 | else: 39 | if isinstance(v, list): 40 | setattr(obj, k, v) 41 | else: 42 | setattr(obj, k, [v]) 43 | 44 | def positive_int(val): 45 | """ 46 | ArgumentParse positive int check 47 | """ 48 | try: 49 | ival = int(val) 50 | assert ival > 0 51 | return ival 52 | except (ValueError, AssertionError): 53 | raise argparse.ArgumentTypeError("'%s' is not a valid positive int" % val) 54 | -------------------------------------------------------------------------------- /exceptions.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | # Copyright (c) 2014-2018, Lars Asplund lars.anders.asplund@gmail.com 19 | 20 | """ 21 | Contains exceptions which are globally known 22 | """ 23 | 24 | class CompileError(Exception): 25 | """ 26 | An error occured when compiling a HDL file 27 | """ 28 | 29 | class TestcaseUnknown(Exception): 30 | def __init__(self, testCase): 31 | super(TestcaseUnknown, self).__init__() 32 | self.errorinfo = '%s is unknown!' % testCase 33 | 34 | def __str__(self): 35 | return self.errorinfo 36 | 37 | class buildUnknown(TestcaseUnknown): 38 | def __init__(self, build): 39 | super(buildUnknown, self).__init__(build) 40 | 41 | class groupUnknown(TestcaseUnknown): 42 | def __init__(self, group): 43 | super(groupUnknown, self).__init__(group) 44 | -------------------------------------------------------------------------------- /lsfCli.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | import argparse 19 | from utils import * 20 | 21 | class queueArgsAction(argparse.Action): 22 | def __call__(self, parser, args, values, option = None): 23 | args.lsfQueue = values 24 | if args.lsfQueue: 25 | appendAttr(args, 'lsfOptions', '-q %s' % args.lsfQueue) 26 | 27 | class rusageArgsAction(argparse.Action): 28 | def __call__(self, parser, args, values, option = None): 29 | args.lsfRusage = values 30 | if args.lsfRusage: 31 | appendAttr(args, 'lsfOptions', '-R %s' % args.lsfRusage) 32 | 33 | class jobNameArgsAction(argparse.Action): 34 | def __call__(self, parser, args, values, option = None): 35 | args.lsfJobName = values 36 | if args.lsfJobName: 37 | appendAttr(args, 'lsfOptions', '-J %s' % args.lsfJobName) 38 | 39 | class lsfCli(object): 40 | def __init__(self, parser=None): 41 | self.subParser = parser 42 | self.args = None 43 | 44 | def addArguments(self): 45 | lsfSubParser = self.subParser.add_parser('lsf') 46 | lsfSubParser.add_argument('-queue', dest='lsfQueue', action=queueArgsAction, 47 | help='Submits job to specified queues.') 48 | 49 | lsfSubParser.add_argument('-rusage', dest='lsfRusage', action=rusageArgsAction, 50 | help='Specifies host resource requirements.') 51 | 52 | lsfSubParser.add_argument('-job_name', dest='lsfJobName', action=jobNameArgsAction, 53 | help='Assigns the specified name to the job.') 54 | 55 | def setParsedArgs(self, parsedArgs): 56 | self.args = parsedArgs 57 | appendAttr(self.args, 'lsfOptions', '') 58 | -------------------------------------------------------------------------------- /Simulator/simCheck.py: -------------------------------------------------------------------------------- 1 | import re 2 | class simCheck(object): 3 | uvmErrorPattern = r'^.*UVM_((ERROR)|(FATAL)) .*\@.*:' 4 | uvmWarningPattern = r'^.*UVM_WARNING .*\@.*:' 5 | uvmReportPattern = r'^--- UVM Report Summary ---' 6 | errorTaskPattern = r'^Error.+:' 7 | 8 | def __init__(self): 9 | super(simCheck, self).__init__() 10 | self._errPatterns = [] 11 | self._excludeErrPatterns = [] 12 | self.setErrPatterns(simCheck.uvmErrorPattern) 13 | self.setErrPatterns(simCheck.errorTaskPattern) 14 | self._warnPatterns = [] 15 | self._excludeWarnPatterns = [] 16 | self.setWarnPatterns(simCheck.uvmWarningPattern) 17 | self._endFlagPatterns = [] 18 | self.setEndFlagPatterns(simCheck.uvmReportPattern) 19 | self._failStatus = '' 20 | self._reasonMsg = '' 21 | self._endFlagHit = False 22 | self._simEndPattern = None 23 | 24 | def resetStatus(self): 25 | self._failStatus = '' 26 | self._reasonMsg = '' 27 | self._endFlagHit = False 28 | 29 | @property 30 | def status (self): 31 | if self._failStatus: 32 | return self._failStatus, self._reasonMsg 33 | elif not self._endFlagHit: 34 | self._failStatus = 'UNKNOWN®' 35 | self._reasonMsg = 'No Simulation End Flag!' 36 | return self._failStatus, self._reasonMsg 37 | else: 38 | return 'PASS', '' 39 | 40 | def setErrPatterns(self, pattern): 41 | self._errPatterns.append(re.compile(pattern)) 42 | 43 | def setWarnPatterns(self, pattern): 44 | self._warnPatterns.append(re.compile(pattern)) 45 | 46 | def setExcludeErrPatterns(self, pattern): 47 | self._excludeErrPatterns.append(re.compile(pattern)) 48 | 49 | def setExcludeWarnPatterns(self, pattern): 50 | self._excludeWarnPatterns.append(re.compile(pattern)) 51 | 52 | def setEndFlagPatterns(self, pattern): 53 | self._endFlagPatterns.append(re.compile(pattern)) 54 | 55 | def check(self, string): 56 | for errPattern in self._errPatterns: 57 | if errPattern.match(string): 58 | excluded = filter(lambda x: x.match(string), self._excludeErrPatterns) 59 | if not list(excluded): 60 | if self._failStatus != 'FAIL': 61 | self._failStatus = 'FAIL' 62 | self._reasonMsg = string 63 | 64 | for warnPattern in self._warnPatterns: 65 | if warnPattern.match(string): 66 | excluded = filter(lambda x:x.match(string), self._excludeWarnPatterns) 67 | if not list(excluded): 68 | if not self._failStatus: 69 | self._failStatus = 'WARN' 70 | self._reasonMsg = string 71 | 72 | for endFlagPattern in self._endFlagPatterns: 73 | if endFlagPattern.match(string) : 74 | self._endFlagHit = True 75 | 76 | if self._simEndPattern.match(string): 77 | return True 78 | -------------------------------------------------------------------------------- /about.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | 19 | """ 20 | Provides documentation and version information 21 | """ 22 | def license_text(): 23 | """ 24 | Returns licence text 25 | """ 26 | return """YASA 27 | ----- 28 | 29 | YASA is released under the terms of Apache License, Version 2.0. 30 | 31 | Copyright (c) 2019, XtremeDV. Jude Zhang All rights reserved. 32 | 33 | uvm 34 | ----- 35 | uvm is the Universal Verification Methodology (UVM) reference implementation 36 | from Accellera. 37 | 38 | vcs 39 | ----- 40 | VCS is a product of Synopsys, Inc. 41 | Copyright 2003-2013 Synopsys, Inc. All Rights Reserved 42 | You are licensed only to use thise products 43 | for which you have lawfully obtained a valid license key. 44 | 45 | irun/xrun 46 | ----- 47 | Incisive/Xcelium is a product of Cadence Design Systems, Inc. 48 | (c) Copyright 1995-2014 Cadence Design Systems, Inc. All Rights Reserved 49 | You are licensed only to use thise products 50 | for which you have lawfully obtained a valid license key. 51 | """ 52 | 53 | def doc(): 54 | """ 55 | Returns short introduction to YASA 56 | """ 57 | return r"""What is YASA? 58 | ============== 59 | 60 | YASA is an open source simulation framework for SystemVerilog/UVM testbentch 61 | released under the terms of Apache License, v. 2.0. 62 | It support mutli_simulators, multi_languages, lsf etc. 63 | It support several excellent features. Such as: 64 | customized command line option, add any compilation options or simulation options, 65 | running a testcase with random seeds for several rounds or running a group of 66 | testcases, each testcase has several command line option. 67 | 68 | Typical Usage: 69 | %> python3 yasaTop.py -h 70 | %> python3 yasaTop.py -doc 71 | %> python3 yasaTop.py -version 72 | %> python3 yasaTop.py -t sanity1 -co 73 | %> python3 yasaTop.py -t sanity1 -r 5 74 | %> python3 yasaTop.py -t sanity1 -seed 352938188 75 | %> python3 yasaTop.py -t sanity1 -seed 352938188 -so 76 | %> python3 yasaTop.py -g top_smoke -co 77 | %> python3 yasaTop.py -g top_smoke -p 5 78 | 79 | 80 | License 81 | ======= 82 | """ + license_text() 83 | 84 | 85 | def version(): 86 | """ 87 | Returns YASA version 88 | """ 89 | if PRE_RELEASE: 90 | return '%i.%i.%irc0' % (VERSION[0], VERSION[1], VERSION[2] + 1) 91 | 92 | return '%i.%i.%i' % (VERSION[0], VERSION[1], VERSION[2]) 93 | 94 | 95 | VERSION = (1, 0, 0) 96 | 97 | # DO NOT TOUCH: Only set to False by PyPI deployment script 98 | PRE_RELEASE = False 99 | -------------------------------------------------------------------------------- /Simulator/simulatorFactory.py: -------------------------------------------------------------------------------- 1 | """ 2 | Create simulator instances 3 | """ 4 | 5 | import os 6 | from .vcsInterface import vcsInterface 7 | from .incisiveInterface import incisiveInterface 8 | from .xceliumInterface import xceliumInterface 9 | #from .simulatorInterface import (BooleanOption, ListOfStringOption) 10 | 11 | class simulatorFactory(object): 12 | """ 13 | Create simulator instances 14 | """ 15 | 16 | @staticmethod 17 | def supported_simulators(): 18 | """ 19 | Return a list of supported simulator classes 20 | """ 21 | #TODO: add simulator interface here 22 | return [vcsInterface, 23 | incisiveInterface, 24 | xceliumInterface, 25 | ] 26 | 27 | def select_simulator(self): 28 | """ 29 | Select simulator class, either from YASA_SIMULATOR environment variable 30 | or the first available 31 | """ 32 | available_simulators = self._detect_available_simulators() 33 | name_mapping = {simulator_class.name: simulator_class for simulator_class in self.supported_simulators()} 34 | if not available_simulators: 35 | raise RuntimeError( 36 | ("Don't have available simulators. " 37 | "Supported simulators are %s") 38 | % list(name_mapping.keys())) 39 | environ_name = "YASA_SIMULATOR" 40 | if environ_name in os.environ: 41 | simulator_name = os.environ[environ_name] 42 | if simulator_name not in name_mapping: 43 | raise RuntimeError( 44 | ("Simulator from " + environ_name + " environment variable %r is not supported. " 45 | "Supported simulators are %r") 46 | % (simulator_name, name_mapping.keys())) 47 | simulator_class = name_mapping[simulator_name] 48 | else: 49 | simulator_class = available_simulators[0] 50 | 51 | return simulator_class 52 | 53 | def add_arguments(self, parser, group): 54 | """ 55 | Add command line arguments to parser 56 | """ 57 | #TODO: Can add global simulator arguments here 58 | #parser.add_argument('-g', '--gui', 59 | # action="store_true", 60 | # default=False, 61 | # help=("Open test case(s) in simulator gui with top level pre loaded")) 62 | environ_name = "YASA_SIMULATOR" 63 | if environ_name in os.environ: 64 | simulator_name = os.environ[environ_name] 65 | for sim in self.supported_simulators(): 66 | if sim.name == simulator_name: 67 | sim.add_arguments(parser, group) 68 | #simulator = self.select_simulator() 69 | #simulator.add_arguments(parser, group) 70 | 71 | def __init__(self): 72 | pass 73 | #self._compile_options = self._extract_compile_options() 74 | #self._sim_options = self._extract_sim_options() 75 | 76 | def _detect_available_simulators(self): 77 | """ 78 | Detect available simulators and return a list 79 | """ 80 | return [simulator_class 81 | for simulator_class in self.supported_simulators() 82 | if simulator_class.is_available()] 83 | 84 | @property 85 | def has_simulator(self): 86 | return bool(self._detect_available_simulators()) 87 | 88 | 89 | SIMULATOR_FACTORY = simulatorFactory() 90 | -------------------------------------------------------------------------------- /globals.py: -------------------------------------------------------------------------------- 1 | import os 2 | import grp 3 | 4 | def checkEnv(): 5 | if not 'YASA_SIMULATOR' in os.environ: 6 | raise EnvironmentError('$YASA_SIMULATOR is not defined') 7 | elif not os.environ['YASA_SIMULATOR'] in ['vcs','irun','xrun']: 8 | raise EnvironmentError('$YASA_SIMULATOR=%s is not inside supported tools["vcs", "irun", "xrun"]' % os.environ['YASA_SIMULATOR']) 9 | if not 'PRJ_HOME' in os.environ: 10 | raise EnvironmentError('$PRJ_HOME is not defined') 11 | if not 'TEMP_ROOT' in os.environ: 12 | raise EnvironmentError('$TEMP_ROOT is not defined') 13 | 14 | checkEnv() 15 | 16 | def defaultCliCfgFile(): 17 | defaultCliCfgFile = os.path.join(os.environ['PRJ_HOME'], 'bin', os.environ['YASA_SIMULATOR']+'_cfg', 'userCli.cfg') 18 | if not os.path.exists(defaultCliCfgFile): 19 | raise EnvironmentError('%s file not exists' % defaultCliCfgFile) 20 | else: 21 | return defaultCliCfgFile 22 | 23 | def defaultBuildFile(): 24 | defaultBuildFile = os.path.join(os.environ['PRJ_HOME'], 'bin', os.environ['YASA_SIMULATOR']+'_cfg', 'build.cfg') 25 | if not os.path.exists(defaultBuildFile): 26 | raise EnvironmentError('%s file not exists' % defaultBuildFile) 27 | else: 28 | return defaultBuildFile 29 | 30 | def defaultGroupFile(): 31 | defaultGroupFile = os.path.join(os.environ['PRJ_HOME'], 'bin', os.environ['YASA_SIMULATOR']+'_cfg', 'group.cfg') 32 | if not os.path.exists(defaultGroupFile): 33 | raise EnvironmentError('%s file not exists' % defaultGroupFile) 34 | else: 35 | return defaultGroupFile 36 | 37 | def defaultTestListFile(): 38 | return 'test.f' 39 | 40 | def defaultCovDir(): 41 | return 'coverage' 42 | 43 | def defaultYasaDir(): 44 | if 'YASA_HOME' in os.environ: 45 | return os.environ['YASA_HOME'] 46 | else : 47 | return os.path.join(os.environ['PRJ_HOME'], 'bin', 'YASA') 48 | 49 | def defautlVplanDir(): 50 | return os.path.join(os.environ['PRJ_HOME'], 'etc', 'vplan') 51 | 52 | 53 | def defaultTestDir(): 54 | if 'TEST_DIR' in os.environ: 55 | return os.environ['TEST_DIR'] 56 | else: 57 | return os.path.join(os.environ['PRJ_HOME'], 'testcases') 58 | 59 | 60 | def defaultWorkDir(): 61 | if 'WORK_DIR' in os.environ: 62 | return os.environ['WORK_DIR'] 63 | else: 64 | return os.path.join(defaultWorkPrjDir(), 'work') 65 | 66 | 67 | def defaultReportDir(): 68 | if 'REPORT_DIR' in os.environ: 69 | return os.environ['REPORT_DIR'] 70 | else: 71 | return os.path.join(defaultWorkPrjDir(), 'report') 72 | 73 | def userSimCheck(): 74 | userSimCheckFile = os.path.join(os.environ['PRJ_HOME'], 'bin', os.environ['YASA_SIMULATOR']+'_cfg', 'userSimCheck.py') 75 | if os.path.isfile(userSimCheckFile): 76 | return ('userSimCheck', userSimCheckFile) 77 | return (None, None) 78 | 79 | 80 | def defaultWorkDir(): 81 | # if os.environ['USER'] in grp.getgrnam('sg-ic-ipdv').gr_mem: 82 | # return os.path.join('/ic/temp/ipdv', os.environ['USER'], os.path.basename(os.environ['PRJ_HOME'])) 83 | # elif os.environ['USER'] in grp.getgrnam('sg-ic-soc').gr_mem: 84 | # return os.path.join('/ic/temp/fe', os.environ['USER'], os.path.basename(os.environ['PRJ_HOME'])) 85 | # else : raise SystemError('You are supposed to be in sg-ic-ipdv, sg-ic-soc, sg-ic-fpga or sg-ic-socdv group, but you are not !') 86 | 87 | return os.path.join(os.environ['TEMP_ROOT'], os.path.basename(os.environ['PRJ_HOME'])) 88 | -------------------------------------------------------------------------------- /color_printer.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | # Copyright (c) 2014-2018, Lars Asplund lars.anders.asplund@gmail.com 19 | """ 20 | Provides capability to print in color to the terminal in both Windows and Linux. 21 | """ 22 | 23 | import sys 24 | 25 | class LinuxColorPrinter(object): 26 | """ 27 | Print in color on linux 28 | """ 29 | 30 | BLUE = 'b' 31 | GREEN = 'g' 32 | RED = 'r' 33 | INTENSITY = 'i' 34 | WHITE = RED + GREEN + BLUE 35 | 36 | def __init__(self): 37 | pass 38 | 39 | def write(self, text, output_file=None, fg=None, bg=None): 40 | """ 41 | Print the text in color to the output_file 42 | uses stdout if output_file is None 43 | """ 44 | if output_file is None: 45 | output_file = sys.stdout 46 | 47 | text = self._ansi_wrap(text, fg, bg) 48 | output_file.write(text) 49 | 50 | @staticmethod 51 | def _to_code(rgb): 52 | """ 53 | Translate strings containing 'rgb' characters to numerical color codes 54 | """ 55 | code = 0 56 | if 'r' in rgb: 57 | code += 1 58 | 59 | if 'g' in rgb: 60 | code += 2 61 | 62 | if 'b' in rgb: 63 | code += 4 64 | return code 65 | 66 | def _ansi_wrap(self, text, fg, bg): 67 | """ 68 | Wrap the text into ANSI color escape codes 69 | fg -- the foreground color 70 | bg -- the background color 71 | """ 72 | codes = [] 73 | 74 | if fg is not None: 75 | codes.append(30 + self._to_code(fg)) 76 | 77 | if bg is not None: 78 | codes.append(40 + self._to_code(bg)) 79 | 80 | if fg is not None and 'i' in fg: 81 | codes.append(1) # Bold 82 | 83 | if bg is not None and 'i' in bg: 84 | codes.append(4) # Underscore 85 | 86 | return "\033[" + ";".join([str(code) for code in codes]) + "m" + text + "\033[0m" 87 | 88 | 89 | class NoColorPrinter(object): 90 | """ 91 | Dummy printer that does not print in color 92 | """ 93 | def __init__(self): 94 | pass 95 | 96 | @staticmethod 97 | def write(text, output_file=None, fg=None, bg=None): # pylint: disable=unused-argument 98 | """ 99 | Print the text in color to the output_file 100 | uses stdout if output_file is None 101 | """ 102 | if output_file is None: 103 | output_file = sys.stdout 104 | output_file.write(text) 105 | 106 | 107 | NO_COLOR_PRINTER = NoColorPrinter() 108 | COLOR_PRINTER = LinuxColorPrinter() 109 | -------------------------------------------------------------------------------- /test_list.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | """ 19 | Functionality to handle lists of test suites and filtering of them 20 | """ 21 | 22 | from test_report import (PASSED, WARNED, FAILED) 23 | 24 | 25 | class TestList(object): 26 | """ 27 | A list of test suites 28 | """ 29 | def __init__(self): 30 | self._test_suites = [] 31 | 32 | def add_suite(self, test_suite): 33 | self._test_suites.append(test_suite) 34 | 35 | def add_test(self, test_case): 36 | """ 37 | Add a single test that is automatically wrapped into a test suite 38 | """ 39 | test_suite = TestSuiteWrapper(test_case) 40 | self._test_suites.append(test_suite) 41 | 42 | def keep_matches(self, test_filter): 43 | """ 44 | Keep only testcases matching any pattern 45 | """ 46 | self._test_suites = [test for test in self._test_suites 47 | if test.keep_matches(test_filter)] 48 | 49 | @property 50 | def num_tests(self): 51 | """ 52 | Return the number of tests within 53 | """ 54 | num_tests = 0 55 | for test_suite in self: 56 | num_tests += len(test_suite.test_names) 57 | return num_tests 58 | 59 | @property 60 | def test_names(self): 61 | """ 62 | Return the names of all tests 63 | """ 64 | names = [] 65 | for test_suite in self: 66 | names += test_suite.test_names 67 | return names 68 | 69 | def __iter__(self): 70 | return iter(self._test_suites) 71 | 72 | def __len__(self): 73 | return len(self._test_suites) 74 | 75 | def __getitem__(self, idx): 76 | return self._test_suites[idx] 77 | 78 | 79 | class TestSuiteWrapper(object): 80 | """ 81 | Wrapper which creates a test suite from a single test case 82 | """ 83 | def __init__(self, test_case): 84 | self._test_case = test_case 85 | 86 | @property 87 | def test_names(self): 88 | return [self._test_case.name] 89 | 90 | @property 91 | def name(self): 92 | return self._test_case.name 93 | 94 | @property 95 | def test_result_file(self): 96 | return self._test_case.test_result_file 97 | 98 | @property 99 | def test_information(self): 100 | return {self.name: self._test_case.test_information} 101 | 102 | def keep_matches(self, test_filter): 103 | return test_filter(name=self._test_case.name, 104 | attribute_names=self._test_case.attribute_names) 105 | 106 | def run(self, *args, **kwargs): 107 | """ 108 | Run the test suite and return the test results for all test cases 109 | """ 110 | test_ok = self._test_case.run() 111 | return test_ok 112 | 113 | #return {self._test_case.name: PASSED if test_ok else FAILED} 114 | -------------------------------------------------------------------------------- /userCli.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | from extconfigobj import ConfigObj, ConfigObjError, Section 19 | from globals import * 20 | 21 | class userCli(object): 22 | def __init__(self, parser=None, ini_file=None): 23 | self.parser = parser 24 | self.kwargs = {} 25 | self.args = None 26 | self.config = ConfigObj(infile=defaultCliCfgFile(), stringify=True) 27 | self.section = self.userCliSection 28 | 29 | @property 30 | def userCliSection(self): 31 | if 'userCli' in self.config: 32 | self.section = self.config['userCli'] 33 | return self.section 34 | else: 35 | raise AttributeError('userCli section is not defined in %s' % defaultCliCfgFile()) 36 | 37 | def setParsedArgs(self, parsedArgs): 38 | self.args = parsedArgs 39 | 40 | def compileOption(self): 41 | args = self.args; 42 | argsList = [] 43 | keyVar = '' 44 | for key in self.section: 45 | keyVar = key.replace('$', '') if not key.find('$') else key 46 | if hasattr(args, keyVar) and getattr(args, keyVar): 47 | for k, v in self.section[key].items(): 48 | if '$' in v and '$' in key: 49 | v = v.replace(key, getattr(args, keyVar)) 50 | if 'compile_option' == k: 51 | argsList = argsList + v if isinstance(v, list) else argsList + [v] 52 | return argsList 53 | 54 | def simOption(self): 55 | args = self.args; 56 | argsList = [] 57 | keyVar = '' 58 | for key in self.section: 59 | keyVar = key.replace('$', '') if not key.find('$') else key 60 | if hasattr(args, keyVar) and getattr(args, keyVar): 61 | for k, v in self.section[key].items(): 62 | if '$' in v and '$' in key: 63 | v = v.replace(key, getattr(args, keyVar)) 64 | if 'sim_option' == k: 65 | argsList = argsList + v if isinstance(v, list) else argsList + [v] 66 | return argsList 67 | 68 | def addArguments(self): 69 | keyVar = '' 70 | action = '' 71 | for key in self.section: 72 | if not key.find('$'): 73 | self.kwargs['default'] = 'rand' 74 | keyVar = key.replace('$', '') if not key.find('$') else key 75 | action = 'store' if not key.find('$') else 'store_true' 76 | self.kwargs = {} 77 | self.kwargs['dest'] = keyVar 78 | self.kwargs['action'] = action 79 | if self.section.inline_comments[key]: 80 | self.kwargs['help'] = self.section.inline_comments[key].replace('#', '') 81 | else: 82 | self.kwargs['help'] = 'user defined option' 83 | 84 | self.parser.add_argument('-%s' % keyVar, **self.kwargs) 85 | 86 | #if __name__ == '__main__': 87 | # import argparse 88 | # import sys 89 | # 90 | # #print(vars(userCli.userCliSection())) 91 | # #userCli.userCliSection.walk(userCli.add_arguments()) 92 | # parser = argparse.ArgumentParser() 93 | # userCli = userCli(parser) 94 | # userCli.addArguments() 95 | # args = parser.parse_args(sys.argv[1:]) 96 | # userCli.setParsedArgs(args) 97 | # print(args) 98 | # print(args.prof) 99 | # print(args.vh) 100 | # print(args.wave_name) 101 | # print(userCli.compileOption()) 102 | # print(userCli.simOption()) 103 | # #print(args.sim_option) 104 | -------------------------------------------------------------------------------- /tbInfo.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | import os 19 | import sys 20 | from utils import * 21 | from globals import * 22 | from readCfgFile import readGroupCfgFile, readBuildCfgFile 23 | 24 | class flowList(object): 25 | def __init__ (self, name): 26 | self._name = name 27 | 28 | @property 29 | def name(self): 30 | return self._name 31 | 32 | @property 33 | def list(self): 34 | pass 35 | 36 | @list.setter 37 | def list(self,value): 38 | pass 39 | 40 | def check(self, value): 41 | return value in self.list 42 | 43 | def show(self): 44 | print ('Available '+self.name+':\n' + '\n'.join(['\t' + x for x in self.list])) 45 | 46 | 47 | class testList(flowList): 48 | def __init__ (self): 49 | super(testList,self).__init__('Tests') 50 | self._testDir=defaultTestDir() 51 | self._testlist = {} 52 | 53 | @property 54 | def list(self): 55 | return self._testlist.keys() 56 | 57 | @property 58 | def testDir(self): 59 | return self._testDir 60 | 61 | @testDir.setter 62 | def testDir(self, value): 63 | if not os.path.isdir(expandDirVar(value)): 64 | raise SystemError('%s is not exists!' % value) 65 | self._testDir = expandDirVar(value) 66 | self._testlist = {} 67 | self._getTestList() 68 | 69 | def genTestFileList(self, dir): 70 | with open(os.path.join(dir,defaultTestListFile()), 'w') as file: 71 | file.write('+incdir+' + self._testDir + '\n') 72 | for test in self._testlist.values(): 73 | file.write('+incdir+' + test + '\n') 74 | file.write('+incdir+' + os.path.join(test, '..') + '\n') 75 | file.write(os.path.join(test, os.path.basename(test) + '.sv') + '\n') 76 | 77 | def _getTestList(self): 78 | for dirpath, dirname, filename in os.walk(self._testDir, topdown=True, followlinks=True): 79 | if '.svn' in dirname: 80 | dirname.remove('.svn') 81 | for file in filename: 82 | basename, extname = os.path.splitext(file) 83 | if extname == '.sv' and basename == os.path.basename(dirpath): 84 | self._testlist[basename] = dirpath 85 | 86 | class allTestList(object): 87 | def __init__ (self): 88 | self._testlists={} 89 | 90 | def setTestLists(self, build, testlist): 91 | self._testlists[build] = testlist 92 | 93 | def show(self): 94 | if self._testlists: 95 | print ('Available tests:') 96 | for key, value in self._testlists.items(): 97 | print('\t'+ key + ':\n' + '\n'.join(['\t\t' + x for x in sorted(value.list)])) 98 | else: 99 | testList().show() 100 | 101 | class groupList(flowList): 102 | def __init__(self): 103 | super(groupList, self).__init__('Groups') 104 | self._groupFile = defaultGroupFile() 105 | self._grouplist = readGroupCfgFile(self._groupFile).testGroup.subSection.keys() 106 | 107 | @property 108 | def list(self): 109 | return self._grouplist 110 | 111 | @property 112 | def groupFile(self): 113 | return self._groupFile 114 | 115 | @groupFile.setter 116 | def groupFile(self, value): 117 | if not os.path.isfile(value): 118 | raise SystemError('%s is not exists!' % value) 119 | self._groupFile = value 120 | self._grouplist = readGroupCfgFile(self._groupFile).testGroup.subSection.keys() 121 | 122 | class buildList(flowList): 123 | def __init__ (self): 124 | super(buildList, self).__init__('Builds') 125 | self._buildlist= readBuildCfgFile(defaultBuildFile()).build.subSection.keys() 126 | 127 | @property 128 | def list(self): 129 | return self._buildlist 130 | 131 | testlist = allTestList() 132 | buildlist = buildList() 133 | grouplist = groupList() 134 | 135 | def show(name): 136 | getattr(sys.modules[__name__], name + 'list').show() 137 | 138 | 139 | #if __name__ == '__main__': 140 | # tests = testList() 141 | # tests._getTestList() 142 | # testlist.setTestLists('default_build', tests) 143 | # show('test') 144 | # show('build') 145 | # show('group') 146 | -------------------------------------------------------------------------------- /database.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | # Copyright (c) 2014-2018, Lars Asplund lars.anders.asplund@gmail.com 19 | 20 | """ 21 | A simple file based database 22 | """ 23 | 24 | from os.path import join, exists 25 | import os 26 | import pickle 27 | import io 28 | import struct 29 | from ostools import renew_path 30 | 31 | 32 | class DataBase(object): 33 | """ 34 | A simple file based database 35 | both keys and values are bytes 36 | 37 | The database consists of a folder with files called nodes. 38 | Each nodes contains four bytes denoting the key length as an 39 | unsigned integer followed by the key followed by the data. 40 | 41 | The reason to not just have the keys as the file names is that 42 | many operating systems does not support very long file names thus limiting the key length 43 | """ 44 | 45 | def __init__(self, path, new=False): 46 | """ 47 | Create database in path 48 | - path is a directory 49 | - new create new database 50 | """ 51 | self._path = path 52 | 53 | if new: 54 | renew_path(path) 55 | elif not exists(path): 56 | os.makedirs(path) 57 | 58 | # Map keys to nodes indexes 59 | self._keys_to_nodes = self._discover_nodes() 60 | if not self._keys_to_nodes: 61 | self._next_node = 0 62 | else: 63 | self._next_node = max(self._keys_to_nodes.values()) + 1 64 | 65 | def _discover_nodes(self): 66 | """ 67 | Discover nodes already found in the database 68 | """ 69 | keys_to_nodes = {} 70 | for file_base_name in os.listdir(self._path): 71 | key = self._read_key(join(self._path, file_base_name)) 72 | assert key not in keys_to_nodes # Two nodes contains the same key 73 | keys_to_nodes[key] = int(file_base_name) 74 | return keys_to_nodes 75 | 76 | @staticmethod 77 | def _read_key_from_fptr(fptr): 78 | """ 79 | Read the key from a file pointer 80 | first read four bytes for the key length then read the key 81 | """ 82 | key_size = struct.unpack("I", fptr.read(4))[0] 83 | key = fptr.read(key_size) 84 | return key 85 | 86 | def _read_key(self, file_name): 87 | """ 88 | Read key found in file_name 89 | """ 90 | with io.open(file_name, "rb") as fptr: 91 | return self._read_key_from_fptr(fptr) 92 | 93 | def _read_data(self, file_name): 94 | """ 95 | Read key found in file_name 96 | """ 97 | with io.open(file_name, "rb") as fptr: 98 | self._read_key_from_fptr(fptr) 99 | data = fptr.read() 100 | return data 101 | 102 | @staticmethod 103 | def _write_node(file_name, key, value): 104 | """ 105 | Write node to file 106 | """ 107 | with io.open(file_name, "wb") as fptr: 108 | fptr.write(struct.pack("I", len(key))) 109 | fptr.write(key) 110 | fptr.write(value) 111 | 112 | def _to_file_name(self, key): 113 | """ 114 | Convert key to file name 115 | """ 116 | return join(self._path, str(self._keys_to_nodes[key])) 117 | 118 | def _allocate_node_for_key(self, key): 119 | """ 120 | Allocate a node index for a new key 121 | """ 122 | assert key not in self._keys_to_nodes 123 | self._keys_to_nodes[key] = self._next_node 124 | self._next_node += 1 125 | 126 | def __setitem__(self, key, value): 127 | if key not in self._keys_to_nodes: 128 | self._allocate_node_for_key(key) 129 | self._write_node(self._to_file_name(key), key, value) 130 | 131 | def __getitem__(self, key): 132 | if key not in self: 133 | raise KeyError(key) 134 | 135 | return self._read_data(self._to_file_name(key)) 136 | 137 | def __contains__(self, key): 138 | return key in self._keys_to_nodes 139 | 140 | 141 | class PickledDataBase(object): 142 | """ 143 | Wraps a byte based database (un)pickling the values 144 | Allowing storage of arbitrary Python objects 145 | """ 146 | def __init__(self, database): 147 | self._database = database 148 | 149 | def __getitem__(self, key): 150 | return pickle.loads(self._database[key]) 151 | 152 | def __setitem__(self, key, value): 153 | self._database[key] = pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL) 154 | 155 | def __contains__(self, key): 156 | return key in self._database 157 | -------------------------------------------------------------------------------- /baseCfg.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | from extconfigobj import ConfigObj, ParseError, Section, getSections, getKeyWords 19 | from globals import * 20 | 21 | class baseCfg(object): 22 | def __init__(self, name, section, parent=None): 23 | self._name = name 24 | self._section = section 25 | self._parent=parent 26 | self._subSection = {} 27 | self._buildInOpts = {} 28 | self._subSectionType=self.__class__ 29 | 30 | @property 31 | def name(self): 32 | return self._name 33 | 34 | @property 35 | def parent(self): 36 | return self._parent 37 | 38 | @property 39 | def subSection(self): 40 | return self._subSection 41 | 42 | @property 43 | def buildInOption(self): 44 | return self._buildInOpts 45 | 46 | def _addOption(self, option): 47 | self._buildInOpts[option] = [] 48 | 49 | def isRoot(self): 50 | return not self._parent 51 | 52 | def isLeaf(self): 53 | return not self._subSection 54 | 55 | def parse(self): 56 | self._readSubSection() 57 | self._readBuildInOption() 58 | 59 | def dump(self, tab=''): 60 | result = tab 61 | result += 'name:' + self.name + '\n' 62 | result += tab+'\tbuildInOption: '+self.buildInOption.__str__()+'\n' 63 | for subSection in self.subSection.values(): 64 | result += subSection.dump(tab+'\t') 65 | return result 66 | 67 | def _readSubSection(self): 68 | if isinstance(self._section, Section): 69 | for k, v in getSections(self._section).items(): 70 | self._subSection[k] = self._subSectionType(k,v,self) 71 | self._subSection[k].parse() 72 | 73 | def _readBuildInOption(self): 74 | for k,v in getKeyWords(self._section).items(): 75 | if self._checkKeyWord(k) : 76 | self._buildInOpts[k] = v #" ".join(v) if isinstance(v, list) else v 77 | 78 | def _checkKeyWord(self, k): 79 | if not k in self._buildInOpts: 80 | raise ParseError('%s is unknown option' % k) 81 | return True 82 | 83 | class includableTopCfg(baseCfg): 84 | def __init__(self, name, section, parent=None): 85 | super(includableTopCfg, self).__init__(name, section, parent) 86 | self._subSectionType = includableCfg 87 | 88 | def parse(self): 89 | super(includableTopCfg, self).parse() 90 | for subSection in self._subSection.values(): 91 | if not isinstance(subSection.include, list): 92 | for incName in [subSection.include]: 93 | subSection.addInclude(self._subSection[incName]) 94 | else: 95 | for incName in subSection.include: 96 | subSection.addInclude(self._subSection[incName]) 97 | 98 | class includableCfg(baseCfg): 99 | def __init__(self, name, section, parent=None): 100 | super(includableCfg, self).__init__(name, section, parent) 101 | self._include = [] 102 | self._addOption('include') 103 | 104 | @property 105 | def include(self): 106 | return self._buildInOpts['include'] 107 | 108 | def addInclude(self, inc): 109 | self._include.append(inc) 110 | 111 | class buildBaseCfg(baseCfg): 112 | def __init__(self, name, section, parent=None): 113 | super(buildBaseCfg, self).__init__(name, section, parent) 114 | self._addOption('pre_compile_option') 115 | self._addOption('compile_option') 116 | self._addOption('post_compile_option') 117 | self._addOption('pre_sim_option') 118 | self._addOption('sim_option') 119 | self._addOption('post_sim_option') 120 | 121 | @property 122 | def compileOption(self): 123 | return self._buildInOpts['compile_option'] 124 | 125 | @property 126 | def simOption(self): 127 | return self._buildInOpts['sim_option'] 128 | 129 | @property 130 | def preCompileOption(self): 131 | return self._buildInOpts['pre_compile_option'] 132 | 133 | @property 134 | def preSimOption(self): 135 | return self._buildInOpts['pre_sim_option'] 136 | 137 | @property 138 | def postCompileOption(self): 139 | return self._buildInOpts['post_compile_option'] 140 | 141 | @property 142 | def postSimOption(self): 143 | return self._buildInOpts['post_sim_option'] 144 | 145 | 146 | #if __name__ == '__main__': 147 | # config = ConfigObj(infile=defaultBuildFile(), stringify=True) 148 | # baseCfg = flowBaseCfg('test',config) 149 | # baseCfg.parse() 150 | # #print(baseCfg.subSection['build'].buildInOption) 151 | # #print(baseCfg.subSection['build'].subSection['dla'].buildInOption) 152 | # #print(baseCfg.buildInOption.keys()) 153 | # #baseCfg.dump(tab='debug point') 154 | -------------------------------------------------------------------------------- /Simulator/incisiveInterface.py: -------------------------------------------------------------------------------- 1 | """ 2 | Interface for the Cadence Incisive simulator 3 | """ 4 | import os 5 | import re 6 | from os.path import join, dirname, abspath, relpath 7 | import sys 8 | import argparse 9 | from utils import * 10 | import logging 11 | from .simulatorInterface import (simulatorInterface, run_command) 12 | from .simCheck import * 13 | 14 | LOGGER = logging.getLogger(__name__) 15 | 16 | class waveArgsAction(argparse.Action): 17 | def __call__(self, parser, args, values, option = None): 18 | args.wave = values 19 | if args.wave == 'vpd': 20 | appendAttr(args, 'compileOption', '-access +r +define+DUMP_VPD') 21 | elif args.wave == 'fsdb': 22 | appendAttr(args, 'compileOption', '-access +r +define+DUMP_FSDB') 23 | elif args.wave == 'gui': 24 | appendAttr(args, 'compileOption', '-access +rwc +define+DUMP_FSDB') 25 | appendAttr(args, 'simOption', '-gui') 26 | 27 | class covArgsAction(argparse.Action): 28 | def __call__(self, parser, args, values, option = None): 29 | args.cov = values 30 | if args.cov == 'all': 31 | appendAttr(args, 'compileOption', '-coverage all') 32 | appendAttr(args, 'simOption', '-coverage all') 33 | appendAttr(args, 'simOption', '+FCOV_EN') 34 | else: 35 | appendAttr(args, 'compileOption', '-cm ' + args.cov) 36 | appendAttr(args, 'simOption', '-cm ' + args.cov) 37 | appendAttr(args, 'simOption', ' +FCOV_EN') 38 | 39 | class indagoArgsAction(argparse.Action): 40 | def __call__(self, parser, args, values, option = None): 41 | args.indago = values 42 | appendAttr(args, 'simOption', '-indago') 43 | if args.indago == 'all': 44 | appendAttr(args, 'compileOption', '-line -linedebug -classlinedebug -uvmlinedebug') 45 | else: 46 | appendAttr(args, 'compileOption', '-line ' + args.indago) 47 | 48 | class seedArgsAction(argparse.Action): 49 | def __call__(self, parser, args, values, option = None): 50 | args.seed = values 51 | if args.seed == 0: 52 | appendAttr(args, 'simOption', '-svseed 0') 53 | else: 54 | appendAttr(args, 'simOption', '-svseed %d' % args.seed) 55 | 56 | class testArgsAction(argparse.Action): 57 | def __call__(self, parser, args, values, option = None): 58 | args.test = values 59 | if args.test: 60 | appendAttr(args, 'simOption', '+UVM_TESTNAME=%s' % args.test) 61 | 62 | class incisiveInterface(simulatorInterface): 63 | """ 64 | Interface for the Cadence Incisive simulator 65 | """ 66 | 67 | name = "irun" 68 | supports_gui_flag = True 69 | 70 | @staticmethod 71 | def add_arguments(parser, group): 72 | """ 73 | Add command line arguments 74 | """ 75 | group.add_argument('-t', '-test', dest='test', action=testArgsAction, help='assign test name') 76 | 77 | parser.add_argument('-w', '-wave', nargs='?', const='fsdb', dest='wave', action=waveArgsAction, 78 | choices=['vpd', 'fsdb', 'gui'], 79 | help='dump waveform(vpd or fsdb), default fsdb') 80 | parser.add_argument('-indago', nargs='?', const='all', dest='indago', action=indagoArgsAction, 81 | help='running Indago in interactive mode(instead of SimVision)') 82 | parser.add_argument('-cov', nargs='?', const='all', dest='cov', action=covArgsAction, 83 | help='collect code coverage, default all kinds collect(line+cond+fsm+tgl+branch+assert') 84 | 85 | parser.add_argument('-seed', type=positive_int, dest='seed', default=0, action=seedArgsAction, 86 | help='set testcase ntb random seed') 87 | 88 | @classmethod 89 | def find_prefix_from_path(cls): 90 | """ 91 | Find irun simulator from PATH environment variable 92 | """ 93 | return cls.find_toolchain(['irun']) 94 | 95 | def __init__(self): 96 | simulatorInterface.__init__(self) 97 | self._simCheck = irunSimCheck() 98 | 99 | @property 100 | def simCheck(self): 101 | return self._simCheck; 102 | 103 | def compileExe(self): 104 | """ 105 | Returns Incisive compile executable cmd 106 | """ 107 | return 'irun' 108 | 109 | def simExe(self): 110 | """ 111 | Returns Incisive simv executable cmd 112 | """ 113 | return 'irun' 114 | 115 | def executeCompile(self, buildDir, cmd, printer, timeout): 116 | """ 117 | Incisive doesn't need compile step, so override this function 118 | in base class, then do nothing 119 | """ 120 | pass 121 | 122 | def executeSimulataion(self, testWordDir, simCmd, timeout): 123 | if not run_command(simCmd, testWordDir, timeout): 124 | return False 125 | else: 126 | return True 127 | 128 | class irunSimCheck(simCheck): 129 | """ 130 | Irun specified simulation results checker 131 | """ 132 | irunErrorPattern = r'^Error-\[.*\]' 133 | coreDumpPattern = r'Completed context dump phase' 134 | simEndPattern = r'ncsim> exit' 135 | timingViolationPattern = r'.*Timing violation.*' 136 | 137 | def __init__(self): 138 | super(irunSimCheck, self).__init__() 139 | self._simEndPattern = re.compile(irunSimCheck.simEndPattern) 140 | self.setErrPatterns(irunSimCheck.irunErrorPattern) 141 | self.setErrPatterns(irunSimCheck.coreDumpPattern) 142 | self.setWarnPatterns(irunSimCheck.timingViolationPattern) 143 | -------------------------------------------------------------------------------- /Simulator/xceliumInterface.py: -------------------------------------------------------------------------------- 1 | """ 2 | Interface for the Cadence Xcelium simulator 3 | """ 4 | import os 5 | import re 6 | from os.path import join, dirname, abspath, relpath 7 | import sys 8 | import argparse 9 | from utils import * 10 | import logging 11 | from .simulatorInterface import (simulatorInterface, run_command) 12 | from .simCheck import * 13 | 14 | LOGGER = logging.getLogger(__name__) 15 | 16 | class waveArgsAction(argparse.Action): 17 | def __call__(self, parser, args, values, option = None): 18 | args.wave = values 19 | if args.wave == 'vpd': 20 | appendAttr(args, 'compileOption', '-access +r +define+DUMP_VPD') 21 | elif args.wave == 'fsdb': 22 | appendAttr(args, 'compileOption', '-access +r +define+DUMP_FSDB') 23 | elif args.wave == 'gui': 24 | appendAttr(args, 'compileOption', '-access +rwc -line -linedebug +define+DUMP_FSDB') 25 | appendAttr(args, 'simOption', '-gui') 26 | 27 | class covArgsAction(argparse.Action): 28 | def __call__(self, parser, args, values, option = None): 29 | args.cov = values 30 | if args.cov == 'all': 31 | appendAttr(args, 'compileOption', '-coverage all') 32 | appendAttr(args, 'simOption', '-coverage all') 33 | appendAttr(args, 'simOption', '+FCOV_EN') 34 | else: 35 | appendAttr(args, 'compileOption', '-cm ' + args.cov) 36 | appendAttr(args, 'simOption', '-cm ' + args.cov) 37 | appendAttr(args, 'simOption', ' +FCOV_EN') 38 | 39 | class indagoArgsAction(argparse.Action): 40 | def __call__(self, parser, args, values, option = None): 41 | args.indago = values 42 | appendAttr(args, 'simOption', '-indago') 43 | if args.indago == 'all': 44 | appendAttr(args, 'compileOption', '-line -linedebug -classlinedebug -uvmlinedebug') 45 | else: 46 | appendAttr(args, 'compileOption', '-line ' + args.indago) 47 | 48 | class seedArgsAction(argparse.Action): 49 | def __call__(self, parser, args, values, option = None): 50 | args.seed = values 51 | if args.seed == 0: 52 | appendAttr(args, 'simOption', '-svseed 0') 53 | else: 54 | appendAttr(args, 'simOption', '-svseed %d' % args.seed) 55 | 56 | class testArgsAction(argparse.Action): 57 | def __call__(self, parser, args, values, option = None): 58 | args.test = values 59 | if args.test: 60 | appendAttr(args, 'simOption', '+UVM_TESTNAME=%s' % args.test) 61 | 62 | class xceliumInterface(simulatorInterface): 63 | """ 64 | Interface for the Cadence Xcelium simulator 65 | """ 66 | 67 | name = "xrun" 68 | supports_gui_flag = True 69 | 70 | @staticmethod 71 | def add_arguments(parser, group): 72 | """ 73 | Add command line arguments 74 | """ 75 | group.add_argument('-t', '-test', dest='test', action=testArgsAction, help='assign test name') 76 | 77 | parser.add_argument('-w', '-wave', nargs='?', const='fsdb', dest='wave', action=waveArgsAction, 78 | choices=['vpd', 'fsdb', 'gui'], 79 | help='dump waveform(vpd or fsdb), default fsdb') 80 | parser.add_argument('-indago', nargs='?', const='all', dest='indago', action=indagoArgsAction, 81 | help='running Indago in interactive mode(instead of SimVision)') 82 | parser.add_argument('-cov', nargs='?', const='all', dest='cov', action=covArgsAction, 83 | help='collect code coverage, default all kinds collect(line+cond+fsm+tgl+branch+assert') 84 | 85 | parser.add_argument('-seed', type=positive_int, dest='seed', default=0, action=seedArgsAction, 86 | help='set testcase ntb random seed') 87 | 88 | @classmethod 89 | def find_prefix_from_path(cls): 90 | """ 91 | Find xrun simulator from PATH environment variable 92 | """ 93 | return cls.find_toolchain(['xrun']) 94 | 95 | def __init__(self): 96 | simulatorInterface.__init__(self) 97 | self._simCheck = xrunSimCheck() 98 | 99 | @property 100 | def simCheck(self): 101 | return self._simCheck; 102 | 103 | def compileExe(self): 104 | """ 105 | Returns Xcelium compile executable cmd 106 | """ 107 | return 'xrun' 108 | 109 | def simExe(self): 110 | """ 111 | Returns Xcelium simv executable cmd 112 | """ 113 | return 'xrun' 114 | 115 | #def executeCompile(self, buildDir, cmd, printer, timeout): 116 | # """ 117 | # Xcelium doesn't need compile step, so override this function 118 | # in base class, then do nothing 119 | # """ 120 | # pass 121 | 122 | def executeSimulataion(self, testWordDir, simCmd, timeout): 123 | if not run_command(simCmd, testWordDir, timeout): 124 | return False 125 | else: 126 | return True 127 | 128 | class xrunSimCheck(simCheck): 129 | """ 130 | xrun specified simulation results checker 131 | """ 132 | xrunErrorPattern = r'^Error-\[.*\]' 133 | coreDumpPattern = r'Completed context dump phase' 134 | simEndPattern = r'xcelium> exit' 135 | timingViolationPattern = r'.*Timing violation.*' 136 | 137 | def __init__(self): 138 | super(xrunSimCheck, self).__init__() 139 | self._simEndPattern = re.compile(xrunSimCheck.simEndPattern) 140 | self.setErrPatterns(xrunSimCheck.xrunErrorPattern) 141 | self.setErrPatterns(xrunSimCheck.coreDumpPattern) 142 | self.setWarnPatterns(xrunSimCheck.timingViolationPattern) 143 | -------------------------------------------------------------------------------- /Simulator/vcsInterface.py: -------------------------------------------------------------------------------- 1 | """ 2 | Interface for the Synopsys VCS MX simulator 3 | """ 4 | import os 5 | import re 6 | from os.path import join, dirname, abspath, relpath 7 | import sys 8 | import argparse 9 | from utils import * 10 | import logging 11 | from .simulatorInterface import (simulatorInterface, run_command) 12 | from .simCheck import * 13 | 14 | LOGGER = logging.getLogger(__name__) 15 | import sys 16 | sys.path.append("../") 17 | from globals import * 18 | 19 | class waveArgsAction(argparse.Action): 20 | """ 21 | '-wave' argument Action callback subclass 22 | FIXME: because i used VCS version: vcs script version : I-2014.03 23 | which is not support '-lca' option, and if you want to dump fsdb waveform, 24 | you must have -fsdb and -debug_pp option. but if you new version, 25 | -debug_pp is deprecated. so you can set -debug_access+pp 26 | and $VERDI_HOME env var for fsdb dump 27 | """ 28 | def __call__(self, parser, args, values, option = None): 29 | args.wave = values 30 | if args.wave == 'vpd': 31 | appendAttr(args, 'compileOption', '-kdb -debug_access+pp +define+DUMP_VPD') 32 | elif args.wave == 'fsdb': 33 | appendAttr(args, 'compileOption', '-kdb -debug_access+pp +define+DUMP_FSDB') 34 | elif args.wave == 'gui': 35 | appendAttr(args, 'compileOption', '-kdb -debug_access+all +define+DUMP_FSDB') 36 | appendAttr(args, 'simOption', '-verdi') 37 | 38 | class covArgsAction(argparse.Action): 39 | """ 40 | '-cov' argument Action callback subclass 41 | """ 42 | def __call__(self, parser, args, values, option = None): 43 | args.cov = values 44 | #TODO: Add -cm_dir and -cm_name options 45 | if args.cov == 'all': 46 | appendAttr(args, 'compileOption', '-cm_dir %s -cm line+cond+fsm+tgl+branch+assert' %(defaultCovDir())) 47 | appendAttr(args, 'simOption', '-cm line+cond+fsm+tgl+branch+assert') 48 | appendAttr(args, 'simOption', '+FCOV_EN') 49 | else: 50 | appendAttr(args, 'compileOption', '-cm_dir ' + defaultCovDir() + ' -cm ' + args.cov) 51 | appendAttr(args, 'simOption', '-cm ' + args.cov) 52 | appendAttr(args, 'simOption', ' +FCOV_EN') 53 | 54 | class seedArgsAction(argparse.Action): 55 | """ 56 | '-seed' argument Action callback subclass 57 | """ 58 | def __call__(self, parser, args, values, option = None): 59 | args.seed = values 60 | if args.seed == 0: 61 | appendAttr(args, 'simOption', '+ntb_random_seed=1') 62 | else: 63 | appendAttr(args, 'simOption', '+ntb_random_seed=%d' % args.seed) 64 | 65 | class testArgsAction(argparse.Action): 66 | """ 67 | '-t' argument Action callback subclass 68 | """ 69 | def __call__(self, parser, args, values, option = None): 70 | args.test = values 71 | if args.test: 72 | appendAttr(args, 'simOption', '+UVM_TESTNAME=%s' % args.test) 73 | 74 | class vcsInterface(simulatorInterface): 75 | """ 76 | Interface for the Synopsys VCS MX simulator 77 | """ 78 | 79 | name = "vcs" 80 | supports_gui_flag = True 81 | 82 | @staticmethod 83 | def add_arguments(parser, group): 84 | """ 85 | Add command line arguments 86 | """ 87 | group.add_argument('-t', '-test', dest='test', action=testArgsAction, help='assign test name') 88 | 89 | parser.add_argument('-w', '-wave', nargs='?', const='fsdb', dest='wave', action=waveArgsAction, 90 | choices=['vpd', 'fsdb', 'gui'], 91 | help='dump waveform(vpd or fsdb), default fsdb') 92 | parser.add_argument('-cov', nargs='?', const='all', dest='cov', action=covArgsAction, 93 | help='collect code coverage, default all kinds collect(line+cond+fsm+tgl+branch+assert') 94 | 95 | parser.add_argument('-seed', type=positive_int, dest='seed', default=0, action=seedArgsAction, 96 | help='set testcase ntb random seed') 97 | 98 | @classmethod 99 | def find_prefix_from_path(cls): 100 | """ 101 | Find vcs simulator from PATH environment variable 102 | """ 103 | return cls.find_toolchain(['vcs']) 104 | 105 | def __init__(self): 106 | simulatorInterface.__init__(self) 107 | self._simCheck = vcsSimCheck() 108 | 109 | @property 110 | def simCheck(self): 111 | """ 112 | Get the Singleton object of vcs sim check class 113 | """ 114 | return self._simCheck; 115 | 116 | def compileExe(self): 117 | """ 118 | Returns vcs compile executable cmd 119 | """ 120 | return 'vcs' 121 | 122 | def simExe(self): 123 | """ 124 | Returns vcs simv executable cmd 125 | """ 126 | return 'simv' 127 | 128 | 129 | def executeSimulataion(self, testWordDir, simCmd, timeout): 130 | if not run_command(simCmd, testWordDir, timeout): 131 | return False 132 | else: 133 | return True 134 | 135 | class vcsSimCheck(simCheck): 136 | """ 137 | VCS specified simulation results checker 138 | """ 139 | vcsErrorPattern = r'^Error-\[.*\]' 140 | coreDumpPattern = r'Completed context dump phase' 141 | simEndPattern = r'V C S S i m u l a t i o n R e p o r t' 142 | timingViolationPattern = r'.*Timing violation.*' 143 | 144 | def __init__(self): 145 | super(vcsSimCheck, self).__init__() 146 | self._simEndPattern = re.compile(vcsSimCheck.simEndPattern) 147 | self.setExcludeWarnPatterns(vcsSimCheck.vcsErrorPattern) 148 | self.setErrPatterns(vcsSimCheck.coreDumpPattern) 149 | self.setWarnPatterns(vcsSimCheck.timingViolationPattern) 150 | -------------------------------------------------------------------------------- /testCaseSuite.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | import os 19 | import sys 20 | import ostools 21 | from test_report import (PASSED, WARNED, FAILED) 22 | from globals import * 23 | 24 | class testcaseSuite(object): 25 | """ 26 | A test case to be run in an independent simulation 27 | """ 28 | def __init__(self, testsWordDir, simCmd, simulator_if): 29 | self._dir = testsWordDir 30 | self._simCmd = simCmd 31 | self._test = os.path.basename(testsWordDir) 32 | self.name = os.path.basename(testsWordDir) 33 | self._run = TestRun(simulator_if=simulator_if, 34 | testWordDir=self._dir, 35 | simCmd = self._simCmd, 36 | test_cases=[self._test]) 37 | 38 | @property 39 | def test_result_file(self): 40 | return self._run.get_test_result() 41 | 42 | @property 43 | def test_information(self): 44 | """ 45 | Returns the test information 46 | """ 47 | return self._test 48 | 49 | def run(self, *args, **kwargs): 50 | """ 51 | Run the test case using the output_path 52 | """ 53 | results = self._run.run(*args, **kwargs) 54 | return results 55 | 56 | class TestRun(object): 57 | """ 58 | A single simulation run yielding the results for one or several test cases 59 | """ 60 | 61 | def __init__(self, simulator_if, testWordDir, simCmd, test_cases): 62 | self._simulator_if = simulator_if 63 | self._testWordDir = testWordDir 64 | self._simCmd = simCmd 65 | self._test_cases = test_cases 66 | 67 | def set_test_cases(self, test_cases): 68 | self._test_cases = test_cases 69 | 70 | def get_test_result(self): 71 | return get_result_file_name(self._testWordDir) 72 | 73 | def run(self): 74 | """ 75 | Run selected test cases within the test suite 76 | 77 | Returns a dictionary of test results 78 | """ 79 | results = {} 80 | for name in self._test_cases: 81 | results[name] = {} 82 | results[name]['reasonMsg'] = '' 83 | results[name]['status'] = FAILED 84 | 85 | # Ensure result file exists 86 | ostools.write_file(get_result_file_name(self._testWordDir), "") 87 | 88 | sim_ok = self._simulate() 89 | 90 | results = self._read_test_results(file_name=get_result_file_name(self._testWordDir)) 91 | 92 | # Do not run post check unless all passed 93 | for status in results.values(): 94 | if status != PASSED: 95 | return results 96 | 97 | #if not self._config.call_post_check(output_path, read_output): 98 | # for name in self._test_cases: 99 | # results[name] = FAILED 100 | 101 | return results 102 | 103 | def _simulate(self): 104 | """ 105 | Run simulation 106 | """ 107 | 108 | return self._simulator_if.simulate( 109 | testWordDir=self._testWordDir, 110 | simCmd = self._simCmd) 111 | 112 | def _read_test_results(self, file_name): 113 | """ 114 | Read test results from yasa_results file 115 | """ 116 | results = {} 117 | for name in self._test_cases: 118 | results[name] = {} 119 | results[name]['reasonMsg'] = '' 120 | results[name]['status'] = FAILED 121 | 122 | if not ostools.file_exists(file_name): 123 | return results 124 | 125 | test_results = ostools.read_file(file_name) 126 | 127 | (userSimCheckFunc, userSimCheckFile) = userSimCheck() 128 | if userSimCheckFile: 129 | sys.path.append(os.path.dirname(userSimCheckFile)) 130 | from userSimCheck import userSimCheck as simCheck 131 | checker=simCheck() 132 | else: 133 | checker= self._simulator_if.simCheck 134 | #elif self._simulator_if.name =='vcs': 135 | # from Simulator.vcsInterface import vcsSimCheck 136 | # checker=vcsSimCheck() 137 | #elif self._simulator_if.name =='irun': 138 | # from Simulator.incisiveInterface import irunSimCheck 139 | # checker=irunSimCheck() 140 | #elif self._simulator_if.name =='xrun': 141 | # from Simulator.xceliumInterface import xrunSimCheck 142 | # checker=xrunSimCheck() 143 | 144 | checker.resetStatus() 145 | for line in test_results.splitlines(): 146 | line = line.strip() 147 | checker.check(line) 148 | status, reasonMsg = checker.status 149 | 150 | for test_name in self._test_cases: 151 | if status == 'PASS': 152 | results[test_name]['status'] = PASSED 153 | else: 154 | results[test_name]['reasonMsg'] = reasonMsg 155 | if status == 'WARN': 156 | results[test_name]['status'] = WARNED 157 | elif status == 'FAIL': 158 | results[test_name]['status'] = FAILED 159 | 160 | for test_name in results: 161 | if test_name not in self._test_cases: 162 | raise RuntimeError("Got unknown test case %s" % test_name) 163 | 164 | return results 165 | 166 | def get_result_file_name(output_path): 167 | return os.path.join(output_path, "sim.log") 168 | -------------------------------------------------------------------------------- /groupCfg.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | from baseCfg import * 19 | from copy import * 20 | from exceptions import groupUnknown 21 | 22 | class groupCfg(includableTopCfg): 23 | def __init__(self, name, section, parent=None): 24 | super(groupCfg, self).__init__(name, section, parent) 25 | self._subSectionType = groupSubCfg 26 | 27 | def getGroup(self, group=''): 28 | if group: 29 | if not group in self.subSection: 30 | raise groupUnknown(group) 31 | groupSection = self.subSection[group] 32 | return groupSection 33 | 34 | 35 | class groupSubCfg(includableCfg): 36 | def __init__(self, name, section, parent=None): 37 | super(groupSubCfg, self).__init__(name, section, parent) 38 | self._addOption('build') 39 | self._addOption('args') 40 | self._addOption('tests') 41 | self._tests = [] 42 | 43 | @property 44 | def incGroups(self): 45 | for inc in self._include: 46 | for incGroup in inc.incGroups: 47 | yield incGroup 48 | yield deepcopy(self) 49 | 50 | def _readBuildInOption(self): 51 | super(groupSubCfg, self)._readBuildInOption() 52 | self._parseBuild() 53 | self._parseArgs() 54 | self._parseTests() 55 | 56 | def _parseBuild(self): 57 | return self._buildInOpts['build'] 58 | 59 | def _parseArgs(self): 60 | return self._buildInOpts['args'] 61 | 62 | def _parseTests(self): 63 | """ 64 | Parse each testcase in group. If group have args field, 65 | append args with testcase name. if tests field itself has 66 | args field. Duplicate fields in group args field will be overwritten 67 | Such as sanity2 will use its seed 56789 and repeat num 2. 68 | seed 56789 overwrite seed 12345 in args field. 69 | sanity3 will use its seed 12345 and repeat num 3 70 | ``` 71 | [[top_smoke]] 72 | build = candy_lover 73 | args = -vh -seed 12345 -r 2 74 | tests = sanity2 -seed 56789 75 | tests = sanity3 -r 3 76 | ``` 77 | """ 78 | testsOpts = [] 79 | # when only one case in group, _buildInOpts['tests'] is string, 80 | #should change to list for post processing 81 | if isinstance(self._buildInOpts['tests'], str): 82 | testsOpts.append(self._buildInOpts['tests']) 83 | self._buildInOpts['tests'] = testsOpts 84 | for test in self._buildInOpts['tests']: 85 | appendArgs = [] 86 | testList = test.split("-") 87 | testArgs = test.split(" ")[1:] 88 | if testList[1:] and self.argsOption: 89 | self._getTestArgs(testList[1:], appendArgs) 90 | testArgs.append(" ".join(['-'+ x for x in appendArgs])) 91 | index = self._buildInOpts['tests'].index(test) 92 | self._buildInOpts['tests'].pop(index) 93 | self._buildInOpts['tests'].insert(index, testList[0] + " ".join(testArgs)) 94 | elif self.argsOption: 95 | testList.append(self.argsOption) 96 | index = self._buildInOpts['tests'].index(test) 97 | self._buildInOpts['tests'].pop(index) 98 | self._buildInOpts['tests'].insert(index, " ".join(testList)) 99 | 100 | def _getTestArgs(self, testArgs, appendArgs): 101 | """ 102 | Get testcase args. Judge if Duplicate fields exist or not. 103 | then deal with these situation. 104 | """ 105 | append = True 106 | argsList = [i.strip() for i in self.argsOptionList if i != ''] 107 | for j in argsList: 108 | j = j.strip().split(" ") 109 | for i in testArgs: 110 | i = i.strip().split(" ") 111 | if len(i) > 1: 112 | if len(j) >1: 113 | if j[0] == i[0]: 114 | append = False 115 | break 116 | elif len(j) == 1: 117 | append = True 118 | elif len(i) == 1: 119 | if len(i) == 1: 120 | if j == i: 121 | append = False 122 | break 123 | elif len(j) > 1: 124 | append = True 125 | if append: 126 | appendArgs.append(" ".join(j)) 127 | 128 | @property 129 | def buildOption(self): 130 | return self._buildInOpts['build'] 131 | 132 | @property 133 | def argsOption(self): 134 | return self._buildInOpts['args'] 135 | 136 | @property 137 | def argsOptionList(self): 138 | if self.argsOption: 139 | return self.argsOption.strip().split("-") 140 | 141 | @property 142 | def testsOption(self): 143 | return self._buildInOpts['tests'] 144 | 145 | #if __name__ == '__main__': 146 | # config = ConfigObj(infile=defaultGroupFile(), stringify=True) 147 | # group = groupCfg('test', config['testgroup']) 148 | # group.parse() 149 | # for v in group.subSection.values(): 150 | # print(v.name) 151 | # print(v.buildOption) 152 | # print(v.argsOption) 153 | # print(v.testsOption) 154 | # print('haha', v.include) 155 | # for include in v.include: 156 | # print(group.getGroup(include).testsOption) 157 | -------------------------------------------------------------------------------- /Simulator/simulatorInterface.py: -------------------------------------------------------------------------------- 1 | """ 2 | Generic simulator interface 3 | """ 4 | import sys 5 | import os 6 | import subprocess 7 | from ostools import Process, simplify_path 8 | from exceptions import CompileError 9 | from color_printer import NO_COLOR_PRINTER 10 | from threading import Timer 11 | 12 | class simulatorInterface(object): 13 | """ 14 | Generic simulator interface 15 | """ 16 | name = None 17 | supports_gui_flag = False 18 | 19 | # True if simulator supports ANSI colors in GUI mode 20 | supports_colors_in_gui = False 21 | 22 | def __init__(self): 23 | self._output_path = '' 24 | self.sim_timeout = 3600 25 | 26 | @property 27 | def output_path(self): 28 | return self._output_path 29 | 30 | @staticmethod 31 | def add_arguments(parser): 32 | """ 33 | Add command line arguments 34 | """ 35 | 36 | @staticmethod 37 | def find_executable(executable): 38 | """ 39 | Return a list of all executables found in PATH 40 | """ 41 | path = os.environ.get('PATH', None) 42 | if path is None: 43 | return [] 44 | 45 | paths = path.split(os.pathsep) 46 | _, ext = os.path.splitext(executable) 47 | 48 | result = [] 49 | if isfile(executable): 50 | result.append(executable) 51 | 52 | for prefix in paths: 53 | file_name = os.path.join(prefix, executable) 54 | if isfile(file_name): 55 | # the file exists, we have a shot at spawn working 56 | result.append(file_name) 57 | return result 58 | 59 | @classmethod 60 | def find_prefix(cls): 61 | """ 62 | Find prefix by looking at YASA__PATH environment variable 63 | """ 64 | prefix = os.environ.get("YASA_" + cls.name.upper() + "_PATH", None) 65 | if prefix is not None: 66 | return prefix 67 | return cls.find_prefix_from_path() 68 | 69 | @classmethod 70 | def find_prefix_from_path(cls): 71 | """ 72 | Find simulator toolchain prefix from PATH environment variable 73 | """ 74 | 75 | @classmethod 76 | def is_available(cls): 77 | """ 78 | Returns True if simulator is available 79 | """ 80 | return cls.find_prefix() is not None 81 | 82 | @classmethod 83 | def find_toolchain(cls, executables, constraints=None): 84 | """ 85 | Find the first path prefix containing all executables 86 | """ 87 | constraints = [] if constraints is None else constraints 88 | 89 | if not executables: 90 | return None 91 | 92 | all_paths = [[os.path.abspath(os.path.dirname(executables)) 93 | for executables in cls.find_executable(name)] 94 | for name in executables] 95 | 96 | for path0 in all_paths[0]: 97 | if all([path0 in paths for paths in all_paths] 98 | + [constraint(path0) for constraint in constraints]): 99 | return path0 100 | return None 101 | 102 | def merge_coverage(self, file_name, args): # pylint: disable=unused-argument, no-self-use 103 | """ 104 | Hook for simulator interface to creating coverage reports 105 | """ 106 | raise RuntimeError("This simulator does not support merging coverage") 107 | 108 | def add_simulator_specific(self): 109 | """ 110 | Hook for the simulator interface to add simulator specific things to the project 111 | """ 112 | pass 113 | 114 | def compile(self, buildDir, cmd, printer=NO_COLOR_PRINTER, timeout=1800): 115 | """ 116 | Compile the project 117 | """ 118 | self.add_simulator_specific() 119 | self.executeCompile(buildDir, cmd, printer, timeout) 120 | 121 | def simulate(self, testWordDir, simCmd): 122 | self.executeSimulataion(testWordDir, simCmd, self.sim_timeout) 123 | 124 | def executeSimulataion(self, testcaseDir, simCmd, timeout): 125 | """ 126 | Simulate 127 | """ 128 | 129 | def executeCompile(self, buildDir, cmd, printer, timeout): 130 | """ 131 | Execute compile step and prints status information and compile log file 132 | """ 133 | try: 134 | if run_compile_command(cmd, buildDir, timeout): 135 | printer.write("Compile passed", fg="gi") 136 | printer.write("\n") 137 | else: 138 | printer.write("Compile failed", fg="ri") 139 | printer.write("\n") 140 | raise CompileError 141 | except OSError: 142 | raise OSError 143 | finally: 144 | print("Log:") 145 | print(' '*4 + os.path.join(buildDir, 'compile.log\n')) 146 | 147 | return True 148 | 149 | @staticmethod 150 | def get_env(): 151 | """ 152 | Allows inheriting classes to overload this to modify environment variables. Return None for default environment 153 | """ 154 | 155 | def isfile(file_name): 156 | """ 157 | Case insensitive os.path.isfile 158 | """ 159 | if not os.path.isfile(file_name): 160 | return False 161 | 162 | return os.path.basename(file_name) in os.listdir(os.path.dirname(file_name)) 163 | 164 | def run_command(command, cwd=None, timeout=1800): 165 | """ 166 | Run a command 167 | """ 168 | try: 169 | proc = Process(command, cwd=cwd) 170 | t = Timer(timeout, lambda: kill(proc)) 171 | t.start() 172 | proc.consume_output() 173 | t.cancel() 174 | return True 175 | except Process.NonZeroExitCode: 176 | t.cancel() 177 | pass 178 | except KeyboardInterrupt: 179 | t.cancel() 180 | raise 181 | return False 182 | 183 | def run_compile_command(command, cwd, timeout): 184 | """ 185 | Run a command 186 | """ 187 | try: 188 | proc = Process(command, cwd=cwd) 189 | t = Timer(timeout, lambda: kill(proc)) 190 | t.start() 191 | proc.consume_output() 192 | t.cancel() 193 | return True 194 | except Process.NonZeroExitCode: 195 | t.cancel() 196 | pass 197 | except KeyboardInterrupt: 198 | print() 199 | print("Caught Ctrl-C shutting down") 200 | t.cancel() 201 | proc.terminate() 202 | return False 203 | 204 | def kill(proc): 205 | print('Subprocess probably got killed by timeout!') 206 | proc.terminate() 207 | -------------------------------------------------------------------------------- /readCfgFile.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | # Copyright (c) 2019-2020, Jude Zhang zhajio.1988@gmail.com 19 | 20 | from buildCfg import * 21 | from groupCfg import * 22 | 23 | class readCfgFileBase(baseCfg): 24 | def __init__(self, name, file): 25 | self._section = ConfigObj(infile=file, stringify=True) 26 | super(readCfgFileBase, self).__init__(name, self._section, None) 27 | self._subSectionType = {} 28 | self._validSection = ['build', 'testgroup'] 29 | 30 | def _readSubSection(self): 31 | for k, v in getSections(self._section).items(): 32 | if self._checkSubSection(k) and k in self._subSectionType: 33 | self._subSection[k] = self._subSectionType[k](k, v, self) 34 | self._subSection[k].parse() 35 | 36 | def _checkSubSection(self, key): 37 | if key not in self._validSection: 38 | raise ParseError('[%s] is unknown section' % key) 39 | return True 40 | 41 | 42 | class readBuildCfgFile(readCfgFileBase): 43 | def __init__(self, file): 44 | super(readBuildCfgFile, self).__init__('readBuildCfgFile', file) 45 | self._subSectionType = {'build': buildCfg} 46 | self.parse() 47 | 48 | @property 49 | def build(self): 50 | if 'build' in self.subSection: 51 | return self.subSection['build'] 52 | 53 | def getBuild(self, build=''): 54 | return self.build.getBuild(build) 55 | 56 | 57 | def compileOption(self, buildName): 58 | return self._toList(self.build.compileOption) + self._toList(self.getBuild(buildName).compileOption) if self.build.compileOption else self.getBuild(buildName).compileOption 59 | 60 | def simOption(self, buildName): 61 | return self._toList(self.build.simOption) + self._toList(self.getBuild(buildName).simOption) if self.build.simOption else self.getBuild(buildName).simOption 62 | 63 | def preCompileOption(self, buildName): 64 | return self._toList(self.build.preCompileOption) + self._toList(self.getBuild(buildName).preCompileOption) 65 | 66 | def preSimOption(self, buildName): 67 | return self._toList(self.build.preSimOption) + self._toList(self.getBuild(buildName).preSimOption) 68 | 69 | def postCompileOption(self, buildName): 70 | return self._toList(self.build.postCompileOption) + self._toList(self.getBuild(buildName).postCompileOption) 71 | 72 | def postSimOption(self, buildName): 73 | return self._toList(self.build.postSimOption) + self._toList(self.getBuild(buildName).postSimOption) 74 | 75 | def _toList(self, preOptions): 76 | if isinstance(preOptions, str): 77 | return [preOptions] 78 | elif isinstance(preOptions, list): 79 | return preOptions 80 | 81 | class readGroupCfgFile(readCfgFileBase): 82 | def __init__(self, file): 83 | super(readGroupCfgFile, self).__init__('readGroupCfgFile', file) 84 | self._subSectionType = {'testgroup': groupCfg} 85 | self.parse() 86 | self._validBuild = [] 87 | self._allBuild = [] 88 | self._tests = {} 89 | 90 | @property 91 | def testGroup(self): 92 | if 'testgroup' in self.subSection: 93 | return self.subSection['testgroup'] 94 | 95 | @property 96 | def validBuild(self): 97 | return self._validBuild[0] 98 | 99 | @property 100 | def allBuild(self): 101 | return list(set(self._allBuild)) 102 | 103 | def getTests(self, groupName): 104 | groupSection = self.testGroup.getGroup(groupName) 105 | globalBuild = groupSection.buildOption 106 | globalTests = groupSection.testsOption 107 | if globalBuild: 108 | self._validBuild.append(globalBuild) 109 | self._allBuild.append(globalBuild) 110 | if globalTests: 111 | self._tests[groupName] = globalTests 112 | if groupSection.include: 113 | for incGroup in groupSection.incGroups: 114 | if incGroup.testsOption: 115 | self._tests[incGroup.name] = incGroup.testsOption 116 | self.setValidBuild(globalBuild, incGroup.buildOption) 117 | self._allBuild.append(incGroup.buildOption) 118 | 119 | self.checkBuild(self._validBuild, groupName) 120 | return self._tests 121 | 122 | def setValidBuild(self, globalBuild, subBuild): 123 | if globalBuild and subBuild and globalBuild != subBuild: 124 | self._validBuild.append(globalBuild) 125 | elif subBuild and not globalBuild: 126 | self._validBuild.append(subBuild) 127 | elif globalBuild and not subBuild: 128 | self._validBuild.append(globalBuild) 129 | 130 | def checkBuild(self, buildList, groupName): 131 | buildSet = set(buildList) 132 | if len(buildSet) != 1: 133 | raise ValueError(('group %s has included subgroup is must be in same build' % groupName)) 134 | 135 | if __name__ == '__main__': 136 | # config = readBuildCfgFile(defaultBuildFile()) 137 | # print(config.build.simOption) 138 | # print(config.build.compileOption) 139 | # print(config.build.getBuild('dla').name) 140 | # print(config.build.getBuild('dla').compileOption) 141 | # print(config.simOption('dla')) 142 | # print(config.preCompileOption('dla')) 143 | # print(config.postCompileOption('dla')) 144 | # print(config.preSimOption('dla')) 145 | # print(config.postSimOption('dla')) 146 | 147 | 148 | config = readGroupCfgFile(defaultGroupFile()) 149 | config.getTests('v1_regr') 150 | config.getTests('top_regr') 151 | 152 | #for v in config.testgroup.subSection.values(): 153 | # print(v.include) 154 | # print(v.name) 155 | #print(v.buildOption) 156 | #print(v.argsOption) 157 | #print(v.testsOption) 158 | #print('haha', v.include) 159 | #for include in v.include: 160 | # print(config.getGroup(include).testsOption) 161 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ******************************************************************************* 2 | # YASA 3 | [![Build Status](https://travis-ci.org/zhajio1988/YASA.svg?branch=master)](https://travis-ci.org/zhajio1988/YASA) 4 | 5 | :snail:Yet Another Simulation Architecture 6 | 7 | Author: Jude Zhang, E-mail: zhajio.1988@gmail.com 8 | 9 | YASA is an open source simulation framework for SystemVerilog/UVM testbentch 10 | released under the terms of Apache License, v. 2.0. 11 | It support mutli_simulators, multi_languages, lsf etc. 12 | It support several excellent features. Such as: 13 | customized command line arguments, can add any compilation options or simulation options, 14 | can running a testcase with random seeds for several rounds or running a group of 15 | testcases, each testcase has several command line options. 16 | 17 | ### Dependencies: 18 | * python 3.6 19 | * configobj 20 | * argparse 21 | 22 | * vcs or incisive simulator 23 | 24 | ### Typical Usage: 25 | * show help doc 26 | 27 | `%> python3 yasaTop.py -h` 28 | 29 | * show YASA doc file and copyright 30 | 31 | `%> python3 yasaTop.py -doc` 32 | 33 | * show YASA version 34 | 35 | `%> python3 yasaTop.py -version` 36 | 37 | * compile only, build candy_lover, unique_sim mode 38 | 39 | `%> python3 yasaTop.py -b candy_lover -co -u` 40 | 41 | * compile only, testcase sanity1 42 | 43 | `%> python3 yasaTop.py -t sanity1 -co` 44 | 45 | * running testcase sanity1, 5 times,each time with random seed 46 | 47 | `%> python3 yasaTop.py -t sanity1 -r 5` 48 | 49 | * running testcase sanity1 with seed 352938188 50 | 51 | `%> python3 yasaTop.py -t sanity1 -seed 352938188` 52 | 53 | * running testcase sanity1 with seed 352938188, sim only 54 | 55 | `%> python3 yasaTop.py -t sanity1 -seed 352938188 -so` 56 | 57 | * compile only, group top_smoke 58 | 59 | `%> python3 yasaTop.py -g top_smoke -co` 60 | 61 | * running group top_smoke, use 5 threads 62 | 63 | `%> python3 yasaTop.py -g top_smoke -p 5` 64 | 65 | ### Help: 66 | %> python3 yasaTop.py -h 67 | ``` 68 | usage: yasaTop.py [-h] [-g GROUP] [-show {test,group,build}] [-so] [-co] 69 | [-b BUILD] [-test_prefix TESTPREFIX] [-r REPEAT] [-c] 70 | [-fail-fast] [-o OUTPUT_PATH] [-x [XUNIT_XML]] 71 | [-xunit-xml-format {jenkins,bamboo}] [-exit-0] 72 | [-dont-catch-exceptions] [-v] [-q] [-no-color] 73 | [-log-level {info,error,warning,debug}] [-p NUM_THREADS] 74 | [-u] [-version] [-doc] [-t TEST] [-w [{vpd,fsdb,gui}]] 75 | [-cov [COV]] [-seed SEED] [-vh] [-prof] 76 | [-wave_name WAVE_NAME] [-pre_comp_option PRE_COMP_OPTION] 77 | [-comp_option COMP_OPTION] 78 | [-post_comp_option POST_COMP_OPTION] 79 | [-pre_sim_option PRE_SIM_OPTION] [-sim_option SIM_OPTION] 80 | [-post_sim_option POST_SIM_OPTION] 81 | {lsf} ... 82 | 83 | Yet another simulation architecture® top scripts 84 | 85 | positional arguments: 86 | {lsf} 87 | 88 | optional arguments: 89 | -h, --help show this help message and exit 90 | -g GROUP, -group GROUP 91 | assign test group name 92 | -show {test,group,build} 93 | show test list, group list or build list 94 | -so, -simonly Only run simulation without compile step 95 | -co, -componly Only compile without running tests 96 | -b BUILD, -build BUILD 97 | assign a specific build 98 | -test_prefix TESTPREFIX 99 | add testcase prefix 100 | -r REPEAT, -repeat REPEAT 101 | testcase will random run in given repeat round 102 | -c, -clean Remove output build dir 103 | -fail-fast Stop immediately on first failing test 104 | -o OUTPUT_PATH, -output-path OUTPUT_PATH 105 | Output path for compilation and simulation artifacts 106 | -x [XUNIT_XML], -xunit-xml [XUNIT_XML] 107 | Xunit test report .xml file 108 | -xunit-xml-format {jenkins,bamboo} 109 | Only valid with --xunit-xml argument. Defines where in 110 | the XML file the simulator output is stored on a 111 | failure. "jenkins" = Output stored in , 112 | "bamboo" = Output stored in . 113 | -exit-0 Exit with code 0 even if a test failed. Still exits 114 | with code 1 on fatal errors such as compilation 115 | failure 116 | -dont-catch-exceptions 117 | Let exceptions bubble up all the way. Useful when 118 | running with "python -m pdb". 119 | -v, -verbose Print test output immediately and not only when 120 | failure 121 | -q, --quiet Do not print test output even in the case of failure 122 | -no-color Do not color output 123 | -log-level {info,error,warning,debug} 124 | Log level of Yasa internal python logging. Used for 125 | debugging 126 | -p NUM_THREADS, -num-threads NUM_THREADS 127 | Number of tests to run in parallel. Test output is not 128 | continuously written in verbose mode with p > 1 129 | -u, -unique_sim Do not re-use the same simulator process for running 130 | different test cases (slower) 131 | -version show program's version number and exit 132 | -doc print doc file 133 | -t TEST, -test TEST assign test name 134 | -w [{vpd,fsdb,gui}], -wave [{vpd,fsdb,gui}] 135 | dump waveform(vpd or fsdb), default fsdb 136 | -cov [COV] collect code coverage, default all kinds 137 | collect(line+cond+fsm+tgl+branch+assert 138 | -seed SEED set testcase ntb random seed 139 | -vh set verbosity to UVM_HIGH 140 | -prof user defined option 141 | -wave_name WAVE_NAME set fsdb waveform name 142 | -pre_comp_option PRE_COMP_OPTION 143 | previous compile option 144 | -comp_option COMP_OPTION 145 | compile option 146 | -post_comp_option POST_COMP_OPTION 147 | post compile option 148 | -pre_sim_option PRE_SIM_OPTION 149 | previous sim option 150 | -sim_option SIM_OPTION 151 | sim_option 152 | -post_sim_option POST_SIM_OPTION 153 | post sim option 154 | ``` 155 | 156 | ### Doc: 157 | %> python3 yasaTop.py -doc 158 | 159 | ### License: 160 | ``` 161 | ======= 162 | YASA 163 | ----- 164 | 165 | YASA is released under the terms of Apache License, Version 2.0. 166 | 167 | Copyright (c) 2019, XtremeDV. Jude Zhang All rights reserved. 168 | 169 | uvm 170 | ----- 171 | uvm is the Universal Verification Methodology (UVM) reference implementation 172 | from Accellera. 173 | 174 | vcs 175 | ----- 176 | VCS is a product of Synopsys, Inc. 177 | Copyright 2003-2013 Synopsys, Inc. All Rights Reserved 178 | You are licensed only to use thise products 179 | for which you have lawfully obtained a valid license key. 180 | 181 | irun 182 | ----- 183 | Incisive is a product of Cadence Design Systems, Inc. 184 | (c) Copyright 1995-2014 Cadence Design Systems, Inc. All Rights Reserved 185 | You are licensed only to use thise products 186 | for which you have lawfully obtained a valid license key. 187 | ``` 188 | 189 | ### Special thanks: 190 | Special thanks to Apollo Li for his IPsim :+1: give me read config file demo script 191 | 192 | and Lars Asplund for his excellent job [VUnit](https://github.com/VUnit/vunit) give me test_runner and report script 193 | -------------------------------------------------------------------------------- /yasaCli.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | import argparse 19 | import sys 20 | from os.path import join, abspath 21 | from Simulator.simulatorFactory import SIMULATOR_FACTORY 22 | from about import version, doc 23 | from globals import * 24 | from utils import * 25 | from userCli import userCli 26 | from lsfCli import lsfCli 27 | 28 | class yasaCli(object): 29 | """ 30 | Yasa command line interface 31 | """ 32 | 33 | def __init__(self, description=None): 34 | """ 35 | :param description: A custom short description of the command line tool 36 | add userCli object and lsfCli object for argparser 37 | add _check function 38 | """ 39 | self.parsedArgs = None 40 | self.parser = _create_argument_parser(description) 41 | self.subParsers = self.parser.add_subparsers(dest='subparsers') 42 | self.userCli = userCli(self.parser) 43 | self.userCli.addArguments() 44 | self.lsfCli =lsfCli(self.subParsers) 45 | self.lsfCli.addArguments() 46 | 47 | def parseArgs(self, argv=None): 48 | """ 49 | Parse command line arguments 50 | 51 | :param argv: Use explicit argv instead of actual command line argument 52 | """ 53 | self.parsedArgs = self.parser.parse_args(args=argv) 54 | self._check() 55 | self.userCli.setParsedArgs(self.parsedArgs) 56 | self.lsfCli.setParsedArgs(self.parsedArgs) 57 | 58 | def _check(self): 59 | pass 60 | 61 | def getParsedArgs(self): 62 | return self.parsedArgs 63 | 64 | def userCliCompileOption(self): 65 | """ 66 | return compilation option extract from userCli command 67 | """ 68 | return self.userCli.compileOption() 69 | 70 | def userCliSimOption(self): 71 | """ 72 | return simulation option extract from userCli command 73 | """ 74 | return self.userCli.simOption() 75 | 76 | def _create_argument_parser(description=None, for_documentation=False): 77 | """ 78 | Create the argument parser 79 | 80 | :param description: A custom short description of the command line tool 81 | :param for_documentation: When used for user guide documentation 82 | :returns: The created :mod:`argparse` parser object 83 | """ 84 | if description is None: 85 | description = 'Yet another simulation architecture, version %s' % version() 86 | 87 | if for_documentation: 88 | default_output_path = "./yasa_out" 89 | else: 90 | default_output_path = join(abspath(os.getcwd()), "yasa_out") 91 | 92 | argParser = argparse.ArgumentParser(description=description) 93 | group = argParser.add_mutually_exclusive_group() 94 | group.add_argument('-g', '-group', 95 | dest='group', 96 | action='store', 97 | help='assign test group name') 98 | 99 | group.add_argument('-show', 100 | dest='show', 101 | choices=['test', 'group', 'build'], 102 | help='show test list, group list or build list') 103 | 104 | argParser.add_argument('-so', '-simonly', 105 | dest='simOnly', 106 | action='store_true', 107 | help='Only run simulation without compile step') 108 | 109 | argParser.add_argument('-co', '-componly', 110 | dest='compOnly', 111 | action='store_true', 112 | help='Only compile without running tests') 113 | 114 | argParser.add_argument('-b', '-build', 115 | dest='build', 116 | action='store', 117 | help='assign a specific build') 118 | 119 | argParser.add_argument('-test_prefix', 120 | dest='testPrefix', 121 | default='', 122 | action='store', 123 | help='add testcase prefix') 124 | 125 | argParser.add_argument('-r', '-repeat', 126 | type=positive_int, 127 | dest='repeat', 128 | default=1, 129 | action='store', 130 | help='testcase will random run in given repeat round') 131 | 132 | argParser.add_argument('-c', '-clean', 133 | dest='clean', 134 | action='store_true', 135 | help='Remove output build dir') 136 | 137 | argParser.add_argument('-fail-fast', action='store_true', 138 | default=False, 139 | dest = 'fail_fast', 140 | help='Stop immediately on first failing test') 141 | 142 | argParser.add_argument('-o', '-output-path', 143 | default=default_output_path, 144 | dest = 'output_path', 145 | action='store', 146 | help='Output path for compilation and simulation artifacts') 147 | 148 | argParser.add_argument('-x', '-xunit-xml', 149 | nargs='?', 150 | const='yasa.xml', 151 | dest='xunit_xml', 152 | action='store', 153 | help='Xunit test report .xml file') 154 | 155 | argParser.add_argument('-xunit-xml-format', 156 | choices=['jenkins', 'bamboo'], 157 | default='jenkins', 158 | help=('Only valid with --xunit-xml argument. ' 159 | 'Defines where in the XML file the simulator output is stored on a failure. ' 160 | '"jenkins" = Output stored in , ' 161 | '"bamboo" = Output stored in .')) 162 | 163 | argParser.add_argument('-exit-0', 164 | default=False, 165 | action="store_true", 166 | dest = 'exit_0', 167 | help=('Exit with code 0 even if a test failed. ' 168 | 'Still exits with code 1 on fatal errors such as compilation failure')) 169 | 170 | argParser.add_argument('-dont-catch-exceptions', 171 | default=False, 172 | action="store_true", 173 | dest = 'dont_catch_exceptions', 174 | help=('Let exceptions bubble up all the way. ' 175 | 'Useful when running with "python -m pdb".')) 176 | 177 | argParser.add_argument('-v', '-verbose', action="store_true", 178 | default=False, 179 | dest = 'verbose', 180 | help='Print test output immediately and not only when failure') 181 | 182 | argParser.add_argument('-q', '--quiet', action="store_true", 183 | default=False, 184 | help='Do not print test output even in the case of failure') 185 | 186 | argParser.add_argument('-no-color', action='store_true', 187 | default=False, 188 | dest='no_color', 189 | help='Do not color output') 190 | 191 | argParser.add_argument('-log-level', 192 | default="warning", 193 | choices=["info", "error", "warning", "debug"], 194 | help=("Log level of Yasa internal python logging. " 195 | "Used for debugging")) 196 | 197 | argParser.add_argument('-p', '-num-threads', type=positive_int, 198 | default=1, 199 | dest ='num_threads', 200 | action = 'store', 201 | help=('Number of tests to run in parallel. ' 202 | 'Test output is not continuously written in verbose mode with p > 1')) 203 | 204 | argParser.add_argument("-u", "-unique_sim", 205 | action="store_true", 206 | dest='unique_sim', 207 | help="Do not re-use the same simulator process for running different test cases (slower)") 208 | 209 | argParser.add_argument('-comp_timeout', type=positive_int, 210 | nargs='?', 211 | dest ='comp_timeout', 212 | action = 'store', 213 | const=3600, default=3600, 214 | help="set compile subprocess watchdog timer") 215 | 216 | argParser.add_argument('-sim_timeout', type=positive_int, 217 | nargs='?', 218 | dest ='sim_timeout', 219 | action = 'store', 220 | const=3600, default=3600, 221 | help="set simulation subprocess watchdog timer") 222 | 223 | #argParser.add_argument("-export-json", 224 | # default=None, 225 | # help="Export project information to a JSON file.") 226 | 227 | argParser.add_argument('-version', action='version', version=version()) 228 | argParser.add_argument('-doc', dest='docFile', action="store_true", help="print doc file") 229 | 230 | SIMULATOR_FACTORY.add_arguments(argParser, group) 231 | 232 | return argParser 233 | 234 | def _argParser_for_documentation(): 235 | """ 236 | Returns an argparse object used by sphinx for documentation in user_guide.rst 237 | """ 238 | return _create_argument_parser(for_documentation=True) 239 | 240 | #if __name__ == '__main__': 241 | # import sys 242 | # 243 | # cli = yasaCli("Yet another simulation architecture top scripts") 244 | # #print(cli.parseArgs(sys.argv[1:])) 245 | # #print(vars(cli.parseArgs(sys.argv[1:]))) 246 | # #print(cli.parseArgs(['-h'])) 247 | # cli.parseArgs(sys.argv[1:]) 248 | # args = cli.getParsedArgs() 249 | # print(args.sim_option) 250 | # print(args.wave_name) 251 | # print(args.prof) 252 | # print(args.seed) 253 | # print(args.compOnly) 254 | # print('11', args.build) 255 | # 256 | # #print(args.simOption) 257 | # #print(args.compileOption) 258 | # print(cli.userCli.compileOption()) 259 | # print(cli.userCli.simOption()) 260 | # print(cli.userCliCompileOption()) 261 | # print(cli.userCliSimOption()) 262 | # print(args.lsfOptions) 263 | # print(args.subparsers) 264 | # print("end") 265 | -------------------------------------------------------------------------------- /yasaTop.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | import sys 19 | import traceback 20 | import logging 21 | import os 22 | from os.path import exists, abspath, join 23 | from database import PickledDataBase, DataBase 24 | import ostools 25 | from yasaCli import yasaCli 26 | from Simulator.simulatorFactory import SIMULATOR_FACTORY 27 | from test_list import TestList 28 | from testCaseSuite import testcaseSuite 29 | from color_printer import (COLOR_PRINTER, 30 | NO_COLOR_PRINTER) 31 | from test_runner import TestRunner 32 | from test_report import TestReport 33 | from exceptions import CompileError 34 | from compileBuild import singleTestCompile, groupTestCompile 35 | from about import version, doc 36 | 37 | LOGGER = logging.getLogger(__name__) 38 | 39 | class yasaTop(object): 40 | """ 41 | YASA top scripts 42 | """ 43 | 44 | def __init__(self, argv): 45 | self._cli = yasaCli("Yet another simulation architecture® top scripts") 46 | self._cli.parseArgs(argv=argv) 47 | self._args = self._cli.getParsedArgs() 48 | self._configure_logging(self._args.log_level) 49 | self._output_path = abspath(self._args.output_path) 50 | if self._args.no_color: 51 | self._printer = NO_COLOR_PRINTER 52 | else: 53 | self._printer = COLOR_PRINTER 54 | 55 | self._simulator_class = SIMULATOR_FACTORY.select_simulator() 56 | 57 | #TODO: Use default simulator options if no simulator was present 58 | #if self._simulator_class is None: 59 | # self._simulator_class = simulatorInterface() 60 | 61 | #TODO: no usage 62 | #self._create_output_path(self._args.clean) 63 | 64 | self._checkArgs(self._args) 65 | 66 | #database = self._create_database() 67 | 68 | def _checkArgs(self, args): 69 | """ 70 | check parsed arguments, in case of input nothing from command line 71 | if you type 72 | >>> YASAsim -doc 73 | will print doc content, then exit with 0 74 | """ 75 | if args.docFile: 76 | print(doc()) 77 | sys.exit(0) 78 | if not args.compOnly and not args.build: 79 | if args.show is None: 80 | if args.group is None and args.test is None: 81 | #TODO: this check should be more robust 82 | self._printer.write('One of argument" -t/-test and -g/-group"must be supplied, when not use argument -show\n', fg='ri') 83 | sys.exit(1) 84 | 85 | def _create_database(self): 86 | """ 87 | Create a persistent database to store expensive parse results 88 | 89 | Check for Python version used to create the database is the 90 | same as the running python instance or re-create 91 | """ 92 | project_database_file_name = join(self._output_path, "project_database") 93 | create_new = False 94 | key = b"version" 95 | version = str((9, sys.version)).encode() 96 | database = None 97 | try: 98 | database = DataBase(project_database_file_name) 99 | create_new = (key not in database) or (database[key] != version) 100 | except KeyboardInterrupt: 101 | raise KeyboardInterrupt 102 | except: # pylint: disable=bare-except 103 | traceback.print_exc() 104 | create_new = True 105 | 106 | if create_new: 107 | database = DataBase(project_database_file_name, new=True) 108 | database[key] = version 109 | 110 | return PickledDataBase(database) 111 | 112 | @staticmethod 113 | def _configure_logging(log_level): 114 | """ 115 | Configure logging based on log_level string 116 | """ 117 | level = getattr(logging, log_level.upper()) 118 | logging.basicConfig(filename=None, format='%(levelname)7s - %(message)s', level=level) 119 | 120 | def main(self, post_run=None): 121 | """ 122 | Run yasa main function and exit 123 | 124 | :param post_run: A callback function which is called after 125 | running tests. The function must accept a single `results` 126 | argument which is an instance of :class:`.Results` 127 | """ 128 | try: 129 | all_ok = self._main(post_run) 130 | except KeyboardInterrupt: 131 | sys.exit(1) 132 | except CompileError: 133 | sys.exit(1) 134 | except SystemExit as e: 135 | sys.exit(e.code) 136 | except: 137 | if self._args.dont_catch_exceptions: 138 | raise 139 | traceback.print_exc() 140 | sys.exit(1) 141 | 142 | if (not all_ok) and (not self._args.exit_0): 143 | sys.exit(1) 144 | 145 | sys.exit(0) 146 | 147 | def _create_tests(self, testWorkDir, simCmd, simulator_if): 148 | """ 149 | Create all test cases corresponding testsuites 150 | """ 151 | 152 | test_list = TestList() 153 | for dir in testWorkDir: 154 | test_list.add_test(testcaseSuite(dir, simCmd, simulator_if=simulator_if)) 155 | 156 | return test_list 157 | 158 | def _main(self, post_run): 159 | """ 160 | Base yasa main function without performing exit 161 | support compile only and sim only option 162 | >>> YASAsim -b candy_lover -co -u 163 | >>> YASAsim -t sanity1 -co 164 | """ 165 | 166 | if self._args.compOnly: 167 | return self._main_compile_only() 168 | 169 | all_ok = self._main_run(post_run) 170 | return all_ok 171 | 172 | def _create_simulator_if(self): 173 | """ 174 | Create new simulator instance 175 | """ 176 | 177 | if self._simulator_class is None: 178 | LOGGER.error("No available simulator detected.\n" 179 | "Simulator binary folder must be available in PATH environment variable.\n" 180 | "Simulator binary folder can also be set the in YASA__PATH environment variable.\n") 181 | exit(1) 182 | 183 | return self._simulator_class() 184 | 185 | def _main_run(self, post_run): 186 | """ 187 | Main with running tests 188 | support single testcase running several rounds with specified seed or random seed 189 | or running a group of testcases(each testcase with specified option) 190 | """ 191 | simulator_if = self._create_simulator_if() 192 | simulator_if.sim_timeout = self._args.sim_timeout; 193 | 194 | if self._args.group: 195 | compile = groupTestCompile(cli=self._cli, simulator_if=simulator_if) 196 | compile.prepareEnv() 197 | else: 198 | compile = singleTestCompile(cli=self._cli, simulator_if=simulator_if) 199 | compile.prepareEnv() 200 | test_list = self._create_tests(compile._testCaseWorkDir, compile.simCmd(), simulator_if) 201 | if not self._args.simOnly: 202 | self._compile(compile._buildDir, compile.compileCmd(), simulator_if) 203 | 204 | start_time = ostools.get_time() 205 | report = TestReport(printer=self._printer, filePath=compile._buildDir) 206 | 207 | try: 208 | self._run_test(test_list, report) 209 | except KeyboardInterrupt: 210 | print() 211 | LOGGER.debug("_main: Caught Ctrl-C shutting down") 212 | finally: 213 | del test_list 214 | 215 | report.set_real_total_time(ostools.get_time() - start_time) 216 | report.print_str() 217 | 218 | if post_run is not None: 219 | post_run(results=Results(simulator_if)) 220 | 221 | del simulator_if 222 | 223 | if self._args.xunit_xml is not None: 224 | xml = report.to_junit_xml_str(self._args.xunit_xml_format) 225 | ostools.write_file(self._args.xunit_xml, xml) 226 | 227 | return report.all_ok() 228 | 229 | def _main_compile_only(self): 230 | """ 231 | Main function when only compiling 232 | """ 233 | simulator_if = self._create_simulator_if() 234 | 235 | if self._args.group: 236 | compile = groupTestCompile(cli=self._cli, simulator_if=simulator_if) 237 | compile.prepareEnv() 238 | else: 239 | compile = singleTestCompile(cli=self._cli, simulator_if=simulator_if) 240 | compile.prepareEnv() 241 | test_list = self._create_tests(compile._testCaseWorkDir, compile.simCmd(), simulator_if) 242 | 243 | self._compile(compile._buildDir, compile.compileCmd(), simulator_if) 244 | return True 245 | 246 | def _create_output_path(self, clean): 247 | """ 248 | Create or re-create the output path if necessary 249 | """ 250 | if clean: 251 | ostools.renew_path(self._output_path) 252 | elif not exists(self._output_path): 253 | os.makedirs(self._output_path) 254 | 255 | def _compile(self, buildDir, cmd, simulator_if): 256 | """ 257 | Compile entire tb 258 | """ 259 | simulator_if.compile(buildDir, cmd, self._printer, self._args.comp_timeout) 260 | 261 | def _run_test(self, test_cases, report): 262 | """ 263 | Run the test suites and return the report 264 | """ 265 | 266 | if self._args.verbose: 267 | verbosity = TestRunner.VERBOSITY_VERBOSE 268 | elif self._args.quiet: 269 | verbosity = TestRunner.VERBOSITY_QUIET 270 | else: 271 | verbosity = TestRunner.VERBOSITY_NORMAL 272 | 273 | runner = TestRunner(report, 274 | verbosity=verbosity, 275 | num_threads=self._args.num_threads, 276 | fail_fast=self._args.fail_fast, 277 | dont_catch_exceptions=self._args.dont_catch_exceptions, 278 | no_color=self._args.no_color) 279 | runner.run(test_cases) 280 | 281 | class Results(object): 282 | """ 283 | Gives access to results after running tests. 284 | """ 285 | 286 | def __init__(self, simulator_if): 287 | self._simulator_if = simulator_if 288 | 289 | def merge_coverage(self, file_name, args=None): 290 | """ 291 | Create a merged coverage report from the individual coverage files 292 | 293 | :param file_name: The resulting coverage file name. 294 | :param args: The tool arguments for the merge command. Should be a list of strings. 295 | """ 296 | 297 | self._simulator_if.merge_coverage(file_name=file_name, args=args) 298 | 299 | if __name__ == '__main__': 300 | yasa = yasaTop(sys.argv[1:]) 301 | yasa.main() 302 | -------------------------------------------------------------------------------- /ostools.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | # Copyright (c) 2014-2018, Lars Asplund lars.anders.asplund@gmail.com 19 | """ 20 | Provides operating systems dependent functionality that can be easily 21 | stubbed for testing 22 | """ 23 | 24 | from __future__ import print_function 25 | 26 | import time 27 | import subprocess 28 | import threading 29 | import psutil 30 | import shutil 31 | import sys 32 | import signal 33 | try: 34 | # Python 3.x 35 | from queue import Queue, Empty 36 | except ImportError: 37 | # Python 2.7 38 | from Queue import Queue, Empty # pylint: disable=import-error 39 | 40 | from os.path import exists, getmtime, dirname, relpath, splitdrive 41 | import os 42 | import io 43 | 44 | import logging 45 | LOGGER = logging.getLogger(__name__) 46 | 47 | class ProgramStatus(object): 48 | """ 49 | Maintain global program status to support graceful shutdown 50 | """ 51 | def __init__(self): 52 | self._lock = threading.Lock() 53 | self._shutting_down = False 54 | 55 | @property 56 | def is_shutting_down(self): 57 | with self._lock: # pylint: disable=not-context-manager 58 | return self._shutting_down 59 | 60 | def check_for_shutdown(self): 61 | if self.is_shutting_down: 62 | raise KeyboardInterrupt 63 | 64 | def shutdown(self): 65 | with self._lock: # pylint: disable=not-context-manager 66 | LOGGER.debug("ProgramStatus.shutdown") 67 | self._shutting_down = True 68 | 69 | def reset(self): 70 | with self._lock: # pylint: disable=not-context-manager 71 | self._shutting_down = False 72 | 73 | 74 | PROGRAM_STATUS = ProgramStatus() 75 | 76 | 77 | class InterruptableQueue(object): 78 | """ 79 | A Queue which can be interrupted 80 | """ 81 | 82 | def __init__(self): 83 | self._queue = Queue() 84 | 85 | def get(self): 86 | """ 87 | Get a value from the queue 88 | """ 89 | while True: 90 | PROGRAM_STATUS.check_for_shutdown() 91 | try: 92 | return self._queue.get(timeout=0.1) 93 | except Empty: 94 | pass 95 | 96 | def put(self, value): 97 | self._queue.put(value) 98 | 99 | def empty(self): 100 | return self._queue.empty() 101 | 102 | 103 | class Process(object): 104 | """ 105 | A simple process interface which supports asynchronously consuming the stdout and stderr 106 | of the process while it is running. 107 | """ 108 | 109 | class NonZeroExitCode(Exception): 110 | pass 111 | 112 | def __init__(self, cmd, cwd=None, env=None): 113 | self._cmd = cmd 114 | self._cwd = cwd 115 | 116 | # Create process with new process group 117 | # Sending a signal to a process group will send it to all children 118 | # Hopefully this way no orphaned processes will be left behind 119 | self._process = subprocess.Popen( 120 | self._cmd, 121 | cwd=self._cwd, 122 | stdout=subprocess.PIPE, 123 | stdin=subprocess.PIPE, 124 | stderr=subprocess.STDOUT, 125 | universal_newlines=True, 126 | shell=True, 127 | bufsize=0, 128 | # Create new process group on POSIX, setpgrp does not exist on Windows 129 | #preexec_fn=os.setsid) 130 | preexec_fn=os.setpgrp) # pylint: disable=no-member 131 | LOGGER.debug("Started process with pid=%i: '%s'", self._process.pid, (" ".join(self._cwd))) 132 | 133 | self._queue = InterruptableQueue() 134 | self._reader = AsynchronousFileReader(self._process.stdout, self._queue) 135 | self._reader.start() 136 | 137 | def write(self, *args, **kwargs): 138 | """ Write to stdin """ 139 | if not self._process.stdin.closed: 140 | self._process.stdin.write(*args, **kwargs) 141 | 142 | def writeline(self, line): 143 | """ Write a line to stdin """ 144 | if not self._process.stdin.closed: 145 | self._process.stdin.write(line + "\n") 146 | self._process.stdin.flush() 147 | 148 | def next_line(self): 149 | """ 150 | Return either the next line or the exit code 151 | """ 152 | 153 | if not self._reader.eof(): 154 | # Show what we received from standard output. 155 | msg = self._queue.get() 156 | 157 | if msg is not None: 158 | return msg 159 | 160 | retcode = self.wait() 161 | return retcode 162 | 163 | def wait(self): 164 | """ 165 | Wait while without completely blocking to avoid 166 | deadlock when shutting down 167 | """ 168 | while self._process.poll() is None: 169 | PROGRAM_STATUS.check_for_shutdown() 170 | time.sleep(0.05) 171 | LOGGER.debug("Waiting for process with pid=%i to stop", self._process.pid) 172 | return self._process.returncode 173 | 174 | def is_alive(self): 175 | """ 176 | Returns true if alive 177 | """ 178 | return self._process.poll() is None 179 | 180 | def consume_output(self, callback=print): 181 | """ 182 | Consume the output of the process. 183 | The output is interpreted as UTF-8 text. 184 | 185 | @param callback Called for each line of output 186 | @raises Process.NonZeroExitCode when the process does not exit with code zero 187 | """ 188 | 189 | def default_callback(*args, **kwargs): 190 | pass 191 | 192 | if not callback: 193 | callback = default_callback 194 | 195 | while not self._reader.eof(): 196 | line = self._queue.get() 197 | if line is None: 198 | break 199 | if callback(line) is not None: 200 | return 201 | 202 | retcode = None 203 | while retcode is None: 204 | retcode = self.wait() 205 | if retcode != 0: 206 | raise Process.NonZeroExitCode 207 | 208 | def terminate(self): 209 | """ 210 | Terminate the process 211 | """ 212 | if self._process.poll() is None: 213 | process = psutil.Process(self._process.pid) 214 | proc_list = process.children(recursive=True) 215 | #proc_list.reverse() 216 | for proc in proc_list: 217 | proc.kill() 218 | process.kill() 219 | 220 | # Let's be tidy and join the threads we've started. 221 | if self._process.poll() is None: 222 | LOGGER.debug("Terminating process with pid=%i", self._process.pid) 223 | self._process.terminate() 224 | 225 | if self._process.poll() is None: 226 | time.sleep(0.05) 227 | 228 | if self._process.poll() is None: 229 | LOGGER.debug("Killing process with pid=%i", self._process.pid) 230 | self._process.kill() 231 | 232 | if self._process.poll() is None: 233 | LOGGER.debug("Waiting for process with pid=%i", self._process.pid) 234 | self.wait() 235 | 236 | LOGGER.debug("Process with pid=%i terminated with code=%i", 237 | self._process.pid, 238 | self._process.returncode) 239 | 240 | self._reader.join() 241 | self._process.stdout.close() 242 | self._process.stdin.close() 243 | 244 | def __del__(self): 245 | try: 246 | self.terminate() 247 | except KeyboardInterrupt: 248 | LOGGER.debug("Process.__del__: Ignoring KeyboardInterrupt") 249 | 250 | class AsynchronousFileReader(threading.Thread): 251 | """ 252 | Helper class to implement asynchronous reading of a file 253 | in a separate thread. Pushes read lines on a queue to 254 | be consumed in another thread. 255 | """ 256 | 257 | def __init__(self, fd, queue, encoding="utf-8"): 258 | threading.Thread.__init__(self) 259 | 260 | # If Python 3 change encoding of TextIOWrapper to utf-8 ignoring decode errors 261 | if isinstance(fd, io.TextIOWrapper): 262 | fd = io.TextIOWrapper(fd.buffer, encoding=encoding, errors="ignore") 263 | 264 | self._fd = fd 265 | self._queue = queue 266 | self._encoding = encoding 267 | 268 | def run(self): 269 | """The body of the thread: read lines and put them on the queue.""" 270 | for line in iter(self._fd.readline, ""): 271 | if PROGRAM_STATUS.is_shutting_down: 272 | break 273 | 274 | # Convert string into utf-8 if necessary 275 | if sys.version_info.major == 2: 276 | string = line[:-1].decode(encoding=self._encoding, errors="ignore") 277 | else: 278 | string = line[:-1] 279 | 280 | self._queue.put(string) 281 | self._queue.put(None) 282 | 283 | def eof(self): 284 | """Check whether there is no more content to expect.""" 285 | return not self.is_alive() and self._queue.empty() 286 | 287 | 288 | def read_file(file_name, encoding="utf-8", newline=None): 289 | """ To stub during testing """ 290 | try: 291 | with io.open(file_name, "r", encoding=encoding, newline=newline) as file_to_read: 292 | data = file_to_read.read() 293 | except UnicodeDecodeError: 294 | LOGGER.warning("Could not decode file %s using encoding %s, ignoring encoding errors", 295 | file_name, encoding) 296 | with io.open(file_name, "r", encoding=encoding, errors="ignore", newline=newline) as file_to_read: 297 | data = file_to_read.read() 298 | 299 | return data 300 | 301 | def write_file(file_name, contents, encoding="utf-8"): 302 | """ To stub during testing """ 303 | 304 | path = dirname(file_name) 305 | if path == "": 306 | path = "." 307 | 308 | if not file_exists(path): 309 | os.makedirs(path) 310 | 311 | with io.open(file_name, "wb") as file_to_write: 312 | file_to_write.write(contents.encode(encoding=encoding)) 313 | 314 | 315 | def file_exists(file_name): 316 | """ To stub during testing """ 317 | return exists(file_name) 318 | 319 | 320 | def get_modification_time(file_name): 321 | """ To stub during testing """ 322 | return getmtime(file_name) 323 | 324 | 325 | def get_time(): 326 | """ To stub during testing """ 327 | return time.time() 328 | 329 | 330 | def renew_path(path): 331 | """ 332 | Ensure path directory exists and is empty 333 | """ 334 | if exists(path): 335 | shutil.rmtree(path) 336 | os.makedirs(path) 337 | 338 | 339 | def simplify_path(path): 340 | """ 341 | Return relative path towards current working directory 342 | unless it is a separate Windows drive 343 | """ 344 | cwd = os.getcwd() 345 | drive_cwd = splitdrive(cwd)[0] 346 | drive_path = splitdrive(path)[0] 347 | if drive_path == drive_cwd: 348 | return relpath(path, cwd) 349 | 350 | return path 351 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /test_report.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | # Copyright (c) 2014-2018, Lars Asplund lars.anders.asplund@gmail.com 19 | """ 20 | Provide test reporting functionality 21 | """ 22 | 23 | from xml.etree import ElementTree 24 | from sys import version_info 25 | import os 26 | import socket 27 | import re 28 | from color_printer import COLOR_PRINTER 29 | from ostools import read_file 30 | 31 | 32 | class TestReport(object): 33 | """ 34 | Collect reports from running testcases 35 | """ 36 | def __init__(self, printer=COLOR_PRINTER, filePath="./"): 37 | self._test_results = {} 38 | self._test_names_in_order = [] 39 | self._printer = printer 40 | self._filePath = filePath 41 | self._real_total_time = 0.0 42 | self._expected_num_tests = 0 43 | self.fp = open(os.path.join(self._filePath, "test_status.hud"), "w+", encoding="utf-8") 44 | self.fp.write("HVP metric = test\n") 45 | 46 | def set_real_total_time(self, real_total_time): 47 | """ 48 | Set the real total execution time 49 | """ 50 | self._real_total_time = real_total_time 51 | 52 | def set_expected_num_tests(self, expected_num_tests): 53 | """ 54 | Set the number of tests that we expect to run 55 | """ 56 | self._expected_num_tests = expected_num_tests 57 | 58 | def num_tests(self): 59 | """ 60 | Return the number of tests in the report 61 | """ 62 | return len(self._test_results) 63 | 64 | def add_result(self, *args, **kwargs): 65 | """ 66 | Add a a test result 67 | """ 68 | result = TestResult(*args, **kwargs) 69 | self._test_results[result.name] = result 70 | self._test_names_in_order.append(result.name) 71 | 72 | def _last_test_result(self): 73 | """ 74 | Return the latest test result or fail 75 | """ 76 | return self._test_results[self._test_names_in_order[-1]] 77 | 78 | def _test_results_in_order(self): 79 | """ 80 | Return the test results in the order they were added 81 | """ 82 | for name in self._test_names_in_order: 83 | yield self.result_of(name) 84 | 85 | def print_latest_status(self, total_tests): 86 | """ 87 | Print the latest status including the last test run and the 88 | total number of passed, failed and warned tests 89 | """ 90 | result = self._last_test_result() 91 | passed, failed, warned = self._split() 92 | if result.passed: 93 | self._printer.write("pass", fg='gi') 94 | self.fp.write("%s = pass\n" % result.name) 95 | elif result.failed: 96 | self._printer.write("fail", fg='ri') 97 | self.fp.write("%s = fail\n" % result.name) 98 | elif result.warned: 99 | self._printer.write("warn", fg='rgi') 100 | self.fp.write("%s = warn\n" % result.name) 101 | else: 102 | self.fp.write("%s = unknown\n" % result.name) 103 | assert False 104 | 105 | args = [] 106 | args.append("P=%i" % len(passed)) 107 | args.append("W=%i" % len(warned)) 108 | args.append("F=%i" % len(failed)) 109 | args.append("T=%i" % total_tests) 110 | 111 | if result.fail_message != '': 112 | self._printer.write(" (%s) %s (%.1f seconds)\n FailMsg: %s\n LogFile: %s" % 113 | (" ".join(args), result.name, result.time, result.fail_message, result.log_file)) 114 | else: 115 | self._printer.write(" (%s) %s (%.1f seconds)\n LogFile: %s" % 116 | (" ".join(args), result.name, result.time, result.log_file)) 117 | 118 | def all_ok(self): 119 | """ 120 | Return true if all test passed 121 | """ 122 | return all(test_result.passed for test_result in self._test_results.values()) 123 | 124 | def has_test(self, test_name): 125 | return test_name in self._test_results 126 | 127 | def result_of(self, test_name): 128 | return self._test_results[test_name] 129 | 130 | def print_str(self): 131 | """ 132 | Print the report as a colored string 133 | """ 134 | 135 | passed, failures, warned = self._split() 136 | all_tests = passed + warned + failures 137 | 138 | if not all_tests: 139 | self._printer.write("No tests were run!", fg="rgi") 140 | self._printer.write("\n") 141 | return 142 | 143 | prefix = "==== Summary " 144 | max_len = max(len(test.name) for test in all_tests) 145 | self._printer.write("%s%s\n" % (prefix, "=" * (max(max_len - len(prefix) + 25, 0)))) 146 | for test_result in all_tests: 147 | test_result.print_status(self._printer, padding=max_len) 148 | 149 | self._printer.write("%s\n" % ("=" * (max(max_len + 25, 0)))) 150 | n_failed = len(failures) 151 | n_warned = len(warned) 152 | n_passed = len(passed) 153 | total = len(all_tests) 154 | 155 | self._printer.write("pass", fg='gi') 156 | self._printer.write(" %i of %i\n" % (n_passed, total)) 157 | 158 | if n_warned > 0: 159 | self._printer.write("warn", fg='rgi') 160 | self._printer.write(" %i of %i\n" % (n_warned, total)) 161 | 162 | if n_failed > 0: 163 | self._printer.write("fail", fg='ri') 164 | self._printer.write(" %i of %i\n" % (n_failed, total)) 165 | self._printer.write("%s\n" % ("=" * (max(max_len + 25, 0)))) 166 | 167 | total_time = sum((result.time for result in self._test_results.values())) 168 | self._printer.write("Total time was %.1f seconds\n" % total_time) 169 | self._printer.write("Elapsed time was %.1f seconds\n" % self._real_total_time) 170 | 171 | self._printer.write("%s\n" % ("=" * (max(max_len + 25, 0)))) 172 | 173 | if n_failed > 0: 174 | self._printer.write("Some failed!", fg='ri') 175 | elif n_warned > 0: 176 | self._printer.write("Some warned!", fg='rgxi') 177 | else: 178 | self._printer.write("All passed!", fg='gi') 179 | self._printer.write("\n") 180 | self.fp.close(); 181 | 182 | assert len(all_tests) <= self._expected_num_tests 183 | if len(all_tests) < self._expected_num_tests: 184 | self._printer.write("WARNING: Test execution aborted after running %d out of %d tests" 185 | % (len(all_tests), self._expected_num_tests), fg='rgi') 186 | self._printer.write("\n") 187 | 188 | def _split(self): 189 | """ 190 | Split the test cases into passed and failures 191 | """ 192 | failures = [] 193 | passed = [] 194 | warned = [] 195 | for result in self._test_results_in_order(): 196 | if result.passed: 197 | passed.append(result) 198 | elif result.failed: 199 | failures.append(result) 200 | elif result.warned: 201 | warned.append(result) 202 | 203 | return passed, failures, warned 204 | 205 | def to_junit_xml_str(self, xunit_xml_format='jenkins'): 206 | """ 207 | Convert test report to a junit xml string 208 | """ 209 | _, failures, warned = self._split() 210 | 211 | root = ElementTree.Element("testsuite") 212 | root.attrib["name"] = "testsuite" 213 | root.attrib["errors"] = "0" 214 | root.attrib["failures"] = str(len(failures)) 215 | root.attrib["warned"] = str(len(warned)) 216 | root.attrib["tests"] = str(len(self._test_results)) 217 | root.attrib["hostname"] = socket.gethostname() 218 | 219 | for result in self._test_results_in_order(): 220 | root.append(result.to_xml(xunit_xml_format)) 221 | 222 | if version_info >= (3, 0): 223 | # Python 3.x 224 | xml = ElementTree.tostring(root, encoding="unicode") 225 | else: 226 | # Python 2.x 227 | xml = ElementTree.tostring(root, encoding="utf-8") 228 | return xml 229 | 230 | 231 | class TestStatus(object): 232 | """ 233 | The status of a test 234 | """ 235 | def __init__(self, name): 236 | self._name = name 237 | 238 | @property 239 | def name(self): 240 | return self._name 241 | 242 | def __eq__(self, other): 243 | return isinstance(other, type(self)) and self.name == other.name 244 | 245 | def __repr__(self): 246 | return "TestStatus(%r)" % self._name 247 | 248 | PASSED = TestStatus("passed") 249 | WARNED = TestStatus("warned") 250 | FAILED = TestStatus("failed") 251 | 252 | class TestResult(object): 253 | """ 254 | Represents the result of a single test case 255 | """ 256 | 257 | def __init__(self, name, status, time, output_file_name): 258 | assert status['status'] in (PASSED, 259 | FAILED, 260 | WARNED) 261 | self.name = name 262 | self._status = status 263 | self.time = time 264 | self._output_file_name = output_file_name 265 | 266 | @property 267 | def output(self): 268 | """ 269 | Return test output 270 | """ 271 | file_exists = os.path.isfile(self._output_file_name) 272 | is_readable = os.access(self._output_file_name, os.R_OK) 273 | if file_exists and is_readable: 274 | return read_file(self._output_file_name) 275 | 276 | return "Failed to read output file: %s" % self._output_file_name 277 | 278 | @property 279 | def log_file(self): 280 | if os.path.isfile(self._output_file_name): 281 | return self._output_file_name 282 | else: 283 | return "Error, %s not exists" % self._output_file_name 284 | 285 | @property 286 | def fail_message(self): 287 | return self._status['reasonMsg'] 288 | 289 | @property 290 | def passed(self): 291 | return self._status['status'] == PASSED 292 | 293 | @property 294 | def warned(self): 295 | return self._status['status'] == WARNED 296 | 297 | @property 298 | def failed(self): 299 | return self._status['status'] == FAILED 300 | 301 | def print_status(self, printer, padding=0): 302 | """ 303 | Print the status and runtime of this test result 304 | """ 305 | if self.passed: 306 | printer.write("pass", fg='gi') 307 | printer.write(" ") 308 | elif self.failed: 309 | printer.write("fail", fg='ri') 310 | printer.write(" ") 311 | elif self.warned: 312 | printer.write("warn", fg='rgi') 313 | printer.write(" ") 314 | 315 | my_padding = max(padding - len(self.name), 0) 316 | 317 | printer.write("%s (%.1f seconds)\n" % (self.name + (" " * my_padding), self.time)) 318 | 319 | def to_xml(self, xunit_xml_format): 320 | """ 321 | Convert the test result to ElementTree XML object 322 | """ 323 | test = ElementTree.Element("testcase") 324 | match = re.search(r"(.+)\.([^.]+)$", self.name) 325 | if match: 326 | test.attrib["classname"] = match.group(1) 327 | test.attrib["name"] = match.group(2) 328 | else: 329 | test.attrib["name"] = self.name 330 | 331 | test.attrib["time"] = "%.1f" % self.time 332 | 333 | # By default the output is stored in system-out 334 | system_out = ElementTree.SubElement(test, "system-out") 335 | #system_out.text = self.output 336 | system_out.text = self.log_file 337 | 338 | if self.failed: 339 | failure = ElementTree.SubElement(test, "failure") 340 | failure.attrib["message"] = self.fail_message 341 | 342 | # Store output under if the 'bamboo' format is specified 343 | if xunit_xml_format == 'bamboo': 344 | failure.text = system_out.text 345 | system_out.text = '' 346 | 347 | elif self.warned: 348 | warned = ElementTree.SubElement(test, "warning") 349 | warned.attrib["message"] = self.fail_message 350 | return test 351 | -------------------------------------------------------------------------------- /test_runner.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | # Copyright (c) 2014-2018, Lars Asplund lars.anders.asplund@gmail.com 19 | 20 | """ 21 | Provided functionality to run a suite of test in a robust way 22 | """ 23 | 24 | from __future__ import print_function 25 | 26 | import os 27 | from os.path import join, exists, abspath, basename, relpath 28 | import traceback 29 | import threading 30 | import sys 31 | import time 32 | import logging 33 | import string 34 | from contextlib import contextmanager 35 | import ostools 36 | from test_report import PASSED, FAILED, WARNED 37 | from hashing import hash_string 38 | 39 | LOGGER = logging.getLogger(__name__) 40 | 41 | class TestRunner(object): 42 | """ 43 | Administer the execution of a list of test suites 44 | """ 45 | VERBOSITY_QUIET = 0 46 | VERBOSITY_NORMAL = 1 47 | VERBOSITY_VERBOSE = 2 48 | 49 | def __init__(self, 50 | report, 51 | verbosity=VERBOSITY_NORMAL, 52 | num_threads=1, 53 | fail_fast=False, 54 | dont_catch_exceptions=False, 55 | no_color=False): 56 | self._lock = threading.Lock() 57 | self._fail_fast = fail_fast 58 | self._abort = False 59 | self._local = threading.local() 60 | self._report = report 61 | assert verbosity in (self.VERBOSITY_QUIET, 62 | self.VERBOSITY_NORMAL, 63 | self.VERBOSITY_VERBOSE) 64 | self._verbosity = verbosity 65 | self._num_threads = num_threads 66 | self._stdout = sys.stdout 67 | self._stderr = sys.stderr 68 | self._dont_catch_exceptions = dont_catch_exceptions 69 | self._no_color = no_color 70 | 71 | ostools.PROGRAM_STATUS.reset() 72 | 73 | @property 74 | def _is_verbose(self): 75 | return self._verbosity == self.VERBOSITY_VERBOSE 76 | 77 | @property 78 | def _is_quiet(self): 79 | return self._verbosity == self.VERBOSITY_QUIET 80 | 81 | def run(self, test_suites): 82 | """ 83 | Run a list of test suites 84 | """ 85 | 86 | num_tests = 0 87 | for test_suite in test_suites: 88 | for test_name in test_suite.test_names: 89 | num_tests += 1 90 | if self._is_verbose: 91 | print("Running test: " + test_name) 92 | 93 | if self._is_verbose: 94 | print("Running %i tests" % num_tests) 95 | print() 96 | 97 | self._report.set_expected_num_tests(num_tests) 98 | 99 | scheduler = TestScheduler(test_suites) 100 | 101 | threads = [] 102 | 103 | # Disable continuous output in parallel mode 104 | write_stdout = self._is_verbose and self._num_threads == 1 105 | 106 | try: 107 | sys.stdout = ThreadLocalOutput(self._local, self._stdout) 108 | sys.stderr = ThreadLocalOutput(self._local, self._stdout) 109 | fixed_size = 16 * 1024 * 1024 # 16M 110 | threading.stack_size(fixed_size) 111 | # Start P-1 worker threads 112 | for _ in range(self._num_threads - 1): 113 | new_thread = threading.Thread(target=self._run_thread, 114 | args=(write_stdout, scheduler, num_tests, False)) 115 | threads.append(new_thread) 116 | new_thread.start() 117 | 118 | # Run one worker in main thread such that P=1 is not multithreaded 119 | self._run_thread(write_stdout, scheduler, num_tests, True) 120 | 121 | scheduler.wait_for_finish() 122 | 123 | except KeyboardInterrupt: 124 | LOGGER.debug("TestRunner: Caught Ctrl-C shutting down") 125 | ostools.PROGRAM_STATUS.shutdown() 126 | raise 127 | 128 | finally: 129 | for thread in threads: 130 | thread.join() 131 | threading.stack_size(0) 132 | 133 | sys.stdout = self._stdout 134 | sys.stderr = self._stderr 135 | LOGGER.debug("TestRunner: Leaving") 136 | 137 | def _run_thread(self, write_stdout, scheduler, num_tests, is_main): 138 | """ 139 | Run worker thread 140 | """ 141 | self._local.output = self._stdout 142 | 143 | while True: 144 | test_suite = None 145 | try: 146 | test_suite = scheduler.next() 147 | 148 | with self._stdout_lock(): 149 | for test_name in test_suite.test_names: 150 | print("Starting %s" % test_name) 151 | #print("Output file: %s" % test_suite.test_result_file) 152 | 153 | self._run_test_suite(test_suite, 154 | write_stdout, 155 | num_tests) 156 | 157 | except StopIteration: 158 | return 159 | 160 | except KeyboardInterrupt: 161 | # Only main thread should handle KeyboardInterrupt 162 | if is_main: 163 | LOGGER.debug("MainWorkerThread: Caught Ctrl-C shutting down") 164 | raise 165 | 166 | return 167 | 168 | finally: 169 | if test_suite is not None: 170 | scheduler.test_done() 171 | 172 | def _add_skipped_tests(self, test_suite, results, start_time, num_tests, output_file_name): 173 | for name in test_suite.test_names: 174 | #TODO: set case not run as failed case 175 | results[name]['status'] = FAILED 176 | self._add_results(test_suite, results, start_time, num_tests, output_file_name) 177 | 178 | def _run_test_suite(self, 179 | test_suite, 180 | write_stdout, 181 | num_tests): 182 | """ 183 | Run the actual test suite 184 | """ 185 | #output_file = None 186 | devNull = None 187 | start_time = ostools.get_time() 188 | results = self._fail_suite(test_suite) 189 | 190 | try: 191 | if write_stdout: 192 | """ 193 | If write_stdout enable, use stdout, showing log in terminal 194 | """ 195 | #self._local.output = Tee([self._stdout]) 196 | self._local.output = self._stdout 197 | else: 198 | """ 199 | Open a dummy file os.devnull for writing log file to it, 200 | not showing log in terminal 201 | If you want to save log in a file, use code below: 202 | >>> output_file = open("xxx.log", "w") 203 | >>> self._local.output = Tee([output_file]) 204 | """ 205 | devNull = open(os.devnull, "w") 206 | self._local.output = devNull 207 | #self._local.output = Tee([devNull]) 208 | 209 | results = test_suite.run() 210 | 211 | except KeyboardInterrupt: 212 | self._add_skipped_tests(test_suite, results, start_time, num_tests, test_suite.test_result_file) 213 | raise KeyboardInterrupt 214 | except: 215 | if self._dont_catch_exceptions: 216 | raise 217 | 218 | with self._stdout_lock(): 219 | traceback.print_exc() 220 | finally: 221 | self._local.output = self._stdout 222 | if devNull != None: 223 | devNull.close() 224 | 225 | #for fptr in [output_file]: 226 | # if fptr is None: 227 | # continue 228 | 229 | # fptr.flush() 230 | # fptr.close() 231 | 232 | any_not_passed = any(value['status'] != PASSED for value in results.values()) 233 | 234 | with self._stdout_lock(): 235 | if (any_not_passed or self._is_verbose) and not self._is_quiet and not write_stdout: 236 | #use stdout, print log file contents in terminal. 237 | self._print_output(test_suite.test_result_file) 238 | 239 | self._add_results(test_suite, results, start_time, num_tests, test_suite.test_result_file) 240 | 241 | if self._fail_fast and any_not_passed: 242 | self._abort = True 243 | 244 | def _print_output(self, output_file_name): 245 | """ 246 | Print contents of output file if it exists 247 | """ 248 | with open(output_file_name, "r") as fh: 249 | for line in fh.readlines(): 250 | self._stdout.write(line) 251 | 252 | def _add_results(self, test_suite, results, start_time, num_tests, output_file_name): 253 | """ 254 | Add results to test report 255 | """ 256 | runtime = ostools.get_time() - start_time 257 | time_per_test = runtime / len(results) 258 | 259 | for test_name in test_suite.test_names: 260 | status = results[test_name] 261 | self._report.add_result(test_name, 262 | status, 263 | time_per_test, 264 | output_file_name) 265 | self._report.print_latest_status(total_tests=num_tests) 266 | print() 267 | 268 | @staticmethod 269 | def _fail_suite(test_suite): 270 | """ Return failure for all tests in suite """ 271 | results = {} 272 | for test_name in test_suite.test_names: 273 | results[test_name] = {} 274 | results[test_name]['reasonMsg'] = '' 275 | results[test_name]['status'] = FAILED 276 | return results 277 | 278 | @contextmanager 279 | def _stdout_lock(self): 280 | """ 281 | Enter this lock when printing to stdout 282 | Ensures no additional output is printed during abort 283 | """ 284 | with self._lock: # pylint: disable=not-context-manager 285 | if self._abort: 286 | raise KeyboardInterrupt 287 | yield 288 | 289 | 290 | class Tee(object): 291 | """ 292 | Provide a write method which writes to multiple files 293 | like the unix 'tee' command. 294 | """ 295 | def __init__(self, files): 296 | self._files = files 297 | 298 | def write(self, txt): 299 | for ofile in self._files: 300 | ofile.write(txt) 301 | 302 | def flush(self): 303 | for ofile in self._files: 304 | ofile.flush() 305 | 306 | 307 | class ThreadLocalOutput(object): 308 | """ 309 | Replacement for stdout/err that separates re-directs 310 | output to a thread local file interface 311 | """ 312 | def __init__(self, local, stdout): 313 | self._local = local 314 | self._stdout = stdout 315 | 316 | def write(self, txt): 317 | """ 318 | Write to file object 319 | """ 320 | if hasattr(self._local, "output"): 321 | self._local.output.write(txt) 322 | else: 323 | self._stdout.write(txt) 324 | 325 | def flush(self): 326 | """ 327 | Flush file object 328 | """ 329 | if hasattr(self._local, "output"): 330 | self._local.output.flush() 331 | else: 332 | self._stdout.flush() 333 | 334 | 335 | class TestScheduler(object): 336 | """ 337 | Schedule tests to different treads 338 | """ 339 | 340 | def __init__(self, tests): 341 | self._lock = threading.Lock() 342 | self._tests = tests 343 | self._idx = 0 344 | self._num_done = 0 345 | 346 | def __iter__(self): 347 | return self 348 | 349 | def __next__(self): 350 | """ 351 | Iterator in Python 3 352 | """ 353 | return self.__next__() 354 | 355 | def next(self): 356 | """ 357 | Iterator in Python 2 358 | """ 359 | ostools.PROGRAM_STATUS.check_for_shutdown() 360 | with self._lock: # pylint: disable=not-context-manager 361 | if self._idx < len(self._tests): 362 | idx = self._idx 363 | self._idx += 1 364 | return self._tests[idx] 365 | 366 | raise StopIteration 367 | 368 | def test_done(self): 369 | """ 370 | Signal that a test has been done 371 | """ 372 | with self._lock: # pylint: disable=not-context-manager 373 | self._num_done += 1 374 | 375 | def is_finished(self): 376 | with self._lock: # pylint: disable=not-context-manager 377 | return self._num_done >= len(self._tests) 378 | 379 | def wait_for_finish(self): 380 | """ 381 | Block until all tests have been done 382 | """ 383 | while not self.is_finished(): 384 | time.sleep(0.05) 385 | 386 | 387 | LEGAL_CHARS = string.printable 388 | ILLEGAL_CHARS = ' <>"|:*%?\\/#&;()' 389 | 390 | def _is_legal(char): 391 | """ 392 | Return true if the character is legal to have in a file name 393 | """ 394 | return (char in LEGAL_CHARS) and (char not in ILLEGAL_CHARS) 395 | 396 | 397 | def create_output_path(output_path, test_suite_name): 398 | """ 399 | Create the full output path of a test case. 400 | Ensure no bad characters and no long path names. 401 | """ 402 | output_path = abspath(output_path) 403 | safe_name = "".join(char if _is_legal(char) else '_' for char in test_suite_name) + "_" 404 | hash_name = hash_string(test_suite_name) 405 | 406 | if "YASA_SHORT_TEST_OUTPUT_PATHS" in os.environ: 407 | full_name = hash_name 408 | else: 409 | full_name = safe_name + hash_name 410 | 411 | return join(output_path, full_name) 412 | 413 | -------------------------------------------------------------------------------- /compileBuild.py: -------------------------------------------------------------------------------- 1 | #****************************************************************************** 2 | # * Copyright (c) 2019, XtremeDV. All rights reserved. 3 | # * 4 | # * Licensed under the Apache License, Version 2.0 (the "License"); 5 | # * you may not use this file except in compliance with the License. 6 | # * You may obtain a copy of the License at 7 | # * 8 | # * http://www.apache.org/licenses/LICENSE-2.0 9 | # * 10 | # * Unless required by applicable law or agreed to in writing, software 11 | # * distributed under the License is distributed on an "AS IS" BASIS, 12 | # * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # * See the License for the specific language governing permissions and 14 | # * limitations under the License. 15 | # * 16 | # * Author: Jude Zhang, Email: zhajio.1988@gmail.com 17 | # ******************************************************************************* 18 | import os 19 | import sys 20 | from globals import * 21 | from readCfgFile import * 22 | from utils import * 23 | import tbInfo 24 | from yasaCli import yasaCli 25 | from random import randint 26 | from exceptions import TestcaseUnknown 27 | from color_printer import COLOR_PRINTER 28 | 29 | class compileBuildBase(object): 30 | def __init__(self, cli=None, ini_file=None, simulator_if=None): 31 | """ 32 | Argument object and input build file control 33 | 34 | :param args: The parsed argument object 35 | :param ini_file: user input build file instead of defaultBuildFile 36 | :returns: None 37 | """ 38 | self._cli = cli 39 | self._simulator_if = simulator_if 40 | self._args = self._cli.getParsedArgs() 41 | if ini_file is not None and os.path.exists(ini_file): 42 | self.buildCfg = readBuildCfgFile(ini_file) 43 | else: 44 | self.buildCfg = readBuildCfgFile(defaultBuildFile()) 45 | self._testList = tbInfo.testList() 46 | self._testcasesDir = [] 47 | self._seeds = [] 48 | 49 | def prepareEnv(self): 50 | """ 51 | Prepare build dir and testcase dirs 52 | """ 53 | self.createRootWorkDir() 54 | if not self._args.simOnly: 55 | self.createBuildDir() 56 | self.createCompileCsh() 57 | if not self._args.compOnly: 58 | self.createCaseDir() 59 | self.genTestscaseSimCsh() 60 | 61 | def createRootWorkDir(self): 62 | createDir(defaultWorkDir()) 63 | 64 | def createBuildDir(self): 65 | createDir(self._buildDir, self._args.clean) 66 | 67 | def createCaseDir(self): 68 | if self._args.unique_sim: 69 | createDir(self._testcaseRootDir, self._args.clean) 70 | 71 | @property 72 | def _buildDir(self): 73 | pass 74 | 75 | @property 76 | def _testcaseRootDir(self): 77 | pass 78 | 79 | @property 80 | def testList(self): 81 | return self._testList 82 | 83 | @property 84 | def _testCaseWorkDir(self): 85 | """ 86 | testcases work dir list, uesd in yasaTop, 87 | for creating testcasesuite 88 | """ 89 | return self._testcasesDir 90 | 91 | def _check(self): 92 | """ 93 | testcases work dir list, uesd in yasaTop, 94 | for creating testcasesuite 95 | """ 96 | try: 97 | if not self.testList.check(self._args.test) and self._args.test: 98 | raise TestcaseUnknown(self._args.test) 99 | elif self._args.show: 100 | tbInfo.show(self._args.show) 101 | sys.exit(0) 102 | except TestcaseUnknown as err: 103 | tbInfo.show('test') 104 | COLOR_PRINTER.write('test: ' + str(err) + '\n', fg='ri') 105 | sys.exit(1) 106 | 107 | def generateSeed(self): 108 | """ 109 | generate testcase used seeds. 110 | repeat default is 1 111 | if seed is default 0, used random data. 112 | if seed is not default, used setted seed data. 113 | """ 114 | self._seeds = [] 115 | for i in range(self._args.repeat): 116 | if self._args.seed == 0: 117 | seed = '%d' % (randint(1, 0xffffffff)) 118 | else: 119 | seed = self._args.seed 120 | self._seeds.append(seed) 121 | 122 | def createCompileCsh(self): 123 | """ 124 | generate compile used cshell files in buildDir. 125 | buildDir can change based on single test or group test flow 126 | in single test flow, buildDir change based on unique_sim cli 127 | """ 128 | self._testList.genTestFileList(self._buildDir) 129 | with open(os.path.join(self._buildDir, 'pre_compile.csh'), 'w') as f: 130 | for item in self.buildCfg.preCompileOption(self._args.build): 131 | f.write(item + '\n') 132 | with open(os.path.join(self._buildDir, 'compile.csh'), 'w') as f: 133 | f.write('#!/bin/sh -fe\n') 134 | f.write(self._simulator_if.compileExe() + ' \\' + '\n') 135 | for index, item in enumerate(self.compileCshContent()): 136 | if index == len(self.compileCshContent())-1: 137 | f.write('\t' + item + '\n') 138 | else: 139 | f.write('\t' + item + ' \\' + '\n') 140 | with open(os.path.join(self._buildDir, 'post_compile.csh'), 'w') as f: 141 | for item in self.buildCfg.postCompileOption(self._args.build): 142 | f.write(item + '\n') 143 | 144 | def compileCshContent(self): 145 | """ 146 | compile cshell file content, can be from build.cfg file, userCli 147 | and argparse namespace 148 | """ 149 | cshContent = self.buildCfg.compileOption(self._args.build) 150 | if self._cli.userCliCompileOption(): 151 | cshContent = cshContent + self._cli.userCliCompileOption() 152 | if hasattr(self._args, 'compileOption') and self._args.compileOption: 153 | cshContent = cshContent + self._args.compileOption 154 | return cshContent + ['-f %s' % defaultTestListFile()] + ['-l compile.log'] 155 | 156 | def compileCmd(self): 157 | """ 158 | compilation command is a string of shell command, run in a python subprocess. 159 | when enable lsf subparser, insert lsf cmds at the top of shell command. 160 | """ 161 | compileCmd = 'set -e; chmod a+x pre_compile.csh compile.csh post_compile.csh; ./pre_compile.csh %s; ./compile.csh; ./post_compile.csh;' % self._args.test 162 | if self._args.subparsers == 'lsf': 163 | lsfOptions = self._args.lsfOptions 164 | return "bsub -Is " + " ".join(lsfOptions) + '"%s"' % compileCmd 165 | else: 166 | return compileCmd 167 | 168 | def createSimCsh(self, testcaseDir, seed, isGroup): 169 | """ 170 | simulation cshell file content, can be from build.cfg file, userCli 171 | and argparse namespace 172 | """ 173 | with open(os.path.join(testcaseDir, 'pre_sim.csh'), 'w') as f: 174 | for item in self.buildCfg.preSimOption(self._args.build): 175 | f.write(item + '\n') 176 | if len(self.buildCfg.preSimOption(self._args.build)) >=1 : 177 | f.write(self._args.test + '\n') 178 | with open(os.path.join(testcaseDir, 'sim.csh'), 'w') as f: 179 | #FIXME: if add shebang line, will cause */simv: No match. shell error 180 | #f.write('#!/bin/csh -fe\n') 181 | f.write('#!/bin/sh -fe\n') 182 | if self._simulator_if.name == 'irun': 183 | f.write(self._simulator_if.simExe() + ' \\' + '\n') 184 | elif self._simulator_if.name == 'xrun': 185 | f.write(self._simulator_if.simExe() + " -R -xmlibdirname " + os.path.join(self._buildDir, "xcelium.d") + ' \\' + '\n') 186 | else: 187 | f.write(os.path.join(self._buildDir, self._simulator_if.simExe()) + ' \\' + '\n') 188 | for index, item in enumerate(self.simCshContent()): 189 | if index == len(self.simCshContent())-1: 190 | #TODO: move this line to vcsInterface, because ntb_random_seed is vcs keyword 191 | if self._simulator_if.name == 'irun': 192 | f.write('\t' + '-f ' + os.path.join(self._buildDir, 'test.f') + ' \\' + '\n') 193 | if self._args.seed == 0: 194 | if self._simulator_if.name == 'vcs': 195 | f.write('\t' + '+ntb_random_seed=%s' % seed + ' \\' + '\n') 196 | elif self._simulator_if.name in ['irun', 'xrun' ]: 197 | f.write('\t' + '-svseed %s' % seed + ' \\' + '\n') 198 | if self._args.cov and self._simulator_if.name == 'vcs': 199 | f.write('\t' + '-cm_name %s' % self._args.test + '__' + str(seed) + ' -cm_dir ' + os.path.join(self._buildDir, defaultCovDir()) + ' \\' + '\n') 200 | if isGroup: 201 | f.write('\t' + item + ' >& /dev/null\n') 202 | else: 203 | f.write('\t' + item + '\n') 204 | else: 205 | f.write('\t' + item + ' \\' + '\n') 206 | with open(os.path.join(testcaseDir, 'post_sim.csh'), 'w') as f: 207 | for item in self.buildCfg.postSimOption(self._args.build): 208 | f.write(item + '\n') 209 | 210 | def simCshContent(self): 211 | simContent = [] 212 | simContent = self.buildCfg.simOption(self._args.build) 213 | if self._cli.userCliSimOption(): 214 | simContent = simContent + self._cli.userCliSimOption() 215 | if hasattr(self._args, 'simOption') and self._args.simOption: 216 | simContent = simContent+ self._args.simOption 217 | return simContent + ['-l sim.log'] 218 | 219 | def simCmd(self): 220 | simCmd = 'set -e; chmod a+x pre_sim.csh sim.csh post_sim.csh; ./pre_sim.csh %s; ./sim.csh; ./post_sim.csh;' % self._args.test 221 | if self._args.subparsers == 'lsf': 222 | lsfOptions = self._args.lsfOptions 223 | return "bsub -Is " + " ".join(lsfOptions) + '"%s"' % simCmd 224 | else: 225 | return simCmd 226 | 227 | class singleTestCompile(compileBuildBase): 228 | def __init__(self, cli=None, ini_file='', simulator_if=None): 229 | super(singleTestCompile, self).__init__(cli, ini_file, simulator_if) 230 | try: 231 | self._build = self.buildCfg.getBuild(self._args.build) 232 | except buildUnknown as err: 233 | tbInfo.show('build') 234 | COLOR_PRINTER.write('build: ' + str(err) + '\n', fg='ri') 235 | sys.exit(1) 236 | self.setTestlist() 237 | self._check() 238 | self.generateSeed() 239 | 240 | @property 241 | def _buildDir(self): 242 | """ 243 | when enable unique_sim. buildDir and testcase dir are both under defaultWorkDir. 244 | >>> YASAsim -t sanity1 -u 245 | ${PRJ_NAME}_out/candy_lover 246 | ${PRJ_NAME}_out/sanity1/sanity1__2814985384/ 247 | when use unique_sim, you can run sanity2 and other cases 248 | simulation directly, such as: 249 | >>> YASAsim -t sanity2 -u -so 250 | ${PRJ_NAME}_out/candy_lover 251 | ${PRJ_NAME}_out/sanity2/sanity2__113547301/ 252 | 253 | if not, buildDir is under testcase root dir 254 | >>> YASAsim -t sanity1 255 | ${PRJ_NAME}_out/sanity1/candy_lover 256 | ${PRJ_NAME}_out/sanity1/sanity1__2814985384/ 257 | 258 | Note: group testcases don't have unique_sim support. 259 | """ 260 | if self._args.unique_sim: 261 | return os.path.join(defaultWorkDir(), self._build.name) 262 | elif self._args.test: 263 | return os.path.join(defaultWorkDir(), self._testcaseRootDir, self._build.name) 264 | 265 | @property 266 | def _testcaseRootDir(self): 267 | """ 268 | create testcase dir, if have prefix, add join prefix and test, use '__' 269 | >>> YASAsim -t sanity1 -test_prefix jude 270 | ${PRJ_NAME}_out/jude__sanity1/candy_lover 271 | ${PRJ_NAME}_out/jude__sanity1/sanity1__3758707525/ 272 | """ 273 | return os.path.join(defaultWorkDir(), self._args.testPrefix + '__' + self._args.test if self._args.testPrefix else self._args.test) 274 | 275 | def setTestlist(self): 276 | """ 277 | set testcase list based on testcase loacation 278 | """ 279 | if self._build.testDir: 280 | self._testList.testDir = self._build.testDir 281 | else: 282 | self._testList.testDir = defaultTestDir() 283 | tbInfo.testlist.setTestLists(self._build.name, self._testList) 284 | 285 | def genTestscaseSimCsh(self): 286 | """ 287 | generate testcase sim cshell files and corresponding files loacation 288 | """ 289 | dir = '' 290 | for i in self._seeds: 291 | dir = os.path.join(self._testcaseRootDir, self._args.test + '__' + str(i)) 292 | self._testcasesDir.append(dir) 293 | createDir(dir) 294 | if len(self._seeds) >= 2: 295 | self.createSimCsh(dir, i, 1) 296 | else: 297 | self.createSimCsh(dir, i, 0) 298 | 299 | class groupTestCompile(compileBuildBase): 300 | def __init__(self, cli=None, group_file='', build_file='', simulator_if=None): 301 | super(groupTestCompile, self).__init__(cli, build_file, simulator_if) 302 | if group_file is not None and os.path.exists(group_file): 303 | self.groupCfg = readGroupCfgFile(group_file) 304 | else: 305 | self.groupCfg = readGroupCfgFile(defaultGroupFile()) 306 | try: 307 | self._group = self.groupCfg.testGroup.getGroup(self._args.group) 308 | self._build = self.buildCfg.getBuild(self._group.buildOption) 309 | except groupUnknown as err: 310 | tbInfo.show('group') 311 | COLOR_PRINTER.write('group: ' + str(err) + '\n', fg='ri') 312 | sys.exit(1) 313 | except buildUnknown as err: 314 | tbInfo.show('build') 315 | COLOR_PRINTER.write('build: ' + str(err) + '\n', fg='ri') 316 | sys.exit(1) 317 | self._args.build = self._group.buildOption 318 | self._testcases = self.groupCfg.getTests(self._args.group) 319 | self.setTestlist() 320 | 321 | def createBuildDir(self): 322 | createDir(self._buildDir, True) 323 | 324 | @property 325 | def _buildDir(self): 326 | if self._group.name: 327 | return os.path.join(defaultWorkDir(), self._groupRootDir, self._group.buildOption) 328 | 329 | @property 330 | def _groupRootDir(self): 331 | return os.path.join(defaultWorkDir(), self._args.testPrefix + '__' + self._group.name if self._args.testPrefix else self._group.name) 332 | 333 | def setTestlist(self): 334 | for buildName in self.groupCfg.allBuild: 335 | build = self.buildCfg.getBuild(buildName) 336 | if build.testDir: 337 | self._testList.testDir = build.testDir 338 | else: 339 | self._testList.testDir = defaultTestDir() 340 | tbInfo.testlist.setTestLists(build.name, self._testList) 341 | 342 | def _getSysArgv(self): 343 | """ 344 | Get cli argv from sys.argv, then add it to testcase options. 345 | but '-g' and '-t' are exclusive, because have konwn '-g' option 346 | in __init__ function, so return a sys.argv without '-g' option. 347 | """ 348 | if '-g' in sys.argv[1:]: 349 | sysArgvList = sys.argv[1:] 350 | index = sysArgvList.index('-g') 351 | sysArgvList.pop(index) 352 | sysArgvList.pop(index) 353 | return sysArgvList 354 | 355 | def genTestscaseSimCsh(self): 356 | """ 357 | In this function, k stands for group name, v stands for a list of testcases and 358 | each testcase's arguments. split the testAndOptions, then get testcase's argument 359 | plus '-b' build name and '-t' testname, _cli.parseArgs() use this list as argv for 360 | parsing. By this method, you can get new parsed argument namespace for each testcase. 361 | testcase dir name is: group name + '__' + testcase name + '__' + random seed 362 | """ 363 | dir = '' 364 | for k, v in self._testcases.items(): 365 | for testAndOptions in v: 366 | testAndOptions = '-b %s '%(self.groupCfg.validBuild) + '-t ' + testAndOptions 367 | self._cli.parseArgs(argv=[x for x in testAndOptions.split(' ') if x !=''] + self._getSysArgv()) 368 | self._args = self._cli.getParsedArgs() 369 | self._check() 370 | self.generateSeed() 371 | for i in self._seeds: 372 | dir = os.path.join(self._groupRootDir, k + '__' + self._args.test + '__' + str(i)) 373 | self._testcasesDir.append(dir) 374 | createDir(dir) 375 | self.createSimCsh(dir, i, 1) 376 | 377 | #if __name__ == '__main__': 378 | # import sys 379 | # from Simulator.vcsInterface import vcsInterface 380 | # simulator_if = vcsInterface() 381 | # 382 | # cli = yasaCli("Yet another simulation architecture") 383 | # cli.parseArgs(sys.argv[1:]) 384 | # #userCliCfg.compileOption(args) 385 | # #compile = singleTestCompile(cli, simulator_if=simulator_if) 386 | # #compile.prepareEnv() 387 | # #compile.generateSeed() 388 | # compile = groupTestCompile(cli, simulator_if=simulator_if) 389 | # compile.prepareEnv() 390 | # compile.generateSeed() 391 | --------------------------------------------------------------------------------