Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8e9c271
Added in a python method that calls sys.settrace and stores the resul…
isc-cge Jun 24, 2024
b9845f8
Got rid of the extra argument in PyStartWithCoverage
isc-cge Jun 27, 2024
d591894
Added a way to hash .PY files in CodeUnit.GetCurrentHash()
isc-cge Jun 27, 2024
d0ce282
Added code to find what lines in a python file are executable, and se…
isc-cge Jun 28, 2024
e9860ce
Finished GetCurrentByName and UpdateSourceMap: GetCurrentByName now s…
isc-cge Jul 1, 2024
baf4f70
Wrote code that successfully saves the line monitoring into ^IRIS.TEM…
isc-cge Jul 2, 2024
91af8be
Meant to be included with the previous commit
isc-cge Jul 2, 2024
7570938
Storing the sys.settrace data as an array indexed by class name, and …
isc-cge Jul 2, 2024
642b813
Wrote the HasPython Method
isc-cge Jul 2, 2024
8300839
Addendum to previous commit: previously, also made the call in EndCov…
isc-cge Jul 3, 2024
d6b81ca
Fetch the executable lines for the python methods in .cls files; whol…
isc-cge Jul 3, 2024
58f1059
Merge branch 'master' of https://github.com/intersystems/TestCoverage…
isc-cge Jul 5, 2024
96098b8
Changed global name from IRIS.TEMPCJG to IRIS.TEMP.TestCoveragePY
isc-cge Jul 5, 2024
f2b21bd
Manually set the python class method definition lines to not executable
isc-cge Jul 5, 2024
6837c62
Added the source code lines in to the .py CodeUnits
isc-cge Jul 5, 2024
bcd58fa
Added cyclomatic complexities for python subunits
isc-cge Jul 5, 2024
040e56a
Cleaned up code with the old method of storing monitor results in PyM…
isc-cge Jul 8, 2024
cc3700d
Added unit tests for Python CodeUnit and embedded python in .CLS files
isc-cge Jul 8, 2024
5e5ca7e
Added a test to make sure the embedded python methods' complexities a…
isc-cge Jul 8, 2024
d2e3ac5
Cleaned up a bit of dead code in CodeUnit and commented a lot of code
isc-cge Jul 8, 2024
793046f
Edited changelog and module.xml
isc-cge Jul 8, 2024
35c1192
fix: classes with only embedded python files are now covered
isc-cge Jul 12, 2024
3c54cd8
merged changes from 3.1.0
isc-cge Jul 12, 2024
d30dcdf
Merged CoverageTargetsWhitespace branch
isc-cge Jul 15, 2024
1e0f4e4
style: addressed Tim's comments on pr 37 (and simplified the sql stat…
isc-cge Jul 15, 2024
08e9902
fix: testcoveragelist doesn't fail if FindCoverageList doesn't find s…
isc-cge Jul 15, 2024
eb6077f
feat: the python monitor results now store Rtnline metrics, and in a …
isc-cge Jul 16, 2024
4ef2261
fix: changed pSizeHint to tSizeHint
isc-cge Jul 17, 2024
d07947e
style: cleaned up some dead code
isc-cge Jul 17, 2024
db53aa9
style: cleaned up some dead code
isc-cge Jul 17, 2024
10f4e2d
Merge branch 'EmbeddedPython' of https://github.com/intersystems/Test…
isc-cge Jul 18, 2024
c4c0164
feat: initialized all metrics to 0 so that none are empty at the end
isc-cge Jul 19, 2024
4963be8
feat: added a property LineIsPython that stores if each line of code …
isc-cge Jul 22, 2024
b1cd1b9
style: removed debugging global
isc-cge Jul 22, 2024
2854c18
style: changed IRIS.TEMP to IRIS.Temp
isc-cge Jul 22, 2024
a0221c2
Merge branch 'master' of https://github.com/intersystems/TestCoverage…
isc-cge Jul 29, 2024
9a2da9b
feat: added listener interface and manager with broadcasting on test …
isc-cge Jul 30, 2024
9f7b9cb
feat: changed the broadcast message type from strig to json
isc-cge Jul 31, 2024
d6b3079
style: added in a broadcast for starting tests too
isc-cge Jul 31, 2024
0161e47
Merge branch 'OutputTracking' into EmbeddedPython
isc-cge Jul 31, 2024
25ae6b1
docs: edited changelog
isc-cge Jul 31, 2024
410b2bf
merged PR 42
isc-cge Jul 31, 2024
abcd54c
Merge PR 41
isc-cge Jul 31, 2024
73119cf
Merge branch 'OutputTracking' into EmbeddedPython
isc-cge Jul 31, 2024
05300c6
docs: fixed changelog.md again
isc-cge Jul 31, 2024
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
239 changes: 200 additions & 39 deletions cls/TestCoverage/Data/CodeUnit.cls
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Index NameTypeHash On (Name, Type, Hash) [ Data = ExecutableLines, Unique ];
/// Name of the code unit
Property Name As %String(MAXLEN = 255) [ Required ];

/// Type (3-letter extension) of the code unit
/// Type (2 or 3-letter extension) of the code unit
Property Type As TestCoverage.DataType.RoutineType [ Required ];

/// Hash of the code unit; for methods for determining this, see <method>GetCurrentHash</method>
Expand All @@ -28,8 +28,12 @@ Property ExecutableLines As TestCoverage.DataType.Bitstring;

/// For classes, map of method names in the code to their associated line numbers
/// For routines, map of labels to associated line numbers
/// For python, map of method names to associated starting line number
Property MethodMap As array Of %Integer;

/// Only for python: map of method names to associated ending line number of the method
Property MethodEndMap As array Of %Integer;

/// For classes, map of line numbers in code to associated method names
/// For routines, map of labels to associated line numbers
Property LineToMethodMap As array Of %Dictionary.CacheIdentifier [ Private ];
Expand Down Expand Up @@ -98,10 +102,21 @@ ClassMethod GetCurrentByName(pInternalName As %String, pSourceNamespace As %Stri
}

Set $Namespace = pSourceNamespace

If (tType = "CLS") {
Do ##class(TestCoverage.Utils).GetClassLineExecutableFlags(tName,.tCodeArray,.tExecutableFlags)
} Else {
} ElseIf ((tType = "INT") || (tType = "MAC")) {
Do ##class(TestCoverage.Utils).GetRoutineLineExecutableFlags(.tCodeArray,.tExecutableFlags)
} ElseIf (tType="PY") {
Do ##class(TestCoverage.Utils).CodeArrayToList(.tCodeArray, .pDocumentText)
Set tExecutableFlagsPyList = ##class(TestCoverage.Utils).GetPythonLineExecutableFlags(pDocumentText)
Kill tExecutableFlags
for i=1:1:tExecutableFlagsPyList."__len__"()-1 {
set tExecutableFlags(i) = tExecutableFlagsPyList."__getitem__"(i)
}
}
Else {
return $$$ERROR($$$GeneralError,"File type not supported")
}

Set $Namespace = tOriginalNamespace
Expand All @@ -120,46 +135,81 @@ ClassMethod GetCurrentByName(pInternalName As %String, pSourceNamespace As %Stri
Set pCodeUnit.Generated = ($$$comClassKeyGet(tName,$$$cCLASSgeneratedby) '= "")
}

Set tMethod = ""
Set tMethodSignature = ""
Set tMethodMask = ""
For tLineNumber=1:1:$Get(tCodeArray,0) {
Set tLine = tCodeArray(tLineNumber)
Do pCodeUnit.Lines.Insert(tLine)

If (tType = "CLS") {
// Extract line offset of methods in classes
Set tStart = $Piece(tLine," ")
If (tStart = "ClassMethod") || (tStart = "Method") {
Set tMethod = $Piece($Piece(tLine,"(")," ",2)
Set tMethodSignature = tLine
Do pCodeUnit.MethodMap.SetAt(tLineNumber,tMethod)
Do pCodeUnit.LineToMethodMap.SetAt(tMethod,tLineNumber)
} ElseIf ($Extract(tStart) = "{") {
// Ignore the opening bracket for a method.
} ElseIf ($Extract(tStart) = "}") && (tMethod '= "") {
// End of method. Add method subunit to class.
Set tSubUnit = ##class(TestCoverage.Data.CodeSubUnit.Method).%New()
Set tSubUnit.Name = tMethod
Set tSubUnit.DisplaySignature = tMethodSignature
Set tSubUnit.Mask = tMethodMask
Do pCodeUnit.SubUnits.Insert(tSubUnit)
Set tMethod = ""
Set tMethodSignature = ""
Set tMethodMask = ""
} ElseIf (tMethod '= "") {
Set $Bit(tMethodMask,tLineNumber) = 1

If (tType = "PY") {
Set ClassName = $Piece(tName,".", *)
Set tMethodInfo = ##class(TestCoverage.Utils).GetPythonMethodMapping(pDocumentText, ClassName)
Set tLineToMethodInfo = tMethodInfo."__getitem__"(0)
Set tMethodMapInfo = tMethodInfo."__getitem__"(1)
for i=1:1:$listlength(pDocumentText) {
Set tMethod = tLineToMethodInfo."__getitem__"(i)
Do pCodeUnit.LineToMethodMap.SetAt(tMethod,i)
}
Set iterator = tMethodMapInfo."__iter__"()
for i=1:1:tMethodMapInfo."__len__"() {
Set tMethod = iterator."__next__"()
Set tStartEnd = tMethodMapInfo."__getitem__"(tMethod)
Set tStartLine = tStartEnd."__getitem__"(0)
Set tEndLine = tStartEnd."__getitem__"(1)
Do pCodeUnit.MethodMap.SetAt(tStartLine,tMethod)
Do pCodeUnit.MethodEndMap.SetAt(tEndLine, tMethod)

Set tMethodMask = ""
for j = tStartLine:1:tEndLine {
Set $Bit(tMethodMask,j) = 1
}
} Else {
// Extract line offset of labels in routines
If ($ZStrip($Extract(tLine),"*PWC") '= "") {
Set tLabel = $Piece($Piece(tLine," "),"(")
Do pCodeUnit.MethodMap.SetAt(tLineNumber,tLabel)
Do pCodeUnit.LineToMethodMap.SetAt(tLabel,tLineNumber)
Set tMethodSignature = $list(pDocumentText, tStartLine)
Set tSubUnit = ##class(TestCoverage.Data.CodeSubUnit.Method).%New()
Set tSubUnit.Name = tMethod
Set tSubUnit.DisplaySignature = tMethodSignature
Set tSubUnit.Mask = tMethodMask
Do pCodeUnit.SubUnits.Insert(tSubUnit)
}
}
Else {
Set tMethod = ""
Set tMethodSignature = ""
Set tMethodMask = ""
For tLineNumber=1:1:$Get(tCodeArray,0) {
Set tLine = tCodeArray(tLineNumber)
Do pCodeUnit.Lines.Insert(tLine)

If (tType = "CLS") {
// Extract line offset of methods in classes
Set tStart = $Piece(tLine," ")
If (tStart = "ClassMethod") || (tStart = "Method") {
Set tMethod = $Piece($Piece(tLine,"(")," ",2)
Set tMethodSignature = tLine
Do pCodeUnit.MethodMap.SetAt(tLineNumber,tMethod)
Do pCodeUnit.LineToMethodMap.SetAt(tMethod,tLineNumber)
} ElseIf ($Extract(tStart) = "{") {
// Ignore the opening bracket for a method.
} ElseIf ($Extract(tStart) = "}") && (tMethod '= "") {
// End of method. Add method subunit to class.
Set tSubUnit = ##class(TestCoverage.Data.CodeSubUnit.Method).%New()
Set tSubUnit.Name = tMethod
Set tSubUnit.DisplaySignature = tMethodSignature
Set tSubUnit.Mask = tMethodMask
Do pCodeUnit.SubUnits.Insert(tSubUnit)
Set tMethod = ""
Set tMethodSignature = ""
Set tMethodMask = ""
} ElseIf (tMethod '= "") {
Set $Bit(tMethodMask,tLineNumber) = 1
}
}
Else {
// Extract line offset of labels in routines
If ($ZStrip($Extract(tLine),"*PWC") '= "") {
Set tLabel = $Piece($Piece(tLine," "),"(")
Do pCodeUnit.MethodMap.SetAt(tLineNumber,tLabel)
Do pCodeUnit.LineToMethodMap.SetAt(tLabel,tLineNumber)
}
}
}
}


Set tSC = pCodeUnit.%Save()
If $$$ISERR(tSC) && $System.Status.Equals(tSC,$$$ERRORCODE($$$IDKeyNotUnique)) {
// Some other process beat us to it.
Expand All @@ -183,10 +233,82 @@ ClassMethod GetCurrentByName(pInternalName As %String, pSourceNamespace As %Stri
Quit tSC
}

/// Get the executable lines of code in python over to the .cls CodeUnit
Method UpdatePyExecutableLines(pName As %String, ByRef pPyCodeUnit) As %Status
{
Set tSC = $$$OK
Set tOriginalNamespace = $Namespace
Set tInitTLevel = $TLevel
Try {
TSTART

Set tBitString = ""
If (##class(TestCoverage.Manager).HasPython(pName)) {

// snapshot the Python CodeUnit for it
Set tFromHash = pPyCodeUnit.Hash
Set tToHash = ..Hash
// now bring over the executable lines

&sql(
DECLARE C1 CURSOR FOR
SELECT map.ToLine
INTO :hToLine
FROM TestCoverage_Data.CodeUnitMap map
JOIN TestCoverage_Data.CodeUnit fromCodeUnit
ON map.FromHash = fromCodeUnit.Hash
JOIN TestCoverage_Data.CodeUnit toCodeUnit
ON map.ToHash = toCodeUnit.Hash
AND fromCodeUnit.Hash = :tFromHash
AND toCodeUnit.Hash = :tToHash
WHERE TestCoverage.BIT_VALUE(fromCodeUnit.ExecutableLines,map.FromLine) <> 0
)
&sql(OPEN C1)
If (SQLCODE < 0) {
Throw ##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE,%msg)
}
For {
&SQL(FETCH C1)
If (SQLCODE < 0) {
Throw ##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE,%msg)
} ElseIf (SQLCODE) {
Quit
}
// Process the fetched rows
// hToLine contains the line number in the .cls file corresponding to executable lines in the .py file
Set $Bit(tBitString, hToLine) = 1
}
&sql(CLOSE C1)
If (SQLCODE < 0) {
Throw ##class(%Exception.SQL).CreateFromSQLCODE(SQLCODE,%msg)
}
}
if (pName = "TestCoverage.Utils") {
set ^IRIS.TEMPCG($i(^IRIS.TEMPCG)) = tBitString
set ^IRIS.TEMPCG($i(^IRIS.TEMPCG)) = ..ExecutableLines
}
Set ..ExecutableLines = $BITLOGIC(..ExecutableLines | tBitString)
Set tSC = ..%Save()
$$$ThrowOnError(tSC)
if (pName = "TestCoverage.Utils") {
set ^IRIS.TEMPCG($i(^IRIS.TEMPCG)) = ..ExecutableLines
}
TCOMMIT
} Catch e {
Set pCodeUnit = $$$NULLOREF
Set tSC = e.AsStatus()
}
While ($TLevel > tInitTLevel) {
TROLLBACK 1
}
Quit tSC
}

Method UpdateSourceMap(pSourceNamespace As %String, ByRef pCache) As %Status
{
Set tSC = $$$OK
Try {

// First, build local array (tMap) of all maps from the .INT file to other files.
If (..Type = "INT") {
For tLineNumber=1:1:..Lines.Count() {
Expand Down Expand Up @@ -225,6 +347,37 @@ Method UpdateSourceMap(pSourceNamespace As %String, ByRef pCache) As %Status
}
}
}
If (..Type = "PY") {

set tClass = ..Name
Set tSourceUnits(tClass_".CLS") = ""
$$$ThrowOnError(..GetCurrentByName(tClass _ ".CLS", pSourceNamespace, .pCLSCodeUnit, .pCLSCache))
// we'll do the mappings from the .py to the .cls direction, so that we don't iterate over objectscript lines
Set tMethod = ""
Do ..MethodMap.GetNext(.tMethod)
while (tMethod '= "")
{
Set tCLSMethodNum = pCLSCodeUnit.MethodMap.GetAt(tMethod)
Set tMethodStart = ..MethodMap.GetAt(tMethod)
Set tMethodEnd = ..MethodEndMap.GetAt(tMethod)
Set tMethodName = tMethod
Set tFullMap(tMethodStart) = $lb("CLS", tClass,tMethodName, -1, -1) ; -1 because the class
; definition doesn't have the +1 offset from the {

Do ..MethodMap.GetNext(.tMethod)
For i = tMethodStart+1:1:tMethodEnd {
Set tClassLineNum = i-tMethodStart
Set tFullMap(i) = $lb("CLS", tClass,tMethodName, tClassLineNum, tClassLineNum)

// extra check to make sure that the lines we're mapping between are the same as expected
Set tClassLineCode = $zstrip(pCLSCodeUnit.Lines.GetAt(tCLSMethodNum + tClassLineNum + 1), "<>W")
Set tPyLineCode = $zstrip(..Lines.GetAt(i), "<>W")
if (tPyLineCode '= tClassLineCode) {
Set tSC = $$$ERROR($$$GeneralError,"Compiled .py code doesn't match .CLS python code ")
}
}
}
}

// If we are a generator .INT file, ensure that we have source for the original class populated.
// In such files, the second line looks like (for example):
Expand Down Expand Up @@ -270,7 +423,7 @@ Method UpdateSourceMap(pSourceNamespace As %String, ByRef pCache) As %Status
Set tCodeUnits(tCodeUnit.Type,tCodeUnit.Name) = tCodeUnit
}

// Create CodeUnitMap data based on .INT->.CLS mapping.
// Create CodeUnitMap data based on .INT / .py ->.CLS mapping.
Set tFromHash = ..Hash
Set tLineNumber = ""
For {
Expand Down Expand Up @@ -396,6 +549,10 @@ ClassMethod GetCurrentHash(pName As %String, pType As %String, Output pHash As %
// Skip header (lines 1-4) which, for .INT routines generated from classes,
// includes the class compilation signature.
Set pHash = ..HashArrayRange(.pCodeArray,5,pName_"."_pType,.tSizeHint)
} ElseIf (pType = "PY") {
Merge pCodeArray = ^ROUTINE(pName_".py",0)
set tSizeHint = ^ROUTINE(pName_".py",0,0)
set pHash = ..HashArrayRange(.pCodeArray, ,pName_".py", .pSizeHint)
} Else {
// Give standard descriptive error about the type being invalid.
$$$ThrowStatus(..TypeIsValid(pType))
Expand Down Expand Up @@ -478,6 +635,11 @@ Storage Default
<Structure>subnode</Structure>
<Subscript>"Lines"</Subscript>
</Data>
<Data name="MethodEndMap">
<Attribute>MethodEndMap</Attribute>
<Structure>subnode</Structure>
<Subscript>"MethodEndMap"</Subscript>
</Data>
<Data name="MethodMap">
<Attribute>MethodMap</Attribute>
<Structure>subnode</Structure>
Expand All @@ -492,4 +654,3 @@ Storage Default
}

}

Loading