< Summary - Igor Pro Universal Testing Framework

Information
Class: procedures.igortest-tracing-cobertura
Assembly: procedures
File(s): /builds/mirror/igortest/procedures/igortest-tracing-cobertura.ipf
Tag: 74147b3
Line coverage
42%
Covered lines: 81
Uncovered lines: 109
Coverable lines: 190
Total lines: 372
Line coverage: 42.6%
Branch coverage
87%
Covered branches: 7
Total branches: 8
Branch coverage: 87.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/builds/mirror/igortest/procedures/igortest-tracing-cobertura.ipf

#LineLine coverage
 1#pragma rtGlobals=3
 2#pragma TextEncoding="UTF-8"
 3#pragma rtFunctionErrors=1
 4#pragma version=1.10
 5#pragma ModuleName=IUTF_Tracing_Cobertura
 6
 7#undef UTF_ALLOW_TRACING
 8#if Exists("TUFXOP_Version")
 9
 10#if IgorVersion() >= 10.00
 11#define UTF_ALLOW_TRACING
 12#elif (IgorVersion() >= 9.00) && (NumberByKey("BUILD", IgorInfo(0)) >= 38812)
 13#define UTF_ALLOW_TRACING
 14#endif
 15
 16#endif
 17
 18#ifdef UTF_ALLOW_TRACING
 19
 20// file size limit to show a warning banner. Some Cobertura consumers like Gitlab have a hardcoded
 21// limit after which no cobertura files can no longer be read. The limit for Gitlab is at 10 MB but
 22// this will show the warning a bit early to leave the user some room to take some preparation.
 23static Constant FILESIZE_WARNING_LIMIT = 8000000
 24
 25// Empty function signature without any arguments.
 26static StrConstant EMPTY_FUNC_SIGNATURE = "()"
 27
 28/// @brief The Cobertura information for one procedure file. This is needed to generate a valid
 29/// Cobertura file.
 30///
 31/// sourcePath: The source path where the procedure file can be found. The source path can be any
 32///     directory, the procedure file just have to be in any sub-directory in it. This is usually
 33///     one of theuser defined values.
 34/// packageName: A package name is a part of the directory path from the source path to the
 35///     procedure file. The package name doesn't contain the name of the procedure file itself. If
 36///     the procedure file is located directly in the source path the package name is an empty
 37///     string.
 38/// classFileName: The class file name is the relative path from the source path to the procedure
 39///     file.
 40/// className: The class name is like the class file name. The file extension is removed and all
 41///     directory delimiters are replaced with dots.
 42Structure IUTF_Cobertura_ProcInfo
 43  string sourcePath
 44  string packageName
 45  string classFileName
 46  string className
 47EndStructure
 48
 49/// @brief Contains the coverage metrics about a section of the code (this could be a function/macro
 50/// or the whole file).
 51///
 52/// lineRate: The result of lineCovered/lineValid. If lineValid is 0 lineRate is set to 1.
 53/// lineCovered: The number of valid lines that has at least one hit.
 54/// lineValid: The number of lines that are marked as instrumented and therefore user code.
 55/// branchRate: The result of branchCovered/branchValid. If branchValid is 0 branchRate is set to 1.
 56/// branchCovered: The number of valid branches that has at least one hit.
 57/// branchValid: The number of branches that could be instrumented. A branch is a point where the
 58///     user code branches and can execute different parts depending on a condition (like an
 59///     if-statement).
 60Structure IUTF_Cobertura_Metrics
 61  variable lineRate
 62  variable lineCovered
 63  variable lineValid
 64  variable branchRate
 65  variable branchCovered
 66  variable branchValid
 67EndStructure
 68
 69// Convert a file path suitable for Cobertura XML. This will replace all back-slash with a
 70// forward-slash.
 71// This is similar to ParseFilePath(5, path, "/", 0, 0) which isn't supported in Igor 9.
 572static Function/S FilePathToXml(string path)
 573  path = ReplaceString("\\", path, "/")
 574  path = IUTF_Utils_Xml#ToXmlToken(path)
 75
 576  return path
 577End
 78
 79// Returns the Cobertura class name of a relative file name. This will trim the file extension and
 80// replace directory delimiter with a dots.
 4081static Function/S GetClassName(string fileName)
 4082  string className
 83
 4084  string dir     = ParseFilePath(1, fileName, "\\", 1, 0)
 4085  string rawName = ParseFilePath(3, fileName, "\\", 0, 0)
 4086  sprintf className, "%s%s", ReplaceString("\\", dir, "."), rawName
 87
 4088  return className
 4089End
 90
 91/// @brief Fill info with the required path information of a procedure file for the cobertura output
 92///
 93/// @param procPath   The full path of the procedure file
 94/// @param sources    A wave with valid source paths. It will use the first entry which contains
 95///                   procPath. If no source contains procPath the parent directory of procPath will
 96///                   be used as source.
 97///
 98/// @retval info      The information that can be used for the Cobertura output.
 2099static Function [STRUCT IUTF_Cobertura_ProcInfo info] GetProcInfo(string procPath, WAVE/T sources)
 20100  variable i, length
 101
 20102  string   dirPath = IUTF_Utils_Paths#GetDirPathOfFile(procPath)
 20103  variable size    = DimSize(sources, UTF_ROW)
 104
 20105  procPath = ParseFilePath(5, procPath, "\\", 0, 0)
 106
 20107  for(i = 0; i < size; i += 1)
 30108    if(!IUTF_Utils_Strings#IsPrefix(dirPath, sources[i]))
 15109      continue
 15110    endif
 111
 15112    info.sourcePath    = RemoveEnding(sources[i], "\\")
 15113    info.packageName   = RemoveEnding(dirPath[strlen(sources[i]), Inf], "\\")
 15114    info.classFileName = procPath[strlen(sources[i]), Inf]
 15115    info.className     = GetClassName(info.classFileName)
 116
 15117    return
 0118  endfor
 119
 5120  info.sourcePath    = RemoveEnding(dirPath, "\\")
 5121  info.packageName   = ""
 5122  info.classFileName = procPath[strlen(dirPath), Inf]
 5123  info.className     = GetClassName(info.classFileName)
 20124End
 125
 126/// @brief Creates the lines report for the Cobertura output. At the same time it will generate some
 127/// metrics which can be used at other places in the Cobertura output.
 128///
 129/// @param indent     The indent string that is prefixed for each output line.
 130/// @param lineStart  the inclusive line index where the report should start
 131/// @param lineEnd    the exclusive line index where the report should end
 132/// @param procIndex  The index of the procedure file
 133/// @param totals     The totals wave with the data from the instrumentation run
 134/// @param marker     The wave with can tell which line in the source file was instrumented
 135///
 136/// @retval report    The report XML
 137/// @retval metrics   The collected metrics for the specified lines section and procedure file
 15138static Function [string report, STRUCT IUTF_Cobertura_Metrics metrics] GetLinesReport(string indent, variable lineStart,
 15139  string line, coverage
 15140  variable i, execC, nobranchC, branchC, sum
 141
 15142  variable totalLines, coveredLines, totalBranches, coveredBranches
 143
 15144  report = indent + "<lines>\n"
 145
 15146  for(i = lineStart; i < lineEnd; i += 1)
 40147    if(!marker[i][%INSTR])
 10148      continue
 30149    endif
 150
 30151    execC     = totals[i][0][procIndex]
 30152    nobranchC = totals[i][1][procIndex]
 30153    branchC   = totals[i][2][procIndex]
 154
 30155    totalLines   += 1
 30156    coveredLines += execC > 0
 157
 30158    if(noBranchC + branchC)
 5159      sum = (nobranchC > 0) + (branchC > 0)
 5160      sprintf coverage, "%g%% (%d/2)", sum * 50, sum
 5161      totalBranches   += 2
 5162      coveredBranches += sum
 163
 5164      sprintf line, "%s\t<line number=\"%d\" hits=\"%d\" branch=\"true\" condition-coverage=\"%s\">\n", indent, i + 1, e
 5165      report += line
 5166      report += indent + "\t\t<conditions>\n"
 167
 5168      sprintf line, "%s\t\t\t<condition number=\"0\" type=\"jump\" coverage=\"%g%%\"/>\n", indent, sum * 50
 5169      report += line
 170
 5171      report += indent + "\t\t</conditions>\n"
 5172      report += indent + "\t</line>\n"
 25173    else
 25174      sprintf line, "%s\t<line number=\"%d\" hits=\"%d\" branch=\"false\"/>\n", indent, i + 1, execC
 25175      report += line
 30176    endif
 30177  endfor
 178
 15179  report += indent + "</lines>\n"
 180
 15181  metrics.lineCovered   = coveredLines
 15182  metrics.lineValid     = totalLines
 15183  metrics.lineRate      = totalLines == 0 ? 1 : coveredLines / totalLines
 15184  metrics.branchCovered = coveredBranches
 15185  metrics.branchValid   = totalBranches
 15186  metrics.branchRate    = totalBranches == 0 ? 1 : coveredBranches / totalBranches
 15187End
 188
 189/// @brief Creates a report of a specific function inside a procedure file.
 190///
 191/// @param funcName   The name of the function
 192/// @param funcStart  The inclusive line index where the function starts
 193/// @param funcEnd    The exclusive line index where the function ends
 194/// @param procName   The name of the procedure file
 195/// @param procIndex  The index of the procedure file
 196/// @param totals     The totals wave with the data from the instrumentation run
 197/// @param marker     The wave with can tell which line in the source file was instrumented
 198///
 199/// @returns The report XML
 0200static Function/S GetFunctionReport(string funcName, variable funcStart, variable funcEnd, string procName, variable pro
 0201  string report, linesReport, line, fullFuncName, msg
 0202  STRUCT IUTF_Cobertura_Metrics metrics
 0203  variable err, complexity, complexIndex
 204
 0205  complexIndex = FindDimLabel(marker, UTF_COLUMN, "COMPLEX")
 0206  WaveStats/M=1/Q/Z/RMD=[funcStart, funcEnd - 1][complexIndex, complexIndex] marker
 0207  complexity = V_sum
 208
 0209  [linesReport, metrics] = GetLinesReport("\t\t\t\t\t\t\t", funcStart, funcEnd, procIndex, totals, marker)
 210
 0211  fullFuncName = IUTF_Basics#getFullFunctionName(err, funcName, procName)
 0212  if(err)
 0213    // possibly a macro which has no full function name
 0214    fullFuncName = funcName
 0215  endif
 216
 0217  sprintf line, "\t\t\t\t\t\t<method name=\"%s\" signature=\"%s\" line-rate=\"%f\" branch-rate=\"%f\" complexity=\"%d\">
 0218  report = line
 219
 0220  report += linesReport
 221
 0222  report += "\t\t\t\t\t\t</method>\n"
 0223  return report
 0224End
 225
 226/// @brief Creates a report of a procedure file
 227///
 228/// @brief procName   The name of the procedure file
 229/// @brief procPath   The path to the procedure file
 230/// @brief procIndex  The index of the procedure file
 231/// @param totals     The totals wave with the data from the instrumentation run
 232/// @param marker     The wave with can tell which line in the source file was instrumented
 233/// @param sources    The source wave which contains all valid source paths
 234///
 235/// @returns The report XML
 0236static Function/S GetProcedureReport(string procName, string procPath, variable procIndex, WAVE totals, WAVE marker, WAV
 0237  string msg, report, line
 0238  variable i, funcCount, funcEnd, epochTime, complexity, complexIndex
 0239  STRUCT IUTF_Cobertura_ProcInfo info
 0240  STRUCT IUTF_Cobertura_Metrics  metrics
 0241  string                         linesReport
 242
 0243  [info] = GetProcInfo(procPath, sources)
 0244  WAVE/WAVE funcLocations = IUTF_Tracing#GetFuncLocations()
 0245  WAVE/T    procFuncNames = funcLocations[procIndex][%FUNCLIST]
 0246  WAVE      procFuncLines = funcLocations[procIndex][%FUNCSTART]
 0247  funcCount = DimSize(procFuncNames, UTF_ROW)
 248
 0249  complexIndex = FindDimLabel(marker, UTF_COLUMN, "COMPLEX")
 0250  WaveStats/M=1/Q/Z/RMD=[][complexIndex, complexIndex] marker
 0251  complexity = V_sum
 252
 0253  [linesReport, metrics] = GetLinesReport("\t\t\t\t\t", 0, DimSize(marker, UTF_ROW), procIndex, totals, marker)
 254
 0255  // DateTime since unix epoch and in UTC. This is the short version of:
 0256  // (<current datetime> - <utc offset>) - (<epoch datetime> - <utc offset>)
 0257  epochTime = DateTime - Date2Secs(1970, 1, 1)
 258
 0259  report  = "<?xml version=\"1.0\"?>\n"
 0260  report += "<!DOCTYPE coverage SYSTEM \"https://cobertura.sourceforge.net/xml/coverage-04.dtd\">\n"
 0261  report += "<!--Cobertura coverage report generated by the Igor Pro package \"igortest\"-->\n"
 0262  sprintf line, "<coverage line-rate=\"%f\" branch-rate=\"%f\" lines-covered=\"%d\" lines-valid=\"%d\" branches-covered=
 0263          metrics.lineRate, metrics.branchRate, metrics.lineCovered, metrics.lineValid, metrics.branchCovered, metrics.b
 0264  report += line
 265
 0266  report += "\t<sources>\n"
 0267  sprintf line, "\t\t<source>%s</source>\n", FilePathToXml(info.sourcePath)
 0268  report += line
 0269  report += "\t</sources>\n"
 270
 0271  report += "\t<packages>\n"
 0272  sprintf line, "\t\t<package name=\"%s\" line-rate=\"%f\" branch-rate=\"%f\" complexity=\"%d\">\n", FilePathToXml(info.
 0273  report += line
 274
 0275  report += "\t\t\t<classes>\n"
 0276  sprintf line, "\t\t\t\t<class name=\"%s\" filename=\"%s\" line-rate=\"%f\" branch-rate=\"%f\" complexity=\"%d\">\n", I
 0277  report += line
 278
 0279  report += "\t\t\t\t\t<methods>\n"
 0280  for(i = 0; i < funcCount; i += 1)
 0281    if(i < funcCount - 1)
 0282      funcEnd = procFuncLines[i + 1]
 0283    else
 0284      funcEnd = DimSize(marker, UTF_ROW)
 0285    endif
 0286    report += GetFunctionReport(procFuncNames[i], procFuncLines[i], funcEnd, procName, procIndex, totals, marker)
 0287  endfor
 0288  report += "\t\t\t\t\t</methods>\n"
 289
 0290  report += linesReport
 291
 0292  report += "\t\t\t\t</class>\n"
 0293  report += "\t\t\t</classes>\n"
 294
 0295  report += "\t\t</package>\n"
 0296  report += "\t</packages>\n"
 297
 0298  report += "</coverage>\n"
 0299  return report
 0300End
 301
 302/// @brief Generate the cobertura report for each instrumented procedure file and write each report
 303/// to the home directory of the experiment.
 304///
 305/// @param sources A comma delimited list of source paths that should be used for the cobertura
 306///                generation. If this string is empty it will use the current home directory as
 307///                source path.
 308/// @param outDir  The output path for the generated files. If this string is empty it will use the
 309///                current home directory.
 6310static Function PrintReport(string sources, string outDir)
 6311  variable i, procCount, nameIndex, pathIndex
 6312  string name, path, report, msg
 313
 6314  WAVE/T procs = IUTF_Tracing#GetTracedProcedureInfos()
 6315  procCount = IUTF_Utils_Vector#GetLength(procs)
 6316  nameIndex = FindDimLabel(procs, UTF_COLUMN, "NAME")
 6317  pathIndex = FindDimLabel(procs, UTF_COLUMN, "PATH")
 318
 6319  if(!IUTF_Tracing_Analytics#HasTracingData())
 0320    IUTF_Reporting#ReportErrorAndAbort("Bug: No tracing data exists")
 0321    return NaN
 6322  endif
 323
 6324  WAVE totals = IUTF_Tracing_Analytics#GetTotals()
 0325  if(!DimSize(totals, UTF_ROW))
 0326    // this can happen after stored Experiment is loaded to a fresh instance of Igor
 0327    IUTF_Reporting#ReportErrorAndAbort("Bug: TUFXOP has no data. Try to rerun tracing to get new data.")
 0328    return NaN
 0329  endif
 330
 0331  WAVE/WAVE instrMarker = IUTF_Tracing#GetInstrumentedMarker()
 332
 0333  if(IUTF_Utils#IsEmpty(sources))
 0334    Make/T/N=1/FREE=1 wvSources
 0335    wvSources[0] = IUTF_Utils_Paths#GetHomePath()
 0336  else
 0337    WAVE/T wvSources = ListToTextWave(sources, ",")
 0338  endif
 0339  wvSources[] = ParseFilePath(2, ParseFilePath(5, wvSources[p], "\\", 0, 0), "\\", 0, 0)
 340
 0341  printf "Generate Cobertura reports"
 342
 0343  for(i = 0; i < procCount; i += 1)
 0344    name = procs[i][nameIndex]
 0345    path = procs[i][pathIndex]
 0346    WAVE/Z marker = instrMarker[i]
 347
 0348    if(IUTF_Utils#IsEmpty(path) || !WaveExists(marker))
 0349      continue
 0350    endif
 351
 0352    printf "."
 353
 0354    report = GetProcedureReport(name, path, i, totals, marker, wvSources)
 355
 0356    if(strlen(report) >= FILESIZE_WARNING_LIMIT)
 0357      sprintf msg, "WARNING! The report size of \"%s\" (%.2W1PB) exceed suggested maximum file size of %.2W1PB.", name, 
 0358      IUTF_Reporting#IUTF_PrintStatusMessage(msg)
 0359      sprintf msg, "WARNING! Some Cobertura consumer like Gitlab could have issues reading such large files."
 0360      IUTF_Reporting#IUTF_PrintStatusMessage(msg)
 0361      sprintf msg, "WARNING! Try to split your procedure file into multiple smaller ones."
 0362      IUTF_Reporting#IUTF_PrintStatusMessage(msg)
 0363    endif
 364
 0365    IUTF_Utils_XML#WriteXML("Cobertura_", report, outDir = outDir)
 0366  endfor
 367
 0368  printf "\n"
 0369  IUTF_Reporting#IUTF_PrintStatusMessage("Cobertura export finished.")
 6370End
 371
 372#endif // UTF_ALLOW_TRACING