Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Feature/more detailed lcov #275

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
535 changes: 392 additions & 143 deletions bsc-plugin/src/lib/rooibos/CodeCoverageProcessor.spec.ts

Large diffs are not rendered by default.

521 changes: 468 additions & 53 deletions bsc-plugin/src/lib/rooibos/CodeCoverageProcessor.ts

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions bsc-plugin/src/lib/rooibos/FileFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { standardizePath as s } from 'brighterscript';
import * as path from 'path';
import * as fs from 'fs';
import * as fse from 'fs-extra';
import type { CoverageMap } from './CodeCoverageProcessor';
import type { TestSuite } from './TestSuite';

export class FileFactory {
Expand Down Expand Up @@ -106,10 +107,9 @@ export class FileFactory {
return contents;
}

public createCoverageComponent(program: Program, coverageMap: any, filepathMap: Map<number, string>) {
public createCoverageComponent(program: Program, baseCoverageReport: CoverageMap) {
let template = this.coverageComponentBrsTemplate;
template = template.replace(/\"\#EXPECTED_MAP\#\"/g, JSON.stringify(coverageMap ?? {}));
template = template.replace(/\"\#FILE_PATH_MAP\#\"/g, JSON.stringify(filepathMap ?? {}));
template = template.replace(/\"\#BASE_COVERAGE_REPORT\#\"/g, JSON.stringify(baseCoverageReport ?? { files: [] }));

this.addFileToRootDir(program, path.join('components/rooibos', 'CodeCoverage.brs'), template);
this.addFileToRootDir(program, path.join('components/rooibos', 'CodeCoverage.xml'), this.coverageComponentXmlTemplate);
Expand Down
141 changes: 112 additions & 29 deletions framework/src/source/CodeCoverage.brs
Original file line number Diff line number Diff line change
@@ -1,39 +1,122 @@
function init()
m.resolvedMap = {}
m.top.observeField("entry", "onEntryChange")
m.top.observeField("save", "onSave")
m.results = []
m.coverageMap = "#BASE_COVERAGE_REPORT#"
m.port = createObject("roMessagePort")
m.top.observeFieldScoped("entry", m.port)
m.top.functionName = "runTaskThread"
m.top.control = "RUN"
end function

function setExpectedMap()
m.top.expectedMap = "#EXPECTED_MAP#"
end function
function runTaskThread() as void
while true
events = []
saving = false
message = getMessage(m.port)
if message <> invalid
events.push(message)
end if
if m.top.save = true
saving = true
? "Saving unprocessed code cov events..."
'Get All the unprocessed messages
while true
message = getMessage(m.port, 3)
if message = invalid
exit while
else
events.push(message)
end if
end while

? "Found" events.count() " unprocessed events..."
end if

' enum CodeCoverageLineType
' noCode = 0
' code = 1
' condition = 2
' branch = 3
' function = 4
' end enum

for each event in events
entry = event.getData()
if entry <> invalid
file = m.coverageMap.files[entry.f]
if entry.r = 4 ' CodeCoverageLineType.function
if file.functions[entry.fn].totalHit = 0
file.functionTotalHit ++
end if
file.functions[entry.fn].totalHit ++
else if entry.r = 3 ' CodeCoverageLineType.branch
for each branch in file.blocks[entry.bl].branches
if branch.id = entry.br
if branch.totalHit = 0
file.branchTotalHit ++
end if
branch.totalHit ++
exit for
end if
end for
else if entry.r = 1 ' CodeCoverageLineType.code
for each line in file.lines
if line.lineNumber = entry.l
if line.totalHit = 0
file.lineTotalHit ++
end if
line.totalHit ++
exit for
end if
end for
end if
m.coverageMap.files[entry.f] = file
end if
end for

if saving = true
m.top.coverageResults = m.coverageMap
return
end if
end while

function setFilePathMap()
m.top.filePathMap = "#FILE_PATH_MAP#"
end function

function onEntryChange()
entry = m.top.entry
' defer till later
m.results.push(entry)

' Gets the next message from the message port and applies a short sleep if no message was returned.
' @param {roMessagePort} port - The active message port to get messages from.
' @param {Integer} [sleepInterval] - How long to sleep if there was no message returned.
' @return {Dynamic} Any resulting message from the message port.
function getMessage(port as object, sleepInterval = 20 as integer) as dynamic
message = port.getMessage()
' I know I will always get something from the port so no need for the uninitialized check in isInvalid
if message = invalid then sleep(sleepInterval)
return message
end function

function onSave()
? "saving data"
for each entry in m.results
if entry <> invalid
fileId = entry.f
lineMap = m.resolvedMap[fileId]
#if false
sub test()
player = m.player

if lineMap = invalid
lineMap = {}
m.resolvedMap[fileId] = lineMap
end if
lineMap[entry.l] = entry.r
report = false
if player = invalid or (player.duration > 0 and player.state = "playing") then
report = true
end if

if report = true then
player.control = "stop"
end if
end sub


sub test()
player = m.player

report = false
if RBS_CC_0_reportCondition(109, 1, player = invalid) or RBS_CC_0_reportCondition(109, 2, (RBS_CC_0_reportCondition(109, 3, player.duration > 0) and RBS_CC_0_reportCondition(109, 4, player.state = "playing"))) then
report = true
end if

if report = true then
player.control = "stop"
end if
end for
m.top.resolvedMap = m.resolvedMap
setExpectedMap()
setFilePathMap()
end function
end sub
#end if
7 changes: 3 additions & 4 deletions framework/src/source/CodeCoverage.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<component name="CodeCoverage"
extends="ContentNode"
extends="Task"
>
<script type="text/brightscript" uri="CodeCoverage.brs" />
<interface>
<field id="entry" type="assocarray" />
<field id="coverageResults" type="assocarray" />

<field id="save" type="boolean" />
<field id="expectedMap" type="assocarray" />
<field id="resolvedMap" type="assocarray" />
<field id="filePathMap" type="assocarray" />
</interface>
</component>
126 changes: 71 additions & 55 deletions framework/src/source/Coverage.bs
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
namespace rooibos.Coverage
function reportCodeCoverage() as void

function saveResults() as dynamic
m.global._rbs_ccn.save = true
cc = m.global._rbs_ccn
if cc.coverageResults = invalid
' The results are still being formatted
' Wait for them to be reported
port = createObject("roMessagePort")
cc.observeFieldScoped("coverageResults", port)
msg = wait(0, port)
coverageResults = msg.getData()
else
' Results are ready to go
coverageResults = cc.coverageResults
end if

return coverageResults
end function

function reportCodeCoverage(coverageResults as dynamic) as void

if m.global._rbs_ccn = invalid
? "There was no rooibos code coverage component - not generating coverage report"
Expand All @@ -9,33 +28,23 @@ namespace rooibos.Coverage
? ""
? "...Generating code coverage report"
? ""
m.global._rbs_ccn.save = true
cc = m.global._rbs_ccn
expectedMap = cc.expectedMap
filePathMap = cc.filePathMap
resolvedMap = cc.resolvedMap

hitFiles = []
missFiles = []
allLinesCount = 0
allLinesHit = 0
for each key in expectedMap
filename = filePathMap[key]
expectedCount = expectedMap[key].count()
for each file in coverageResults.files
filename = "pkg:/" + file.sourceFile
expectedCount = file.lineTotalFound
allLinesCount += expectedCount
allLinesHit += file.lineTotalHit
if expectedCount > 0
if resolvedMap[key] <> invalid
resolvedCount = resolvedMap[key].count()
allLinesHit += resolvedCount
if resolvedCount = 0
resolvedPercent = 0
else
resolvedPercent = (resolvedCount / expectedCount) * 100
end if
hitFiles.push({ percent: resolvedPercent, text: filename + ": " + str(resolvedPercent).trim() + "% (" + stri(resolvedCount).trim() + "/" + stri(expectedCount).trim() + ")" })
if file.lineTotalHit = 0
resolvedPercent = 0
else
missFiles.push(filename + ": MISS!")
resolvedPercent = (file.lineTotalHit / expectedCount) * 100
end if
hitFiles.push({ percent: resolvedPercent, text: filename + ": " + str(resolvedPercent).trim() + "% (" + stri(file.lineTotalHit).trim() + "/" + stri(expectedCount).trim() + ")" })
else
missFiles.push(filename + ": MISS!")
end if
Expand All @@ -52,7 +61,7 @@ namespace rooibos.Coverage
? "+++++++++++++++++++++++++++++++++++++++++++"
? ""
? "Total Coverage: " ; str(allLinesPercent).trim() ; "% (" ; stri(allLinesHit).trim() ; "/" + stri(allLinesCount).trim() ; ")"
? "Files: " ; resolvedMap.count(); "/" ; expectedMap.count()
? "Files: " ; hitFiles.count(); "/" ; coverageResults.files.count()
? ""
? "HIT FILES"
? "---------"
Expand All @@ -72,70 +81,77 @@ namespace rooibos.Coverage
? "+++++++++++++++++++++++++++++++++++++++++++"
end function

function createLCovOutput(logToConsole = true as boolean)
function createLCovOutput(coverageResults as dynamic, logToConsole = true)
? "Generating lcov.info file..."
fileResults = []

cc = m.global._rbs_ccn
expectedMap = cc.expectedMap
filePathMap = cc.filePathMap
resolvedMap = cc.resolvedMap
for each file in coverageResults.files
buffer = `TN:\n`
buffer += `SF:${file.sourceFile}\n`
' buffer += `VER:\n`

results = []
' Add all the found functions for the file
for each func in file.functions
buffer += `FN:${func.startLine},${func.endLine},${func.name}\n`
end for

for each module in filePathMap.items()
buffer = ""
moduleNumber = module.key
filePath = module.value
packageName = "."
' Write function related data
for each func in file.functions
if func.totalHit > 0
buffer += `FNDA:${func.totalHit},${func.name}\n`
end if
end for

relativePath = filePath.replace("pkg:", packageName)
sanitizedPath = relativePath.replace("\\", "/")
buffer += `FNF:${file.functionTotalFound}\n`
buffer += `FNH:${file.functionTotalHit}\n`

buffer += "TN:" + chr(10)
buffer += "SF:" + sanitizedPath + chr(10)
' Write branch related data
for each block in file.blocks
for each branch in block.branches
if branch.totalHit > 0
buffer += `BRDA:${branch.line},${block.id},${branch.id},${branch.totalHit}\n`
end if
end for
end for

for each expected in expectedMap[moduleNumber]
lineNumber = val(expected)
SHIFT = 1
buffer += `BRF:${file.branchTotalFound}\n`
buffer += `BRH:${file.branchTotalHit}\n`

if resolvedMap[moduleNumber] <> invalid and resolvedMap[moduleNumber].doesExist(expected)
buffer += "DA:" + str(lineNumber + SHIFT).trim() + "," + str(resolvedMap[moduleNumber][expected]).trim() + chr(10)
else
buffer += "DA:" + str(lineNumber + SHIFT).trim() + ",0" + chr(10)
end if
' Write the per line related data
for each line in file.lines
buffer += `DA:${line.lineNumber},${line.totalHit}\n`
end for

buffer += "LF:" + str(expectedMap[moduleNumber].count()).trim() + chr(10)
buffer += `LF:${file.lineTotalFound}\n`
buffer += `LH:${file.lineTotalHit}\n`
buffer += `end_of_record`

if resolvedMap[moduleNumber] <> invalid
buffer += "LH:" + str(resolvedMap[moduleNumber].count()).trim() + chr(10)
else
buffer += "LH:0" + chr(10)
end if

buffer += "end_of_record"
if logToConsole
? buffer
' When logging to the console it is very possible to flood the buffer and cause the application to exit.
' Sleep for a short amount of time so as to give console scrapers time to empty the buffer
sleep(30)
else
results.push(buffer)
fileResults.push(buffer)
end if
end for

return results.join(chr(10))
if not logToConsole
return fileResults.join(chr(10))
else
return ""
end if
end function

function printLCovInfo()
function printLCovInfo(coverageResults as dynamic)

?
? "+++++++++++++++++++++++++++++++++++++++++++"
? "LCOV.INFO FILE"
? "+++++++++++++++++++++++++++++++++++++++++++"
?
? "+-=-coverage:start"
rooibos.coverage.createLCovOutput()
rooibos.coverage.createLCovOutput(coverageResults)
? "+-=-coverage:end"
end function

Expand Down
5 changes: 3 additions & 2 deletions framework/src/source/TestRunner.bs
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,11 @@ namespace rooibos
m.nodeContext.global.testsScene.rooibosTestResult = rooibosResult

if m.config.isRecordingCodeCoverage
rooibos.Coverage.reportCodeCoverage()
coverageResults = rooibos.Coverage.saveResults()
rooibos.Coverage.reportCodeCoverage(coverageResults)

if m.config.printLcov = true
rooibos.Coverage.printLCovInfo()
rooibos.Coverage.printLCovInfo(coverageResults)
end if
else
? "rooibos.Coverage.reportCodeCoverage is not a function"
Expand Down
Loading