jan@167: "{ Package: 'stx:goodies/builder/reports' }" jan@167: jan@167: "{ NameSpace: Builder }" jan@167: jan@167: ReportFormat subclass:#CoverageReportFormat jan@167: instanceVariableNames:'' jan@167: classVariableNames:'' jan@167: poolDictionaries:'' jan@167: category:'Builder-Reports-Formats' jan@167: ! jan@167: jan@167: CoverageReportFormat subclass:#Cobertura jan@183: instanceVariableNames:'currentPackage currentClass currentClassLinesBuffer currentMethod jan@183: infos' jan@167: classVariableNames:'' jan@167: poolDictionaries:'' jan@167: privateIn:CoverageReportFormat jan@167: ! jan@167: jan@193: Parser subclass:#MethodAnalyzer jan@193: instanceVariableNames:'intervals branches' jan@193: classVariableNames:'' jan@193: poolDictionaries:'' jan@193: privateIn:CoverageReportFormat::Cobertura jan@193: ! jan@193: jan@167: jan@167: !CoverageReportFormat class methodsFor:'testing'! jan@167: jan@167: isAbstract jan@214: jan@167: ^self == CoverageReportFormat jan@167: jan@167: "Created: / 04-08-2011 / 11:44:25 / Jan Vrany " jan@167: "Modified: / 25-06-2013 / 01:26:24 / Jan Vrany " jan@214: "Modified (format): / 30-07-2013 / 09:19:32 / Jan Vrany " jan@167: ! ! jan@167: jan@167: !CoverageReportFormat::Cobertura class methodsFor:'accessing'! jan@167: jan@167: symbolicNames jan@167: "Returns a collection of symbolic names for this format" jan@167: jan@167: ^ self shouldImplement jan@167: ! ! jan@167: jan@167: !CoverageReportFormat::Cobertura class methodsFor:'documentation'! jan@167: jan@167: documentation jan@167: " } jan@167: jan@167: Replace 'Object', 'NewClass1' and jan@167: the empty string arguments by true values. jan@167: jan@167: Install (or change) the class by 'accepting', jan@167: either via the menu or the keyboard (usually CMD-A). jan@167: jan@167: You can also change the category simply by editing jan@167: the categoryString and accepting. jan@167: jan@167: To be nice to others (and yourself later), do not forget to jan@167: add some documentation; preferably under the classes documentation jan@167: protocol. jan@167: (see the `create documentation stubs' item in the methodList menu; jan@167: switch from instance to class to find this menu item.) jan@167: jan@167: Notice, that ST/X uses the convention to document the class using jan@167: comment-only class methods (however, ST80 comments are supported and jan@167: can be changed via the class-documentation menu). jan@167: jan@167: " jan@167: ! ! jan@167: jan@167: !CoverageReportFormat::Cobertura methodsFor:'accessing - defaults'! jan@167: jan@167: defaultFileSuffix jan@167: "superclass Builder::ReportFormat says that I am responsible to implement this method" jan@167: jan@167: ^ 'xml' jan@167: jan@167: "Modified: / 25-06-2013 / 02:02:06 / Jan Vrany " jan@167: ! ! jan@167: jan@213: !CoverageReportFormat::Cobertura methodsFor:'private'! jan@213: jan@213: sourceInfoForClass: class inPackage: package jan@213: | infosPerPackage cls | jan@213: jan@213: cls := class. jan@213: cls isMetaclass ifTrue:[ jan@213: cls := cls theNonMetaclass jan@213: ]. jan@213: cls isPrivate ifTrue:[ jan@213: cls := cls topOwningClass. jan@213: ]. jan@213: infosPerPackage := infos at: package ifAbsentPut:[Dictionary new]. jan@213: ^ infosPerPackage at: class ifAbsentPut: [ReportSourceInfo forClass: cls inPackage: package]. jan@213: jan@213: "Created: / 29-07-2013 / 18:43:13 / Jan Vrany " jan@213: ! jan@213: jan@213: sourceInfoForExtensionsinPackage: package jan@213: | infosPerPackage | jan@213: jan@213: infosPerPackage := infos at: package ifAbsentPut:[Dictionary new]. jan@213: ^ infosPerPackage at: 'extensions.st' ifAbsentPut: [ReportSourceInfo forExtensionsInPackage: package]. jan@213: jan@213: "Created: / 29-07-2013 / 18:43:39 / Jan Vrany " jan@213: ! ! jan@213: jan@167: !CoverageReportFormat::Cobertura methodsFor:'writing'! jan@167: jan@167: write: instrumentedMethods jan@167: | packageMap | jan@167: jan@167: packageMap := Dictionary new. jan@167: infos := Dictionary new. jan@167: instrumentedMethods do:[:method| jan@183: | classMap methodSet | jan@167: classMap := packageMap at: method package ifAbsentPut: [ Dictionary new ]. jan@186: methodSet := classMap at: method mclass theNonMetaclass ifAbsentPut: [ Set new ]. jan@183: methodSet add: method. jan@167: ]. jan@167: jan@167: packageMap keys asSortedCollection do:[:package| jan@167: | classMap | jan@167: jan@167: self writePackage: package with:[ jan@167: ((classMap := packageMap at: package) keys asSortedCollection:[:a :b| a name < b name ]) do:[:class| jan@167: self writeClass: class with:[ jan@256: | methodSetOrdered | jan@167: jan@213: "/ methodSetOrdered := (classMap at: class) asSortedCollection:[:a :b | (info offsetOfMethod: a) < (info offsetOfMethod: b)]. jan@213: methodSetOrdered := (classMap at: class) asSortedCollection:[:a :b | a selector < b selector]. jan@183: methodSetOrdered do:[:method| jan@167: self writeMethod: method. jan@167: ] jan@167: ] jan@167: ] jan@167: ] jan@167: ] jan@167: jan@167: "Created: / 25-06-2013 / 13:17:40 / Jan Vrany " jan@213: "Modified: / 29-07-2013 / 18:49:39 / Jan Vrany " jan@256: "Modified (format): / 15-12-2014 / 10:21:36 / Jan Vrany " jan@167: ! jan@167: jan@167: writeClass: class with: content jan@167: jan@167: | className classFileName classPathName | jan@167: jan@167: className := class name. jan@167: classFileName := class isPrivate jan@182: ifTrue:[(Smalltalk fileNameForClass: class topOwningClass) , '.st'] jan@182: ifFalse:[(Smalltalk fileNameForClass: class) , '.st']. jan@167: class package ~~ currentPackage ifTrue:[ jan@167: classFileName := 'extensions.st' jan@167: ]. jan@167: classPathName := ((currentPackage copyReplaceAll: $: with: Filename separator) replaceAll: $/ with: Filename separator) jan@167: , Filename separator , classFileName. jan@167: jan@167: stream nextPutAll:' '. jan@167: stream nextPutLine:' '. jan@167: currentClass := class. jan@183: currentClassLinesBuffer := String new writeStream. jan@167: content value. jan@167: currentClass := nil. jan@167: stream nextPutLine:' '. jan@183: stream nextPutLine:' '. jan@183: stream nextPutAll: currentClassLinesBuffer contents. jan@183: stream nextPutLine:' '. jan@183: currentClassLinesBuffer := nil. jan@167: stream nextPutLine:' ' jan@167: jan@167: "Created: / 25-06-2013 / 12:29:14 / Jan Vrany " jan@183: "Modified: / 27-06-2013 / 00:05:47 / Jan Vrany " jan@167: ! jan@167: jan@167: writeFooter jan@167: stream nextPutAll:' jan@167: ' jan@167: jan@167: "Modified: / 25-06-2013 / 11:57:02 / Jan Vrany " jan@167: ! jan@167: jan@167: writeHeader jan@167: stream nextPutAll:' jan@167: jan@167: jan@182: '. jan@182: stream nextPutLine:' '. jan@182: Smalltalk packagePath do:[:each| jan@182: stream jan@182: nextPutAll: ''; jan@182: nextPutAll: each asFilename asAbsoluteFilename pathName; jan@182: nextPutAll: ''; jan@182: cr. jan@182: ]. jan@182: stream nextPutLine:' '. jan@182: stream nextPutLine:' '. jan@167: jan@182: "Modified: / 26-06-2013 / 17:50:24 / Jan Vrany " jan@167: ! jan@167: jan@183: writeLine: lineNr hits: nhits on: s jan@167: jan@183: s nextPutAll:' '. jan@167: jan@183: "Created: / 27-06-2013 / 00:03:08 / Jan Vrany " jan@167: ! jan@167: jan@167: writeMethod: method jan@167: jan@193: | info firstCharOffset firstLineNr lastLineNr analyzer lines name | jan@167: jan@186: name := method selector. jan@186: method mclass isMetaclass ifTrue:[ jan@186: name := name , ' [class method]'. jan@186: ]. jan@186: jan@214: stream nextPutAll:' '. jan@167: stream nextPutLine:' '. jan@167: currentMethod := method. jan@167: jan@213: info := method package == method mclass package jan@213: ifTrue:[self sourceInfoForClass: method mclass inPackage: method package] jan@213: ifFalse:[self sourceInfoForExtensionsinPackage: method package]. jan@213: jan@167: firstCharOffset := info offsetOfMethod: method. jan@167: firstLineNr := (info lineAndColumnOfOffset: firstCharOffset) x. jan@167: lastLineNr := (info lineAndColumnOfOffset: firstCharOffset + method source size) x. jan@167: jan@192: lines := Array new: lastLineNr - firstLineNr + 1 withAll: nil. jan@193: analyzer := MethodAnalyzer new. jan@193: analyzer parseMethod: method source in: method mclass. jan@193: analyzer intervals do:[:interval| jan@193: | start stop | jan@213: start := info lineAndColumnOfOffset: firstCharOffset + interval first - 1. jan@213: stop := info lineAndColumnOfOffset: firstCharOffset + interval last - 1. jan@193: start x to: stop x do:[:lineNr| jan@213: lines at: lineNr - firstLineNr + 1 put: -1. jan@193: ]. jan@193: ]. jan@167: jan@192: (method statementInvocationInfo copy sort:[:a :b | a startPosition < b startPosition]) do:[:eachBlockInfo | jan@167: | startLine endLine | jan@167: jan@167: startLine := (info lineAndColumnOfOffset: firstCharOffset + eachBlockInfo startPosition - 1) x. jan@167: endLine := (info lineAndColumnOfOffset: firstCharOffset + eachBlockInfo endPosition - 1) x. jan@167: startLine to: endLine do:[:lineNr| jan@213: jan@193: (lines at: (lineNr - firstLineNr + 1)) == -1 ifTrue:[ jan@192: lines at: (lineNr - firstLineNr + 1) put: (eachBlockInfo count) jan@192: ] ifFalse:[ jan@213: lines at: (lineNr - firstLineNr + 1) put: (((lines at: (lineNr - firstLineNr + 1)) ? (SmallInteger maxVal)) min: eachBlockInfo count) jan@192: ] jan@167: ] jan@167: ]. jan@167: 1 to: lines size do:[:i| jan@193: (lines at: i) notNil ifTrue:[ jan@193: (lines at: i) == -1 ifTrue:[ jan@193: lines at: i put: 0. jan@193: ]. jan@193: self writeLine: (i + firstLineNr - 1) hits: ((lines at: i)) on: stream. jan@193: self writeLine: (i + firstLineNr - 1) hits: ((lines at: i)) on: currentClassLinesBuffer. jan@193: ] jan@167: ]. jan@167: jan@167: currentMethod := nil. jan@167: stream nextPutLine:' '. jan@167: stream nextPutLine:' ' jan@167: jan@167: "Created: / 25-06-2013 / 13:17:52 / Jan Vrany " jan@214: "Modified: / 30-07-2013 / 09:12:19 / Jan Vrany " jan@167: ! jan@167: jan@167: writePackage: packageName with: aBlock jan@167: jan@167: stream nextPutAll:' '. jan@167: stream nextPutLine:' '. jan@167: currentPackage := packageName. jan@167: aBlock value. jan@167: currentPackage := nil. jan@167: stream nextPutLine:' '. jan@167: stream nextPutLine:' ' jan@167: jan@167: "Created: / 25-06-2013 / 11:55:17 / Jan Vrany " jan@167: "Modified: / 25-06-2013 / 13:24:58 / Jan Vrany " jan@167: ! ! jan@167: jan@193: !CoverageReportFormat::Cobertura::MethodAnalyzer methodsFor:'accessing'! jan@193: jan@193: branches jan@193: ^ branches jan@193: ! jan@193: jan@193: intervals jan@193: ^ intervals jan@193: ! ! jan@193: jan@193: !CoverageReportFormat::Cobertura::MethodAnalyzer methodsFor:'code generation hooks'! jan@193: jan@193: statementListRewriteHookFor:aStatementNode jan@193: "invoked whenever a statement list node has been generated; jan@193: gives subclasses a chance to rewrite (instrument) it" jan@193: jan@193: | stmt | jan@193: jan@193: intervals isNil ifTrue:[ jan@193: intervals := OrderedCollection new. jan@193: ]. jan@193: stmt := aStatementNode. jan@193: [ stmt notNil ] whileTrue:[ jan@193: intervals add: (stmt startPosition to: stmt endPosition). jan@193: stmt := stmt nextStatement. jan@193: ]. jan@193: ^ aStatementNode jan@193: jan@193: "Created: / 29-07-2013 / 10:16:34 / Jan Vrany " jan@193: "Modified: / 29-07-2013 / 11:25:00 / Jan Vrany " jan@193: ! ! jan@193: jan@167: !CoverageReportFormat class methodsFor:'documentation'! jan@167: jan@167: version jan@167: ^ '$Header$' jan@167: ! jan@167: jan@167: version_CVS jan@167: ^ '$Header$' jan@167: ! ! jan@167: