Wiki

Ticket #245: metrics.patch

File metrics.patch, 51.9 KB (added by hopscc, 7 years ago)
  • Source/Phases/MetricsPhase.cobra

     
     1class MetricsPhase inherits Phase 
     2    """ 
     3    Run any Code Metrics accumulation or calculation that can be done on the AST. 
     4    Uses a Visitor subclass MetricsGenerator 
     5    """ 
     6 
     7    cue init(c as Compiler) 
     8        base.init(c) 
     9 
     10    get description as String is override 
     11        return 'Calculating Code Metrics' 
     12 
     13    def innerRun is override 
     14        c = .compiler 
     15     
     16        if not c.options.containsKey('metrics') # metrics calc not enabled 
     17            return 
     18         
     19        count = 0 
     20        for mod in c.modules, if mod inherits CobraModule, count += 1 
     21        if count == 0, return  # probably all sharp modules 
     22 
     23        MetricsGeneratorVisitor().gen(c) 
     24 
  • Source/BackEndClr/SharpGenerator.cobra

     
    7272        _modules.add(SharpModule(fileName, _verbosity)) 
    7373 
    7474    def writeSharpRunAllTests(cw as CurlyWriter) 
    75         runner = .options['test-runner'] to String  # ex: Cobra.Lang.CobraCore.runAllTests, ex: MyProgram.runTests 
     75        runner = .options.getSubOpt('testifyArgs', 'runner') to String  # ex: Cobra.Lang.CobraCore.runAllTests, ex: MyProgram.runTests 
    7676        if runner <> 'nil' 
    7777            if runner.endsWith('()'), runner = runner[:-2] 
    7878            if runner.startsWith('Cobra.Lang.'), runner = 'CobraLangInternal.' + runner['Cobra.Lang.'.length:] 
  • Source/files-to-compile.text

     
    3535DocGenerator 
    3636SyntaxHighlighter 
    3737 
     38MetricsGenerator 
     39 
    3840Phases/Phase.cobra 
    3941Phases/BindRunTimeLibraryPhase.cobra 
    4042Phases/ReadLibrariesPhase.cobra 
     
    4749Phases/BindImplementationPhase.cobra 
    4850Phases/CountNodesPhase.cobra 
    4951Phases/SuggestDefaultNumberPhase.cobra 
     52Phases/MetricsPhase.cobra 
    5053 
    5154BackEndCommon/CurlyWriter 
    5255BackEndCommon/CurlyGenerator 
  • Source/MetricsGenerator.cobra

     
     1""" 
     2Runs in metrics phase if enabled, Walks AST and generates various code metrics 
     3    ToDate: LinesOfCode and a simple Mccabe/Cyclomatic Complexity Calculation. 
     4""" 
     5 
     6 
     7use System.Reflection 
     8 
     9class MethodMetricsAccumulator 
     10    """Value Object for accumulating metrics per Method""" 
     11    var name = '' 
     12    var mcc = 0 # mccabe Complexity calc accumulator 
     13 
     14     
     15class MetricsGeneratorVisitor inherits Visitor 
     16    """ 
     17    Example invocations: 
     18     
     19    cobra -metrics Utils.cobra 
     20    cobra -metrics:loc -files:files-to-compile.text 
     21    cobra -metrics:mcc=10,loc=50 -lib:System.Web 
     22 
     23    Potential options: 
     24        * warnings vs Table 
     25 
     26    Also, see TODO comments in the code. 
     27    """ 
     28 
     29    var methodAccStack =  Stack<of MethodMetricsAccumulator>() 
     30     
     31    # Threshold values for emitting message, set from optionValues 
     32    var locThreshold = 0 
     33    var mccThreshold = 0 
     34 
     35    var warn = false 
     36    var metOpt as OptionValues?     # commandline optionValues for accessing metricsOptions 
     37    var linesOut =0 
     38     
     39    cue init 
     40        base.init 
     41         
     42    get methodName as String is override 
     43        return 'gen' 
     44         
     45    ## Generate 
     46 
     47    def dispatch(obj as Object?) 
     48        if true 
     49            base.dispatch(obj) 
     50        else 
     51            try 
     52                base.dispatch(obj) 
     53            catch exc as Exception 
     54                while exc inherits TargetInvocationException and exc.innerException, exc = exc.innerException to ! 
     55                print 
     56                print ' *** exception for obj [obj]:' 
     57                print ' ### [exc.typeOf.name]: [exc.message]' 
     58 
     59    def gen(c as Compiler) 
     60        """ Entry point for starting Visitor scan for metric calc""" 
     61             
     62        .metOpt = c.options 
     63        .locThreshold = .metOpt.getSubOpt('metrics','loc', 999) 
     64        .mccThreshold = .metOpt.getSubOpt('metrics','mcc', 999) 
     65        assert .metOpt 
     66        # .dispatch(c.globalNS) 
     67        .dispatch(c.modules) 
     68         
     69 
     70    #def gen(obj as Object?) 
     71    #   if obj is nil, return 
     72    #   msg = '*** Unbound visitation for type [obj.getType]: [obj]' 
     73    #   print msg 
     74 
     75    def gen(mod as Module) 
     76        # This is to capture "native" modules like SharpModule 
     77        pass 
     78 
     79    def gen(mod as AssemblyModule) 
     80        pass 
     81 
     82    def gen(mod as CobraModule) 
     83        try 
     84            for decl in mod.topNameSpace.declsInOrder 
     85                .dispatch(decl) 
     86        finally 
     87            pass #.finishFile 
     88     
     89 
     90    def gen(ns as NameSpace) 
     91        for decl in ns.declsInOrder, .dispatch(decl) 
     92 
     93    # Box includes all below; Class, Interface, Struct, Extension 
     94    def gen(box as Box) 
     95        .genBoxMembers(box) 
     96/# 
     97    def gen(cl as Class) 
     98        # TODO: inherits, implements, generic constraints 
     99        .genBoxMembers(cl) 
     100 
     101    def gen(ifc as Interface) 
     102        # TODO: inherits, generic constraints 
     103        #.writeDocString(ifc.docString) 
     104        .genBoxMembers(ifc) 
     105 
     106    def gen(struc as Struct) 
     107        # TODO: implements, generic constraints 
     108        #.writeDocString(struc.docString) 
     109        .genBoxMembers(struc) 
     110 
     111    def gen(ext as Extension) 
     112        #.writeDocString(ext.docString) 
     113        .genBoxMembers(ext) 
     114#/ 
     115 
     116    def genBoxMembers(box as Box) 
     117        typeDecls = .sorted(for decl in box.declsInOrder where decl inherits IType) 
     118        if typeDecls.count 
     119            .genBoxMembers(typeDecls) 
     120 
     121        sharedDecls = .sorted(for decl in box.declsInOrder where decl.isShared and not (decl to Object) inherits IType) 
     122        if sharedDecls.count 
     123            .genBoxMembers(sharedDecls) 
     124 
     125        nonsharedDecls = .sorted(for decl in box.declsInOrder where not decl.isShared) 
     126        if nonsharedDecls.count 
     127            .genBoxMembers(nonsharedDecls) 
     128 
     129    def genBoxMembers(decls as IList<of IBoxMember>) 
     130        for decl in decls 
     131            .dispatch(decl) 
     132        #for i in decls.count 
     133            #decl = decls[i] 
     134                 
     135    def gen(method as AbstractMethod) 
     136        fullName = '[method.parentBox.name].[method.name]' 
     137        mmacc = MethodMetricsAccumulator() 
     138        mmacc.name = fullName 
     139        mmacc.mcc = 0 
     140        .methodAccStack.push(mmacc) 
     141         
     142        .genStatementList(method.statements) 
     143        #TODO requirepart, ensurepart 
     144 
     145        mmacc = .methodAccStack.pop 
     146        if .metOpt.hasSubOpt('metrics','loc') and method.statements.count 
     147            stmts = method.statements 
     148            startLine = stmts[0].token.lineNum 
     149            numLines = stmts[stmts.count-1].lastToken.lineNum - startLine + 1 
     150             
     151        if .metOpt.hasSubOpt('metrics','mcc') 
     152            mcc = mmacc.mcc 
     153             
     154        if mcc > .mccThreshold or numLines > .locThreshold  # 10 or 25 and 100 
     155            # Original McCabe had count <10 (7+/-2) Great, 10-15 OK, >50 Unmaintainable 
     156            # later research suggests   <11 Great, < 25 OK, >25 =>  >=30% correlation to > faults (http://www.sdtimes.com/content/article.aspx?ArticleID=31820) 
     157            metricsLine = StringBuilder('[fullName.padLeft(50)]\t\t') 
     158            if .metOpt.hasSubOpt('metrics','loc') 
     159                metricsLine.append('[numLines]\t') 
     160            if .metOpt.hasSubOpt('metrics','mcc') 
     161                metricsLine.append('[mcc]') 
     162            #if .warn 
     163            #   if mcc > .mccThreshold 
     164            #       _warning('Method [fullName] complexity=[mcc]  McCabe Cyclomatic Complexity > [.ccMetricsThreshold].') 
     165            #   if numLines > .locThreshold  
     166            #       _warning('Method [fullName] LOC=[numLines]: LOC > [.locThreshold] ([stmts.count] toplevel statements).')  
     167            #else 
     168            if .linesOut == 0 
     169                hdrLine = StringBuilder('...METHOD...'.padLeft(50)) 
     170                hdrLine.append('\t\t') 
     171                if .metOpt.hasSubOpt('metrics','loc'), hdrLine.append('Lines\t') 
     172                if .metOpt.hasSubOpt('metrics','mcc'), hdrLine.append('McCabe') 
     173                print hdrLine.toString   
     174            print metricsLine.toString 
     175            .linesOut += 1   
     176                 
     177    def genStatementList(statements as List<of Stmt>) 
     178        if statements.count 
     179            for stmt in statements 
     180                .dispatch(stmt) 
     181             
     182    # convenience methods for bumping stack top mccabe metrics accumulator field 
     183    def incMcCabe 
     184        .methodAccStack.peek.mcc += 1 
     185         
     186    # For metrics to date (mccabe) we are only interested in branch type statements.  
     187    # For anything else with statement lists or blocks we just need to dispatch on them  
     188 
     189    # Branch Statement and sub parts 
     190    def gen(brStmt as BranchStmt) 
     191        .incMcCabe 
     192        #.genExpr(brStmt.expr)       
     193        for onPart in brStmt.onParts 
     194            .dispatch(onPart) 
     195        if brStmt.elsePart 
     196            .dispatch(brStmt.elsePart) 
     197         
     198    def gen(onPart as BranchOnPart) 
     199        #for expr in onPart.exprs 
     200        #   .genExpr(expr) 
     201        .dispatch(onPart.block) 
     202     
     203    def gen(expct as ExpectStmt)     
     204        .dispatch(expct.block) 
     205         
     206    def gen(forStmt as ForStmt) 
     207        .incMcCabe 
     208        .dispatch(forStmt.block) 
     209     
     210    def gen(ifStmt as IfStmt) 
     211        .incMcCabe 
     212        #.genExpr(ifStmt.cond) 
     213        # TODO possibly increment again for each and/or in condition list 
     214        .dispatch(ifStmt.trueStmts) 
     215        if ifStmt.falseStmts 
     216            .dispatch(ifStmt.falseStmts) 
     217             
     218    def gen(l as LockStmt) 
     219        #.genExpr(l.expr) 
     220        .dispatch(l.block) 
     221         
     222    def gen(prtStmt as PrintRedirectStmt) 
     223        .dispatch(prtStmt.block) 
     224         
     225    def gen(tryStmt as TryStmt) 
     226        .dispatch(tryStmt.tryBlock) 
     227        for cb in tryStmt.catchBlocks 
     228            .dispatch(cb)    
     229        if tryStmt.successBlock 
     230            .dispatch(tryStmt.successBlock) 
     231        if tryStmt.finallyBlock 
     232            .dispatch(tryStmt.finallyBlock) 
     233         
     234    def gen(cb as CatchBlock) 
     235        .dispatch(cb.block) 
     236 
     237    def gen(us as UsingStmt) 
     238        #.genExpr(us.varExpr) # UsingStmt needs property for varExpr 
     239        #.genExpr(us.initExpr) 
     240        .dispatch(us.block) 
     241         
     242    def gen(aws as AbstractWhileStmt) 
     243        .incMcCabe 
     244        #.genExpr(aws.expr) 
     245        # TODO possibly increment again for each and/or in condition list 
     246        .dispatch(aws.block) 
     247 
     248    def gen(block as BlockStmt) 
     249        for stmt in block.stmts 
     250            .dispatch(stmt) 
     251     
     252    #Expressions 
     253    def genExpr(expr as Expr) 
     254        pass 
     255        #.dispatch(expr)             
     256                 
     257     
     258    # catch everything else and ignore 
     259    def gen(node as INode) 
     260        pass 
     261         
     262 
     263    ## Sorting members 
     264 
     265    def sorted(decls as IList<of IBoxMember>) as List<of IBoxMember> 
     266        """ 
     267        Return the box members in a logically sorted order. 
     268        First order is by type of member (enums, vars, constructors, props, indexers, methods). 
     269        Second order is alphabetical. 
     270        Third order is parameter count. 
     271        """ 
     272        t = List<of IBoxMember>(decls) 
     273        t.sort(ref .compareMembers) 
     274        return t 
     275 
     276    def compareMembers(a as IBoxMember, b as IBoxMember) as int is shared 
     277        if a.getType is b.getType 
     278            diff = a.name.compareTo(b.name)  # note that Cobra disallows member names that differ only by case 
     279            if diff == 0 
     280                if a inherits AbstractMethod 
     281                    diff = a.params.count.compareTo((b to AbstractMethod).params.count) 
     282                else if a inherits Indexer 
     283                    # b must also be an indexer since their names were the same 
     284                    diff = a.params.count.compareTo((b to Indexer).params.count) 
     285            else if a.name.startsWith('_') and not b.name.startsWith('_') 
     286                diff = .compareNamesWithFirstUnderscored(a.name, b.name) 
     287            else if not a.name.startsWith('_') and b.name.startsWith('_') 
     288                diff = -1 * .compareNamesWithFirstUnderscored(b.name, a.name) 
     289        else 
     290            diff = .rank(a).compareTo(.rank(b)) 
     291        return diff 
     292 
     293    def compareNamesWithFirstUnderscored(a as String, b as String) as int is shared 
     294        require 
     295            a.startsWith('_') 
     296            not b.startsWith('_') 
     297        body 
     298            a = a[1:] 
     299            if a == b, return 1  # underscored/protected member comes last 
     300            else, return a.compareTo(b) 
     301             
     302    def rank(obj as IBoxMember) as int is shared 
     303        if obj inherits EnumDecl, return 0 
     304        if obj inherits MethodSig, return 10 
     305        if obj inherits Box, return 20 
     306        if obj inherits BoxConst, return 25 
     307        if obj inherits BoxVar, return 30 
     308        if obj inherits Initializer, return 40 
     309        if obj inherits BoxEvent, return 45 
     310        if obj inherits Property, return 50 
     311        if obj inherits Indexer, return 60 
     312        if obj inherits Method, return 70 
     313        throw FallThroughException(obj) 
  • Source/Statements.cobra

     
    199199        _onParts = onParts 
    200200        _elsePart = elsePart 
    201201 
     202    get lastToken as IToken is override 
     203        if _elsePart, return _elsePart.lastToken 
     204        return _onParts[_onParts.count-1].lastToken # _onParts[[-1]].lastToken 
     205 
    202206    def addSubFields 
    203207        base.addSubFields 
    204208        .addField('expr', .expr) 
     
    232236        _exprs = exprs 
    233237        _block = block 
    234238 
     239    get lastToken as IToken 
     240        return _block.lastToken 
     241 
    235242    def addSubFields 
    236243        base.addSubFields 
    237244        .addField('exprs', _exprs) 
  • Source/Compiler.cobra

     
    5757        """ 
    5858        Return binary output name for compilation of files for this backend. 
    5959        """ 
     60         
     61class NilBackEnd inherits BackEnd 
     62    """Null BackEnd Implementation for suppressing any BackEnd processing.""" 
     63     
     64    cue init(c as Compiler) 
     65        base.init(c) 
    6066 
     67    def makePhases(phases as IList<of Phase>) is override 
     68        pass 
    6169 
     70    def computeOutName as String is override 
     71        return .compiler.computeOutNameSharp 
     72         
     73 
    6274class Compiler implements ITypeProvider, IWarningRecorder, IErrorRecorder, ICompilerForNodes is partial 
    6375    """ 
    6476    General notes: 
     
    297319        .backEnd.makePhases(phases) 
    298320        if .options.boolValue('timeit') 
    299321            phases.add(CountNodesPhase(this)) 
     322        if .options.containsKey('metrics') 
     323            phases.add(MetricsPhase(this)) 
    300324        for i, phase in phases.numbered, phase.stableOrder = i 
    301325        phases.sort  # see Phase.order and .compareTo 
    302326        return phases 
     
    14171442            on 'clr',  _backEnd = ClrBackEnd(this) 
    14181443            on 'jvm',  _backEnd = JvmBackEnd(this) 
    14191444            on 'objc', _backEnd = ObjcBackEnd(this) 
     1445            on 'nil',  _backEnd = NilBackEnd(this) # Do FrontEnd execution only 
    14201446            else, throw FallThroughException(.options.get('back-end')) 
    14211447 
    14221448 
  • Source/CommandLine.cobra

     
    3434     
    3535    get type as String 
    3636        require .isUnpacked 
    37         ensure result in ['accumulator', 'args-list', 'bool', 'int', 'menu', 'set', 'string'] 
     37        ensure result in ['accumulator', 'args-list', 'bool', 'int', 'menu', 'set', 'string', 'multival'] 
    3838        return _type 
    3939 
    4040    get synonyms from var 
     
    7777        if _type == 'main' 
    7878            _isMain, _type = true, 'bool' 
    7979        if .containsKey('is-main'), _isMain = this['is-main'] to bool 
    80         assert _type in ['accumulator', 'args-list', 'bool', 'int', 'menu', 'set', 'string'] 
     80        assert _type in ['accumulator', 'args-list', 'bool', 'int', 'menu', 'set', 'string', 'multival'] 
    8181        if .containsKey('synonyms') 
    8282            for syn in this['synonyms'] 
    8383                _synonyms.add(syn) 
     
    9494        _unpackPlatforms 
    9595        _isUnpacked = true 
    9696        assert .type=='menu' implies .choices.count > 0 
    97         assert .choices.count <> 0 implies .type in ['menu', 'set'] 
     97        assert .choices.count <> 0 implies .type in ['menu', 'set', 'multival'] 
     98        assert .type=='multival' implies .containsKey('subOptions') and .containsKey('choices') 
     99        assert .type=='multival' implies this['subOptions'].specsList.count == this['choices'].count 
     100        assert .type=='multival' implies .choices.count > 0 
    98101 
    99102    def _unpackPlatforms 
    100103        if not .containsKey('platforms') 
     
    103106            assert platform in ['jvm', '.net', 'objc'] 
    104107        this['platforms'] = Set<of String>(this['platforms'] to List<of String>) 
    105108 
     109    def defaultIfOn as dynamic 
     110        if .containsKey('defaultIfOn'), return this['defaultIfOn'] 
     111        if .containsKey('default'), return this['default'] 
     112        return 'on'  
     113         
     114    def subOptions as List<of OptionSpec> 
     115        """Return SubOption list ( of OptionSpec) or empty list if no subOptions""" 
     116        if .containsKey('subOptions')   
     117            clos = this['subOptions'] 
     118            assert clos inherits CommandLineOptionSpecs 
     119            return clos.specsList  
     120        return List<of OptionSpec>() 
     121        # TODO: cache this in unpack and just return cached value from here 
    106122 
     123    def subOptionValue(name as String, keyList as vari String) as dynamic 
     124        """ 
     125        Find subOptions spec with given name; using keys from keyList return the value for  
     126        the first existing key in spec.  
     127        Return empty string if no subOptions or no matching keys found. 
     128        """ 
     129        for subOpt in .subOptions 
     130            if subOpt['name'] == name   
     131                for key in keyList 
     132                    if subOpt.containsKey(key), return subOpt[key] 
     133                break    
     134        return ''    
     135         
     136    def subOptionSpec(name as String) as OptionSpec 
     137        """Return subOption for name or fail""" 
     138        require .containsKey('subOptions')   
     139        for subOpt in .subOptions 
     140            if subOpt['name'] == name   
     141                return subOpt 
     142        assert false, name 
     143        return  OptionSpec() 
     144         
    107145class CommandLineOptionSpecs 
    108146    """ 
    109147    Returns the command line option specifications, via .specsList, as a List of OptionSpec. 
     
    114152 
    115153    cue init 
    116154        base.init 
    117         for rawSpec in _rawCommandLineOptionSpecs 
     155        __initInternal(_rawCommandLineOptionSpecs) 
     156             
     157    cue init(rawOptionSpecs)  
     158        base.init 
     159        __initInternal(rawOptionSpecs) 
     160         
     161    def __initInternal(rawOptionSpecs)  
     162        assert rawOptionSpecs.count 
     163        for rawSpec in rawOptionSpecs 
    118164            spec = OptionSpec() 
    119165            if rawSpec inherits Dictionary<of String, Object> 
    120                 for key in rawSpec.keys, spec[key] = rawSpec[key] 
     166                for key in rawSpec.keys 
     167                    spec[key] = if(key == 'subOptions', CommandLineOptionSpecs(rawSpec[key]), rawSpec[key]) 
    121168            else if rawSpec inherits Dictionary<of String, String> 
    122169                for key in rawSpec.keys, spec[key] = rawSpec[key] 
    123170            else 
    124171                throw FallThroughException(rawSpec.getType) 
    125172            spec.unpack 
    126173            _specs.add(spec) 
    127  
     174                 
    128175    def specsList as List<of OptionSpec> 
    129176        """ 
    130177        Returns the option specs in the order they were declared. 
    131178        """ 
    132179        return _specs 
    133180 
     181    # Spec for each cobra compiler commandline option - List of Dictionaries 
     182    # TODO: doc the types 
     183    #type: main 
     184    #type: string 
     185    #type: int 
     186    #type: bool 
     187    #type: set 
     188    #type: menu 
     189    # 
     190    # type: multival supports a single cmdline option supporting multiple subOptions as comma separated name[=value] pairs 
     191    #   e.g. -multiValueDefault  -multival:key0,key1=15,key2=fred  
     192    #   Uses standard entries  
     193    #   (e.g. 'default' for a default value (commaSep list of pairs) if the cmdline option not given) 
     194    #   On the cmdline you can specify just the option (without any subOptions - to enable a default set) 
     195    #       this uses the value of entry 'defaultIfOn' (or 'default' if non existent.) 
     196    #   Must have a  'choices' entry which is a list of the recognised subOption names AND  
     197    #   Must have a 'subOptions' entry whose contents is a list of Dictionaries, one for each subOption supported 
     198    #       Each subOption dict must have at least entry for 'name' but more commonly also  
     199    #       'type', 'description' (for help display) and possibly entries for defaults  
     200    #       'defaultIfKeyOnly' and  'default' 
     201    #       On cmdline can give just a subOption( without '=value'). If want to support this and its different from  
     202    #           a value for 'default' (below) provide an entry for 'defaultIfKeyOnly' giving the value for this case. 
     203    #       If want a subOption value set if the option is enabled regardless of the subOption being given 
     204    #           then provide a 'default' entry. 
     205    #    
    134206    var _rawCommandLineOptionSpecs = [ 
    135207        { 
    136208            'name': 'about', 
     
    332404            'args': 'TYPENAME', 
    333405        }, 
    334406        { 
     407            'name': 'metrics', 
     408            'type': 'multival', 
     409            'args': r'[loc[=value],][mcc[=value]...]', 
     410            'description': 'Enable metrics calculations; name specific metrics to be done and any threshold values as comma separated name=value pairs.', 
     411            'choices': ['loc', 'mcc'],  #allowable subOption keys 
     412            'example': ['loc=80,mcc=10', 'mcc,loc=50', 'mcc=12', 'mcc,loc'], 
     413            #'default': 'loc=100,mcc=25',       # default if not specify name at all (none) 
     414            'defaultIfOn': 'loc=100,mcc=25',    # default (what subOpt values enabled) if only specify name 
     415            'subOptions' : [ 
     416                { 
     417                    'name': 'loc', 
     418                    'type': 'int', 
     419                    'description': 'Show methodname and number of codelines if greater than threshold value.',  
     420                    'defaultIfKeyOnly': '100',  # value to set if only specify subOpt name 
     421                },   
     422                { 
     423                    'name': 'mcc', 
     424                    'type': 'int', 
     425                    'description': 'Show method name and McCabe Complexity Calculation value if omplexity more than threshold. Reasonable values are in range 12-30', 
     426                    'defaultIfKeyOnly': '25', # value to set if only specify subOpt name 
     427                }, 
     428            ], 
     429        }, 
     430        { 
    335431            'name': 'namespace', 
    336432            'synonyms': ['name-space', 'ns'], 
    337433            'type': 'string', 
     
    415511            'args': '"arg1 arg2"', 
    416512        }, 
    417513        { 
     514            'name': 'parse', 
     515            'synonyms': ['syntax', 'p'], 
     516            'description': 'Parse only, no backend phases (native codegen or compilation).',  
     517            'type': 'main', 
     518        }, 
     519        { 
    418520            'name': 'target', 
    419521            'synonyms': ['t'], 
    420522            'description': 'Build a specific target.', 
     
    429531            'type': 'main', 
    430532        }, 
    431533        { 
    432             'name': 'test-runner', 
    433             'type': 'string', 
    434             'description': 'Specify the method to invoke to run the unit tests. The method must be "shared". Typically the method will make use of classes in Cobra.Lang.Test to set up and initiate the test run.', 
    435             'default': 'Cobra.Lang.CobraCore.runAllTests', 
    436             'args': 'QUALIFIED-METHOD-NAME|nil', 
    437         }, 
    438         { 
    439534            'name': 'testify', 
    440             'description': '...', 
     535            'description': 'Run Cobra compiler test suite', 
    441536            'type': 'main', 
    442537            'developer-only': true, 
    443538        }, 
    444539        { 
    445             'name': 'testify-results', 
    446             'description': 'The filename to write the testify results to. Progress is still written to console.', 
    447             'type': 'string', 
    448             'default': 'r-testify', 
     540            'name': 'testifyArgs', 
     541            'type': 'multival', 
     542            'description': 'Set names and values for controlling testify as a list of comma separated name=value pairs.', 
     543            'choices': ['resultsFile', 'runner', 'numThreads'],  #allowable subOption keys 
     544            'example': ['numThreads=4,resultsFile=testResult', 'runner=Core.myTestRunner', 'numThreads=2'], 
    449545            'developer-only': true, 
     546            'default': 'resultsFile=r-testify,runner=Cobra.Lang.CobraCore.runAllTests,numThreads=1', 
     547            'subOptions' : [ 
     548                { 
     549                    'name': 'resultsFile', 
     550                    'type': 'string', 
     551                    'description': 'Filename to write the testify results to. Progress is still written to console.', 
     552                    'default': 'r-testify',        # value to default to if not specify name at all 
     553                    #'defaultIfKeyOnly': 'r-testify', # value to set if only specify subOpt name 
     554                },   
     555                { 
     556                    'name': 'runner', 
     557                    'type': 'string', 
     558                    'description': 'method to invoke to run the unit tests. The method must be "shared". Typically the method will make use of classes in Cobra.Lang.Test to setup and initiate the test run.', 
     559                    'default': 'Cobra.Lang.CobraCore.runAllTests', 
     560                }, 
     561                { 
     562                    'name': 'numThreads', 
     563                    'type': 'int', 
     564                    'description': 'Number of threads to run the testify testsuite in.', 
     565                    'default': '1', 
     566                }, 
     567            ], 
    450568        }, 
    451569        { 
    452             'name': 'testify-threads', 
    453             'description': '...', 
    454             'type': 'int', 
    455             'developer-only': true, 
    456         }, 
    457         { 
    458570            'name': 'timeit', 
    459571            'description': 'Gives the total duration of running cobra (including the target program, if it is to be run). This is "wall time", not "cpu time".', 
    460572            # although this option is implied by 'testify', the description does not say so, since 'testify' is a hidden option 
     
    488600            'type': 'main', 
    489601        }, 
    490602    ] 
     603     
    491604 
    492  
    493605class CommandLine 
    494606    """ 
    495607    The main options that control the command line's behavior are: 
     
    589701            dest = _htmlWriter to TextWriter 
    590702        else 
    591703            dest = Console.out 
    592         if _htmlWriter 
    593             stylePath = Path.combine(Path.getDirectoryName(CobraCore.exePath), 'styles-output-html.css') 
    594             _htmlWriter.writeHtml('<html><head><link href="file://[stylePath]" rel=stylesheet type="text/css"></head><body>[_htmlWriter.newLine]') 
     704             
     705        _runPreamble 
    595706        print to dest 
    596707            paths = _pathList to ! 
    597708            options = _options 
     
    609720                print 'Paths:' 
    610721                for path in paths 
    611722                    print '    [path]' 
     723 
    612724            if options.boolValue('testify') 
    613725                .doTestify(paths) 
    614726            else if options.boolValue('run') 
     
    617729                .doTest(paths) 
    618730            else if options.boolValue('compile') 
    619731                .doCompile(paths) 
     732            else if options.boolValue('parse') 
     733                .doParse(paths) 
    620734            else if options.boolValue('highlight') 
    621735                .doHighlight(paths) 
    622736            else if options.boolValue('document') 
     
    635749                .doHelp 
    636750            else 
    637751                .doRun(paths) 
     752            _runPostamble 
     753                 
     754    def _runPreamble         
    638755        if _htmlWriter 
     756            stylePath = Path.combine(Path.getDirectoryName(CobraCore.exePath), 'styles-output-html.css') 
     757            _htmlWriter.writeHtml('<html><head><link href="file://[stylePath]" rel=stylesheet type="text/css"></head><body>[_htmlWriter.newLine]') 
     758 
     759    def _runPostamble 
     760        if _htmlWriter 
    639761            _htmlWriter.writeHtml('</body></html>[_htmlWriter.newLine]') 
    640762 
    641763    def doHighlight(paths as List<of String>) as Compiler 
     
    650772            Node.setCompiler(nil) 
    651773        return comp 
    652774 
     775    def doParse(paths as List<of String>) as Compiler 
     776        """ 
     777        FrontEnd (parse) Processing only. Tokenise..Parse..Bindings.  
     778        No Back end processing; No codegen, no native code compilation  
     779        """ 
     780        .options['back-end'] = 'nil' 
     781        return .doCompile(paths, true, false, nil) 
     782             
    653783    def doCompile(paths as List<of String>) as Compiler 
    654784        return .doCompile(paths, true, false, nil) 
    655785 
     
    679809            # Each phase of the compiler may throw an exception to stop compilation. 
    680810            # Before doing so, it prints its errors. 
    681811            assert c.errors.count>0 
    682             if _options.containsKey('editor') 
    683                 spec = _options['editor'] to String? 
    684             else 
    685                 spec = Environment.getEnvironmentVariable('COBRA_EDITOR') 
    686             if spec and spec <> '' 
    687                 if spec.indexOf('FILE')==-1 
    688                     .error('Missing FILE from editor spec.') 
    689                 if spec.indexOf('LINE')==-1 
    690                     .error('Missing LINE from editor spec.') 
    691                 i = spec.indexOf('_') 
    692                 if i == -1 
    693                     i = spec.indexOf(' ') 
    694                     if i == -1 
    695                         .error('Missing underscore or space from editor spec.') 
    696                 exeName = spec.substring(0, i) 
    697                 args = spec.substring(i+1) 
    698                 for error in c.errors 
    699                     if error.isError and error.hasSourceSite 
    700                         if error.fileName.trim <> '' 
    701                             # trace error.fileName, error.lineNum 
    702                             args = args.replace('FILE', error.fileName) 
    703                             args = args.replace('LINE', error.lineNum.toString) 
    704                             p = System.Diagnostics.Process() 
    705                             p.startInfo.fileName = exeName 
    706                             p.startInfo.arguments = args 
    707                             p.startInfo.useShellExecute = false 
    708                             if _verbosity >= 3 
    709                                 print 'Running: [p.startInfo.fileName] [p.startInfo.arguments]' 
    710                             try 
    711                                 p.start 
    712                                 p.waitForExit  # TODO: is this really needed? 
    713                             catch exc as Exception 
    714                                 print 'Cannot invoke editor:' 
    715                                 print '    Command: [p.startInfo.fileName] [p.startInfo.arguments]' 
    716                                 print '    Exception: [exc]' 
    717                             break 
     812            editorSpec = if(_options.containsKey('editor'), _options['editor'] to String?, Environment.getEnvironmentVariable('COBRA_EDITOR')) 
     813            if editorSpec and editorSpec.length 
     814                _editorOnErrorLines(c, editorSpec to !) 
     815     
    718816        CobraMain.linesCompiled = c.linesCompiled  
    719817        CobraMain.nodesCompiled = c.nodesCompiled  
    720818        CobraMain.tokensCompiled = c.tokensCompiled  
    721819        return c 
    722820 
     821    def _editorOnErrorLines(c as Compiler, editorSpec as String) 
     822        if editorSpec.indexOf('FILE')==-1,  .error('Missing FILE from editor spec.') 
     823        if editorSpec.indexOf('LINE')==-1,  .error('Missing LINE from editor spec.') 
     824        i = editorSpec.indexOf('_') 
     825        if i == -1 
     826            i = editorSpec.indexOf(' ') 
     827            if i == -1, .error('Missing underscore or space from editor spec.') 
     828 
     829        editorExe = editorSpec.substring(0, i) 
     830        args = editorSpec.substring(i+1) 
     831        for error in c.errors 
     832            if error.isError and error.hasSourceSite 
     833                if error.fileName.trim <> '' 
     834                    # trace error.fileName, error.lineNum 
     835                    args = args.replace('FILE', error.fileName) 
     836                    args = args.replace('LINE', error.lineNum.toString) 
     837                    p = System.Diagnostics.Process() 
     838                    p.startInfo.fileName = editorExe 
     839                    p.startInfo.arguments = args 
     840                    p.startInfo.useShellExecute = false 
     841                    if _verbosity >= 3 
     842                        print 'Running: [p.startInfo.fileName] [p.startInfo.arguments]' 
     843                    try 
     844                        p.start 
     845                        p.waitForExit  # TODO: is this really needed? 
     846                    catch exc as Exception 
     847                        print 'Cannot invoke editor:' 
     848                        print '    Command: [p.startInfo.fileName] [p.startInfo.arguments]' 
     849                        print '    Exception: [exc]' 
     850                    break 
     851         
    723852    def doDocument(paths as List<of String>) as Compiler 
    724853        comp = .doCompile(paths, false, false, do(c as Compiler)=c.lastPhase inherits BindInterfacePhase) 
    725854        GenerateHtmlDocVisitor(do(module)=module inherits CobraModule).gen(comp) 
     
    799928     
    800929        exeFileName as String? = nil 
    801930        runArgs = .options.getStringList('run-args') 
    802         # TODO: what's this? 
     931        # TODO: exeArgs as arglist of Strings exe-name exe-arg-0 exe-arg-1 ... cf argv list 
    803932        # exeArgs = .options.getDefaultLOStr('exe-args') 
    804933        # if exeArgs.count 
    805934        #   exeFileName = exeArgs[0] 
     
    835964        print '    * commands that operate on path(s) are:' 
    836965        print '      -compile .... Compile only. Also, -c' 
    837966        print '      -run ........ Run the program (compile if necessary). Also -r (Default)' 
    838         print '      -test ....... Run the unit tests of a library.' 
     967        print '      -test ....... Run the unit tests of an app or library.' 
    839968        print '      -document ... Document the program (partial compilation). Also, -doc' 
    840969        print '      -highlight .. Syntax highlight the program in HTML.' 
     970        print '      -parse   .... Parse only. No BackEnd processing. Also, -syntax, -p' 
     971        if Utils.isDevMachine 
     972            print '      -testify .... Run the compiler testsuite (cwd must be cobra Source dir).' 
    841973        print '' 
    842974        print '  cobra <options> <command>' 
    843975        print '    * standalone commands are:' 
     
    8771009                    sep = ', ' 
    8781010                print 
    8791011            s = spec.description 
    880             while s.length 
    881                 if s.length < width 
    882                     print '[leftMarginStr][s]' 
    883                     s = '' 
    884                 else 
    885                     # TODO: bug in here for narrow widths. try "width = 20" to reproduce 
    886                     j = width + 1 
    887                     if j >= s.length, j = s.length - 1 
    888                     while j > 0 and s[j] <> ' ', j -= 1 
    889                     if j 
    890                         sub = s.substring(0, j) 
    891                         s = if(s.length, s.substring(j+1), '') 
    892                         print '[leftMarginStr][sub]' 
     1012            if s.length 
     1013                _printWrapped(s, width, leftMarginStr)  
     1014            for subOptionSpec in spec.subOptions  
     1015                assert subOptionSpec.name.length 
     1016                if subOptionSpec.containsKey('description') 
     1017                    _printWrapped('"[subOptionSpec.name]" = [subOptionSpec.description]' , width, leftMarginStr + '  ')  
     1018                if subOptionSpec.hasDefault 
     1019                    soDefault = subOptionSpec['default'] 
     1020                    print '[leftMarginStr]    (default is [soDefault])' 
    8931021            if spec.containsKey('example') 
    8941022                if spec['example'] inherits System.Collections.IList 
    8951023                    first = true 
     
    9031031            if spec.containsKey('eg') # verbatim example line 
    9041032                print '[leftMarginStr]e.g. [spec["eg"]]' 
    9051033 
     1034    def _printWrapped(s as String, width as int, leftMarginStr as String) 
     1035        while s.length 
     1036            if s.length < width 
     1037                print '[leftMarginStr][s]' 
     1038                s ='' 
     1039            else     
     1040                # TODO: bug in here for narrow widths. try "width = 20" to reproduce 
     1041                j = width + 1 
     1042                if j >= s.length, j = s.length - 1 
     1043                while j > 0 and s[j] <> ' ', j -= 1 
     1044                if j 
     1045                    sub = s.substring(0, j) 
     1046                    s = if(s.length, s.substring(j+1), '') 
     1047                    print '[leftMarginStr][sub]' 
     1048 
    9061049    def doAbout 
    9071050        # CC: multiline string 
    9081051        print 
     
    12571400             
    12581401    def _addInDefaults(valueDict as Dictionary<of String, Object>) 
    12591402        for spec in _optionSpecs 
     1403            if spec.containsKey('subOptions') and valueDict.containsKey(spec.name)  
     1404                # set defaults for unspecified subOptions with defaults if any subOption is set 
     1405                _addInSubOptionDefaults(spec, valueDict) 
     1406 
    12601407            if not valueDict.containsKey(spec.name) and spec.hasDefault 
    12611408                defaultValue = _interpretValue(spec.default, spec) to ! 
    12621409                if .verbosity 
    12631410                    print 'Setting option "[spec.name]" to default value [defaultValue].' 
    12641411                valueDict[spec.name] = defaultValue 
    1265  
     1412                 
     1413    def _addInSubOptionDefaults(spec as OptionSpec, valueDict as Dictionary<of String, Object>) 
     1414        ov = valueDict[spec.name] to OptionValues # that whats been explicitly set 
     1415        for subOptSpec in spec.subOptions 
     1416            if subOptSpec.hasDefault 
     1417                if not ov.containsKey(subOptSpec.name) # value not set already 
     1418                    defaultValue = _interpretValue(subOptSpec['default'], subOptSpec) to ! 
     1419                    # Should we force subOptions to always have a default value ?? 
     1420                    if .verbosity 
     1421                        print 'Setting option "[spec.name]:[subOptSpec.name]" to default value [defaultValue].' 
     1422                    ov[subOptSpec.name] = defaultValue 
     1423                                     
     1424                         
    12661425    def _unpackOptions(valueDict as Dictionary<of String, Object>, fileList as List<of String>)  
    12671426        """ 
    12681427        Unpack certain options (verbosity and timeit) into specific class fields,  
     
    13811540                    else 
    13821541                        valueSet.add(choice) 
    13831542                value = valueSet 
     1543            on 'multival' # comma separated  key=value pairs  - key1[=value],key2[=value],... 
     1544                if valueStr =='on', valueStr =  spec.defaultIfOn 
     1545                pairs = List<of String>(valueStr.split(c',')) 
     1546                assert pairs.count > 0 
     1547                subValueOpts = OptionValues() 
     1548                for kvp in pairs 
     1549                    kve = List<of String>(kvp.trim.split(c'=')) 
     1550                    assert kve.count > 0 
     1551                    key = kve[0] 
     1552                    if not key in spec.choices and key <> 'on' 
     1553                        print 'Unknown subOption key "[key]" in option "[spec.name]"' 
     1554                        continue 
     1555                    valStr = if(kve.count > 1, kve[1], spec.subOptionValue(key, 'defaultIfKeyOnly', 'default') to String) 
     1556                    iValue = _interpretValue(valStr, spec.subOptionSpec(key))    
     1557                    subValueOpts.setKV(key, iValue) 
     1558                value = subValueOpts     
    13841559            else 
    13851560                throw FallThroughException(spec.type) 
    13861561        return value 
     
    14281603 
    14291604 
    14301605class OptionValues inherits Dictionary<of String, Object> 
    1431  
     1606     
    14321607    var _isSpecified = Dictionary<of String, bool>()  # CC: could just be a Set 
    14331608 
    14341609    cue init 
     
    14601635 
    14611636    def get(key as String) as dynamic? 
    14621637        return this[key] 
    1463  
     1638         
     1639    def setKV(key as String, value as Object) 
     1640        this[key] = value 
     1641        .didSpecify(key) 
     1642         
    14641643    def getDefault(key as String, default as dynamic?) as dynamic? 
    14651644        if .containsKey(key) 
    14661645            return this[key] 
     
    14771656        else 
    14781657            return List<of String>() 
    14791658 
     1659    def hasSubOpt(majorKey as String, minorKey as String) as bool 
     1660        """Return bool indicating existance of given Option and subOption""" 
     1661        if not .containsKey(majorKey) 
     1662            return false 
     1663        so = .get(majorKey) to OptionValues 
     1664        return  so.containsKey(minorKey) 
     1665                 
     1666    def getSubOpt(majorKey as String, minorKey as String, default as dynamic) as dynamic 
     1667        """ 
     1668        Return subOption value for the given option and subOption (for a multi key=value Option) 
     1669        SubOptions are stored themselves as a set of OptionValues. 
     1670        If no major or minor key value return default.   
     1671        """ 
     1672        if .containsKey(majorKey) 
     1673            so = .get(majorKey) to OptionValues 
     1674            if  so.containsKey(minorKey) 
     1675                return so[minorKey]  
     1676        return default 
     1677                 
     1678    def getSubOpt(majorKey as String, minorKey as String ) as dynamic 
     1679        """ 
     1680        Return subOption value for the given option and subOption (multivalued option,  multi key=value). 
     1681        Both the Option and subOption must exist otherwise an assert exception is incurred 
     1682        """ 
     1683        assert .containsKey(majorKey) 
     1684        so = .get(majorKey) to OptionValues 
     1685        assert  so.containsKey(minorKey) 
     1686        return so[minorKey]  
     1687 
    14801688    # CC: def getDefault<of T>(key as String, value as T) as T ... 
    14811689 
    14821690    def setValue(key as String) as Set<of String> 
  • Source/TestifyRunner.cobra

     
    5858        paths = _pathList 
    5959        if paths.count == 0 
    6060            paths = .cobraTestPaths + [Path.getFullPath(Path.combine('..', 'HowTo'))] 
    61         numThreads = .options.getDefault('testify-threads', 1) to int 
     61        #numThreads = .options.getDefault('testify-threads', 1) to int 
     62        numThreads = .options.getSubOpt('testifyArgs', 'numThreads', 1) to int 
    6263        if numThreads > 1 
    6364            .runThreaded(numThreads, paths) 
    6465        else 
     
    7778 
    7879        args = CobraCore.commandLineArgs 
    7980        _subCobraExe = args[0] 
    80         _subCommandLineArgs = for arg in args[1:] where arg.startsWith('-') and not '-testify-threads:' in arg 
     81        _subCommandLineArgs = for arg in args[1:] where arg.startsWith('-') and not '-testifyArgs:' in arg 
    8182 
    8283        _statusWriter.writeLine('Queueing for threads:') 
    8384        for path in paths 
     
    102103            concat.append(sep) 
    103104            concat.append(File.readAllText(fileName)) 
    104105            sep = sepNl 
    105         File.writeAllText(.options['testify-results'] to String, concat.toString) 
     106        File.writeAllText(.options.getSubOpt('testifyArgs','resultsFile') to String, concat.toString) 
    106107        for fileName in _subResultsFileNames 
    107108            try 
    108109                File.delete(fileName) 
     
    110111                _statusWriter.writeLine('warning: Cannot delete "[fileName]" due to: [exc]') 
    111112 
    112113    def _printTotals 
    113         resultsFileName = .options['testify-results'] to String 
     114        resultsFileName = .options.getSubOpt('testifyArgs', 'resultsFile') to String 
    114115        using resultsWriter = File.appendText(resultsFileName) 
    115116            __printTotals(resultsWriter to !) 
    116117        __printTotals(_statusWriter to !) 
     
    133134                pathIndex = _subDirQueue.count 
    134135                resultsFileName = 'r-testify-[pathIndex]' 
    135136                lock _subResultsFileNames, _subResultsFileNames[pathIndex] = resultsFileName 
    136                 args = _subCommandLineArgs + ['-testify-results:[resultsFileName]', '-testify-threads:1', '"[path]"'] 
     137                args = _subCommandLineArgs + ['-testifyArgs:resultsFile=[resultsFileName],numThreads=1', '"[path]"'] 
    137138                lock _statusWriter, _statusWriter.writeLine('Thread [tid] start: [args.join(" ")]') 
    138139            p = Process() 
    139140            p.startInfo.useShellExecute = false 
     
    177178        _statusWriter = IndentedWriter(AutoFlushWriter(Console.out)) 
    178179        _statusWriter.indentString = '    ' 
    179180        try 
    180             resultsFileName = .options['testify-results'] to String 
     181            resultsFileName = .options.getSubOpt('testifyArgs', 'resultsFile') to String 
    181182            using resultsWriter = File.createText(resultsFileName) 
    182183                _resultsWriter = IndentedWriter(AutoFlushWriter(resultsWriter)) 
    183184                print to _resultsWriter to ! 
  • Tests/700-command-line/912-parse-only.cobra

     
     1# Test parse only + output metrics info (No native compile or run) 
     2use System.Text.RegularExpressions 
     3 
     4class Test  
     5 
     6    def main is shared  
     7        cobraPath = CobraCore.findCobraExe to ! 
     8        if 'Snapshot' in cobraPath 
     9            # TODO 2008-10-19: Not sure why this is happening yet. 
     10            print 'WARNING: Snapshot in cobra path:', cobraPath 
     11            cobraPath = cobraPath.replace('\\Snapshot\\', '\\') 
     12            cobraPath = cobraPath.replace('/Snapshot/', '/') 
     13        src = '911-metrics-fodder.cobra' 
     14        exe = '911-metrics-fodder.exe' 
     15        try 
     16            File.delete(exe) 
     17        catch 
     18            pass 
     19        .parseOnly(cobraPath, '-parse [src]') 
     20        assert not File.exists(exe) 
     21         
     22        #default metrics mccabe >25 
     23        .metrics1(cobraPath, '-syntax -metrics [src]') 
     24         
     25        #metrics explicit mccabe >10, loc >20  
     26        .metrics2(cobraPath, '-p -metrics:loc=20,mcc=10 [src]') 
     27        assert not File.exists(exe) 
     28     
     29        .metricsErr(cobraPath, '-p -metrics:Xmcc=20 [src]') 
     30         
     31    def parseOnly(cobraPath as String, cmdln as String)is shared 
     32        p as System.Diagnostics.Process?  
     33        output = CobraCore.runCobraExe(cobraPath, cmdln, out p)  
     34        #print output  
     35        assert 'Unhandled Exception' not in output  
     36        assert 'Executed' not in output  
     37        assert '...METHOD...\t\tLines\tMcCabe' not in output  
     38        assert p.exitCode == 0  
     39 
     40    #default metrics mccabe >25 
     41    def metrics1(cobraPath as String, cmdln as String) is shared 
     42        p as System.Diagnostics.Process?  
     43        output = CobraCore.runCobraExe(cobraPath, cmdln, out p)  
     44        #print output  
     45        assert 'Unhandled Exception' not in output  
     46        assert 'Executed' not in output  
     47        assert '...METHOD...\t\tLines\tMcCabe' in output  
     48        assert Regex.isMatch(output, r'MetricsTest\.complexExpr\s*76\s*32') 
     49        assert p.exitCode == 0  
     50         
     51    #metrics explicit mccabe >1, loc >1 (all multiline methods) 
     52    def metrics2(cobraPath as String, cmdln as String) is shared 
     53        p as System.Diagnostics.Process?  
     54        output = CobraCore.runCobraExe(cobraPath, cmdln, out p)  
     55        #print output  
     56        assert 'Unhandled Exception' not in output  
     57        assert 'Executed' not in output  
     58        assert '...METHOD...\t\tLines\tMcCabe' in output  
     59        assert Regex.isMatch(output, r'MetricsTest\.tryAll\s*26\s*6') 
     60        assert Regex.isMatch(output, r'MetricsTest\.complexExpr\s*76\s*32') 
     61        assert p.exitCode == 0  
     62 
     63    def metricsErr(cobraPath as String, cmdln as String) is shared 
     64        p as System.Diagnostics.Process?  
     65        output = CobraCore.runCobraExe(cobraPath, cmdln, out p)  
     66        #print output  
     67        assert 'Unhandled Exception' not in output  
     68        assert 'Executed' not in output  
     69        assert '...METHOD...\t\tLines\tMcCabe' not in output  
     70        assert 'Unknown subOption key "Xmcc" in option "metrics"' in output 
     71        assert p.exitCode == 0  
     72         
  • Tests/700-command-line/910-metrics.cobra

     
     1# Test compile+output metrics info (after compile and run)  
     2use System.Text.RegularExpressions 
     3 
     4class Test  
     5 
     6    def main is shared  
     7        cobraPath = CobraCore.findCobraExe to ! 
     8        if 'Snapshot' in cobraPath 
     9            # TODO 2008-10-19: Not sure why this is happening yet. 
     10            print 'WARNING: Snapshot in cobra path:', cobraPath 
     11            cobraPath = cobraPath.replace('\\Snapshot\\', '\\') 
     12            cobraPath = cobraPath.replace('/Snapshot/', '/') 
     13             
     14        src='911-metrics-fodder.cobra'   
     15        .noMetrics(cobraPath, src) 
     16 
     17        #default metrics mccabe >25 
     18        .metrics1(cobraPath, '-metrics [src]') 
     19         
     20        #metrics explicit mccabe >1, loc >1 ( all multiline methods) 
     21        .metrics2(cobraPath, '-metrics:loc=1,mcc=1 [src]') 
     22         
     23        #mcc only no LOC 
     24        .metrics3(cobraPath, '-metrics:mcc=1 [src]') 
     25         
     26        #LOC no mcc 
     27        .metrics4(cobraPath, '-metrics:loc=10 [src]') 
     28         
     29    def noMetrics(cobraPath as String, cmdln as String)is shared 
     30        p as System.Diagnostics.Process?  
     31        output = CobraCore.runCobraExe(cobraPath, cmdln, out p)  
     32        #print output  
     33        assert 'Unhandled Exception' not in output  
     34        assert 'Executed' in output  
     35        assert '...METHOD...\t\tLines\tMcCabe' not in output  
     36        assert p.exitCode == 0  
     37 
     38    #default metrics mccabe >25 
     39    def metrics1(cobraPath as String, cmdln as String) is shared 
     40        p as System.Diagnostics.Process?  
     41        output = CobraCore.runCobraExe(cobraPath, cmdln, out p)  
     42        #print output  
     43        assert 'Unhandled Exception' not in output  
     44        assert 'Executed' in output  
     45        assert '...METHOD...\t\tLines\tMcCabe' in output  
     46        assert Regex.isMatch(output, r'MetricsTest\.complexExpr\s*76\s*32') 
     47        assert p.exitCode == 0  
     48         
     49    #metrics explicit mccabe >1, loc >1 (all multiline methods) 
     50    def metrics2(cobraPath as String, cmdln as String) is shared 
     51        p as System.Diagnostics.Process?  
     52        output = CobraCore.runCobraExe(cobraPath, cmdln, out p)  
     53        #print output  
     54        assert 'Unhandled Exception' not in output  
     55        assert 'Executed' in output  
     56        assert '...METHOD...\t\tLines\tMcCabe' in output  
     57        assert Regex.isMatch(output, r'MetricsTest\.main\s*5\s*0') 
     58        assert Regex.isMatch(output, r'MetricsTest\.none\s*19\s*0') 
     59        assert Regex.isMatch(output, r'MetricsTest\.small\s*7\s*3') 
     60        assert Regex.isMatch(output, r'MetricsTest\.tryAll\s*26\s*6') 
     61        assert Regex.isMatch(output, r'MetricsTest\.chkBranch\s*6\s*1') 
     62        assert Regex.isMatch(output, r'MetricsTest\.chkFor\s*4\s*1') 
     63        assert Regex.isMatch(output, r'MetricsTest\.chkIf\s*3\s*1') 
     64        assert Regex.isMatch(output, r'MetricsTest\.chkIfElse\s*4\s*1') 
     65        assert Regex.isMatch(output, r'MetricsTest\.chkIfElseIf\s*5\s*2') 
     66        assert Regex.isMatch(output, r'MetricsTest\.chkPostWhile\s*5\s*1') 
     67        assert Regex.isMatch(output, r'MetricsTest\.complexExpr\s*76\s*32') 
     68        assert p.exitCode == 0  
     69 
     70    #metrics explicit mcc only no LOC 
     71    def metrics3(cobraPath as String, cmdln as String) is shared 
     72        p as System.Diagnostics.Process?  
     73        output = CobraCore.runCobraExe(cobraPath, cmdln, out p)  
     74        #print output  
     75        assert 'Unhandled Exception' not in output  
     76        assert 'Executed' in output  
     77        assert '...METHOD...\t\tMcCabe' in output  
     78        assert Regex.isMatch(output, r'MetricsTest\.small\s*3') 
     79        assert Regex.isMatch(output, r'MetricsTest\.tryAll\s*6') 
     80        assert Regex.isMatch(output, r'MetricsTest\.chkIfElseIf\s*2') 
     81        assert Regex.isMatch(output, r'MetricsTest\.complexExpr\s*32') 
     82        assert p.exitCode == 0  
     83         
     84    #metrics explicit LOC only no mcc 
     85    def metrics4(cobraPath as String, cmdln as String) is shared 
     86        p as System.Diagnostics.Process?  
     87        output = CobraCore.runCobraExe(cobraPath, cmdln, out p)  
     88        #print output  
     89        assert 'Unhandled Exception' not in output  
     90        assert 'Executed' in output  
     91        assert '...METHOD...\t\tLines' in output  
     92        assert Regex.isMatch(output, r'MetricsTest\.none\s*19') 
     93        assert Regex.isMatch(output, r'MetricsTest\.tryAll\s*26') 
     94        assert Regex.isMatch(output, r'MetricsTest\.complexExpr\s*76') 
     95        assert p.exitCode == 0  
     96         
  • Tests/700-command-line/911-metrics-fodder.cobra

     
     1# .skip.  
     2# program run to test LOC/McCabe metrics generation 
     3class MetricsTest 
     4    def main is shared 
     5        a = CobraCore.commandLineArgs 
     6        CobraCore.noOp(a) 
     7        # .small(99) 
     8        # emit something to show compilation worked 
     9        print 'Executed' 
     10         
     11    # the following methods contents are nonsensical and exist only to provide 
     12    # examples of cobra statements that do and dont get processed for metrics 
     13 
     14    # if any of these methods change size/linecount the LOC values in  
     15    # 910-metrics.cobra also need to be changed 
     16         
     17    #mccabe 1 
     18    def chkIf(v as int) 
     19        if v < 1 
     20            return   
     21        print v 
     22     
     23    #mccabe 1 
     24    def chkIfElse(v as int) 
     25        if v < 1 
     26            return   
     27        else 
     28            print v 
     29         
     30    #mccabe 2 
     31    def chkIfElseIf(v as int) 
     32        if v < 1 
     33            return   
     34        else if v > 21 
     35            v = 21 
     36        print v 
     37     
     38    #mccabe 1 
     39    def chkFor(v as int) 
     40        j =0 
     41        for i in v 
     42            j = j +v 
     43        print j 
     44         
     45    #mccabe 1 
     46    def chkWhile(v as int) 
     47        i = j = 0 
     48        while i < v 
     49            j = j + v 
     50            i += 1 
     51            break 
     52            continue # (:-) 
     53        print j 
     54     
     55    #mccabe 1 
     56    def chkPostWhile(v as int) 
     57        i = j = 0 
     58        post while i < v 
     59            j = j + v 
     60            i += 1 
     61        print j 
     62         
     63    #mccabe 1 
     64    def chkBranch(v as int) 
     65        branch v 
     66            on 1, ones =1 
     67            on 11, ones =2 
     68            on 111, ones =3 
     69            else, ones =-1 
     70        print ones 
     71             
     72    #mccabe 3 
     73    def small( v as int) is shared 
     74        if v < 1 
     75            return   
     76        if v >10 
     77            print 'v over 10' 
     78         
     79        if v >1 and v < 5 
     80            print 'tween 1 and 5' 
     81         
     82    #mccabe 0 
     83    def none(v as int) as int is shared 
     84        assert v >0 
     85        try 
     86            v = 100-v 
     87            v2 = 100/v 
     88        catch 
     89            print 'caught' 
     90        finally 
     91            v += 1 
     92        CobraCore.noOp(v2) 
     93         
     94        trace v 
     95        throw ArgumentException() 
     96        expect ArgumentException 
     97            throw ArgumentException('expect') 
     98        #listen,ignore 
     99        #using, yield 
     100        lock Object() 
     101            pass 
     102        return v 
     103         
     104    #mccabe 6 
     105    def tryAll(v as int) is shared 
     106        if v <= 0   #1 
     107            return 
     108        else if v > 21 #2 
     109            v = 21 
     110             
     111        j=0 
     112        for i in v  #3 
     113            j += i       
     114         
     115        v1 =v 
     116        while v/10 > 1 #4 
     117            v = v-5      
     118 
     119        v = v1 
     120        post while v <30 #5 
     121            v = v +5     
     122 
     123        v = v1   
     124        branch v        # 6 
     125            on 10 
     126                tens='1' 
     127            on 20, tens='2' 
     128            on 30, tens='3' 
     129            else 
     130                tens='unknown' 
     131        print v, tens        
     132         
     133    #mccabe 32 
     134    def complexExpr(peek as String) as String 
     135        if peek=='LPAREN' 
     136            expr='(' 
     137        else if peek[0]=='DOT' 
     138            expr='.' 
     139            try 
     140                peek = peek[1:] 
     141                if peek=='ID'  
     142                    expr = 'MemberExpr(memberToken) to Expr' 
     143                else if peek=='OPEN_CALL' 
     144                    expr = '.callExpr' 
     145                else if peek=='OPEN_GENERIC' 
     146                    expr = '.callExpr' 
     147                else 
     148                    .throwError('Syntax error after "."') 
     149            finally 
     150                print '.opStack.pop' 
     151            return 'BinaryOpExpr.make' 
     152        else if peek=='NIL' 
     153            return 'NilLiteral(.grab)' 
     154        else if peek=='TRUE' 
     155            return 'BoolLit(.grab)' 
     156        else if peek=='FALSE' 
     157            return 'BoolLit(.grab)' 
     158        else if peek=='THIS' 
     159            return 'ThisLit(.grab)' 
     160        else if peek=='BASE' 
     161            return 'BaseLit(.grab)' 
     162        else if peek=='VAR' 
     163            assert peek.length #_curCodePart 
     164            if true #_curCodePart inherits ProperDexerXetter 
     165                return 'VarLit(.grab, _curCodePart)' 
     166            else 
     167                .throwError('Cannot refer to `var` in expressions outside of a property `get` or `set`.') 
     168                throw Exception() # stop a warning 
     169        else if peek=='CHAR_LIT_SINGLE' 
     170            return 'CharLit(.grab)' 
     171        else if peek=='CHAR_LIT_DOUBLE' 
     172            return 'CharLit(.grab)' 
     173        else if peek=='STRING_START_SINGLE' 
     174            return ".stringWithSubstitutionLit('STRING_START_SINGLE', 'STRING_PART_SINGLE', 'STRING_STOP_SINGLE')" 
     175        else if peek=='STRING_START_DOUBLE' 
     176            return ".stringWithSubstitutionLit('STRING_START_DOUBLE', 'STRING_PART_DOUBLE', 'STRING_STOP_DOUBLE')" 
     177        else if peek=='STRING_SINGLE' 
     178            return 'StringLit(.grab)' 
     179        else if peek=='STRING_DOUBLE' 
     180            return 'StringLit(.grab)' 
     181        else if peek=='INTEGER_LIT' 
     182            return 'IntegerLit(.grab)' 
     183        else if peek=='DECIMAL_LIT' 
     184            return 'DecimalLit(.grab)' 
     185        else if peek=='FRACTIONAL_LIT' 
     186            return 'FractionalLit(.grab)' 
     187        else if peek=='FLOAT_LIT' 
     188            return 'FloatLit(.grab)' 
     189        else if peek=='LBRACKET' 
     190            return '.literalList' 
     191        else if peek=='ARRAY_OPEN' 
     192            return '.literalArray' 
     193        else if peek=='LCURLY' 
     194            return '.literalDictOrSet' 
     195        else if peek in ['OPEN_DO', 'DO'] 
     196            return '.doExpr' 
     197        else if peek=='OPEN_IF' 
     198            return '.ifExpr' 
     199        else if peek=='FOR' 
     200            return '.forExpr' 
     201        else if peek=='OPEN_CALL' 
     202            return '.callExpr' 
     203        else if peek=='OPEN_GENERIC' 
     204            if true #.opStack.count and .opStack.peek == 'DOT' 
     205                return '.callExpr' 
     206            else 
     207                return 'TypeExpr(.typeId)' 
     208        else if peek=='ID' 
     209            return '.identifierExpr' 
     210        return expr 
     211         
     212    def throwError(s as String) 
     213        pass     
  • Developer/IntermediateReleaseNotes.text

     
    6464 
    6565* Generate warning on use of deprecated `$sharp('...')` syntax in favor of newer alternate sharp-string-literal form: `sharp'...'` and `sharp"..."`. 
    6666 
     67* Add support for code metrics, LOC and McCabe so far. ticket:245 
    6768 
     69 
    6870================================================================================ 
    6971Library 
    7072================================================================================ 
     
    9496 
    9597* Added -verbosity-ref option to output information about resolving references to libraries. 
    9698 
    97  
     99* Added support for specifying a cmdline option with subOptions and modified   
     100    -test-runner, -testify-Threads, -testify-results to use it becoming a new multival option  
     101    -testifyArgs with subOptions runner,results and numThreads. 
     102    e.g. -testifyArgs:results=tResults,numThreads=4,runner=Core.Runner 
     103         -testifyArgs:numThreads=4 
     104         
    98105================================================================================ 
    99106Samples etc. 
    100107================================================================================ 
     
    475482* Fixed: Dynamic binding cannot find `shared` methods.  ticket:208 
    476483 
    477484* Fixed: File and line number are duplicated in some compiler error messages.  ticket:212 
     485 
     486* Fixed: Better error message for unrecognised docString : ticket:218 
     487