Skip to content

Commit 427888b

Browse files
committed
Initial commit of code
1 parent fd3cfde commit 427888b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+6037
-2
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
*.bak
2+
*.xml
3+
readme.txt

CONTRIBUTING.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# How to contribute
2+
3+
Thank you for your interest in contributing! While this project originated at InterSystems, it is our hope that the community will continue to extend and enhance it. Our priority is to add and enhance features to meet our internal and general use cases. We do not intend to provide adapters to other code coverage reporting tools and formats, but if you make one, please do share it with the community!
4+
5+
## Submitting changes
6+
7+
If you have made a change that you would like to contribute back to the community, please send a [GitHub Pull Request](/pull/new/master) explaining it. If your change fixes an issue that you or another user reported, please mention it in the pull request. You can find out more about pull requests [here](http://help.github.com/pull-requests/).
8+
9+
## Coding conventions
10+
11+
Generally speaking, just try to match the conventions you see in the code you are reading. For this project, these include:
12+
13+
* Do not use shortened command and function names - e.g., `s` instead of `set`, `$p` instead of `$piece`
14+
* One command per line
15+
* Do not use dot syntax
16+
* Indentation with tabs
17+
* Pascal case class and method names
18+
* Avoid using postconditionals
19+
* Local variables start with `t`; formal parameter names start with `p`
20+
* Always check %Status return values
21+
22+
Thanks,
23+
Tim Leavitt, InterSystems Corporation

README.md

Lines changed: 122 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,122 @@
1-
# TestCoverage
2-
Test Coverage Tool
1+
# Unit Test Coverage for ObjectScript
2+
3+
Run your typical ObjectScript %UnitTest tests and see which lines of your code are executed. Includes Cobertura-style reporting for use in continuous integration tools.
4+
5+
## Getting Started
6+
7+
### Installation: from Terminal
8+
9+
First, clone or download the repository. Then run the following commands:
10+
11+
```
12+
Set root = "<path on filesystem to which repository was cloned/downloaded>"
13+
Do $System.OBJ.ImportDir(root,"*.inc;*.cls","ck",,1)
14+
```
15+
16+
### Installation: from Atelier
17+
18+
Note: this assumes you have the Git plugin for Atelier installed.
19+
20+
1. Right-click in the Atelier Explorer and select Import...
21+
2. Select the "Projects from Git" wizard under the "Git" folder, then click "Next"
22+
3. In the "Select Repository Source" dialog, select "Clone URI" and enter this repository's URI, then click "Next"
23+
4. Select the branch(es) you intend to build from, then click "Next"
24+
5. Select a local destination in which to store the code, then click "Next"
25+
6. Choose "import existing Eclipse projects" and select the TestCoverage project, then click "Next"
26+
7. Configure a connection for the project
27+
8. Synchronize the project with your selected namespace
28+
29+
### Security
30+
Note that, depending on your security settings, SQL privileges may be required for access to test coverage data. The relevant permissions may be granted by running:
31+
32+
```
33+
zw ##class(TestCoverage.Utils).GrantSQLReadPermissions("<username or role that should have read permissions>")
34+
```
35+
36+
For example:
37+
38+
```
39+
zw ##class(TestCoverage.Utils).GrantSQLReadPermissions("_PUBLIC")
40+
```
41+
42+
## User Guide
43+
44+
### Running Tests with Coverage
45+
Generally speaking, set `^UnitTestRoot`, and then call `##class(TestCoverage.Manager).RunTest()` the same you would call `##class(%UnitTest.Manager).RunTest()`. For more information on InterSystems' %UnitTest framework, see the [tutorial](https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=TUNT) and/or the [class reference for %UnitTest.Manager](https://docs.intersystems.com/latest/csp/documatic/%25CSP.Documatic.cls?PAGE=CLASS&LIBRARY=%25SYS&CLASSNAME=%25UnitTest.Manager).
46+
47+
The "userparam" argument can be used to pass information about code coverage data collection. For example:
48+
49+
```
50+
Set tCoverageParams("CoverageClasses") = <$ListBuild list of class names for which code coverage data should be collected>
51+
Set tCoverageParams("CoverageRoutines") = <$ListBuild list of routine names for which code coverage data should be collected>
52+
Set tCoverageParams("CoverageDetail") = <0 to track code coverage overall; 1 to track it per test suite (the default); 2 to track it per test class; 3 to track it per test method.>
53+
Do ##class(TestCoverage.Manager).RunTest(,,.tCoverageParams)
54+
```
55+
56+
The first two arguments to `TestCoverage.Manager:RunTest` are the same as `%UnitTest.Manager`.
57+
58+
At the selected level of granularity (before all tests or a test suite/case/method is run), there will be a search for a file named "coverage.list" within the directory for the test suite and parent directories, stopping at the first such file found. This file may contain a list of classes, packages, and routines for which code coverage will be measured. For .MAC routines only (not classes/packages), the coverage list also supports the * wildcard. It is also possible to exclude classes/packages by prefixing the line with "-". For example, to track coverage for all classes in the `MyApplication` package (except those in the `MyApplication.UI` subpackage), and all routines with names starting with "MyApplication":
59+
60+
```
61+
// Include all application code
62+
MyApplication.PKG
63+
MyApplication*.MAC
64+
65+
// Exclude Zen Pages
66+
-MyApplication.UI.PKG
67+
```
68+
69+
As an alternative approach, with unit test classes that have already been loaded and compiled (and which will not be deleted after running tests) and a known list of classes and routines for which code coverage should be collected, use:
70+
71+
```
72+
Do ##class(TestCoverage.Manager).RunAllTests(tPackage,tLogFile,tCoverageClasses,tCoverageRoutines,tCoverageLevel,.tLogIndex,tSourceNamespace,tProcessIDs,tTiming)
73+
```
74+
75+
Where:
76+
77+
* `tPackage` has the top-level package containing all the unit test classes to run. These must already be loaded.
78+
* `tLogFile` (optional) may specify a file to log all output to.
79+
* `tCoverageClasses` (optional) has a $ListBuild list of class names within which to track code coverage. By default, none are tracked.
80+
* `tCoverageRoutines` (optional) has a $ListBuild list of routine names within which to track code coverage. By default, none are tracked.
81+
* `tCoverageLevel` (optional) is 0 to track code coverage overall; 1 to track it per test suite (the default); 2 to track it per test class; 3 to track it per test method.
82+
* `tLogIndex` (optional) allows for aggregation of code coverage results across unit test runs. To use this, get it back as output from the first test run, then pass it to the next.
83+
* `tSourceNamespace` (optional) specifies the namespace in which classes were compiled, defaulting to the current namespace. This may be required to retrieve some metadata.
84+
* `tPIDList` (optional) has a $ListBuild list of process IDs to monitor. If this is empty, all processes are monitored. By default, only the current process is monitored.
85+
* `tTiming` (optional) is 1 to capture execution time data for monitored classes/routines as well, or 0 (the default) to not capture this data.
86+
87+
### Viewing Results
88+
After running the tests, a URL is shown in the output at which you can view test coverage results.
89+
90+
### Reporting on results in Cobertura format
91+
The `RunTest()` method reports back a log index in the "userparam" argument. This can be used to generate a report in the same format as Cobertura, a popular Java code coverage tool. For example:
92+
93+
```
94+
Set userParams("CoverageDetail") = 0
95+
Do ##class(TestCoverage.Manager).RunTest(,"/nodelete",.userParams)
96+
Set reportFile = "C:\Temp\Reports\"_tUserParams("LogIndex")_"\coverage.xml"
97+
Do ##class(TestCoverage.Report.Cobertura.ReportGenerator).GenerateReport(userParams("LogIndex"),reportFile)
98+
```
99+
100+
This exports both the coverage results themselves and the associated source code (in UDL format) for correlation/display, and has been verified with [the Cobertura plugin for Jenkins](https://wiki.jenkins.io/display/JENKINS/Cobertura+Plugin).
101+
102+
## Support
103+
104+
If you find a bug or would like to request an enhancement, [report an issue](/issues/new). If you have a question, post it on the [InterSystems Developer Community](https://community.intersystems.com/) - consider using the "Testing" or "Continuous Integration" tags as appropriate.
105+
106+
## Contributing
107+
108+
Please read [contributing](CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
109+
110+
## Versioning
111+
112+
We use [SemVer](http://semver.org/) for versioning. For the versions available, see the [tags on this repository](https://github.com/your/project/tags).
113+
114+
## Authors
115+
116+
* **Tim Leavitt** - *Initial implementation* - [timleavitt](http://github.com/timleavitt)
117+
118+
See also the list of [contributors](/contributors) who participated in this project.
119+
120+
## License
121+
122+
This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Class TestCoverage.Data.Aggregate.Base Extends %Persistent [ Abstract, NoExtent ]
2+
{
3+
4+
Property ExecutableLines As %Integer [ Required ];
5+
6+
Property CoveredLines As %Integer [ Required ];
7+
8+
Property ExecutableMethods As %Integer [ Required ];
9+
10+
Property CoveredMethods As %Integer [ Required ];
11+
12+
Property RtnLine As %Integer [ InitialExpression = 0 ];
13+
14+
Property Time As TestCoverage.DataType.Timing [ InitialExpression = 0 ];
15+
16+
Property TotalTime As TestCoverage.DataType.Timing [ InitialExpression = 0 ];
17+
18+
}
19+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
Class TestCoverage.Data.Aggregate.ByCodeUnit Extends TestCoverage.Data.Aggregate.Base
2+
{
3+
4+
Index RunCodeUnit On (Run, CodeUnit) [ Unique ];
5+
6+
Property Run As TestCoverage.Data.Run [ Required ];
7+
8+
ForeignKey RunFK(Run) References TestCoverage.Data.Run() [ OnDelete = cascade ];
9+
10+
Property CodeUnit As TestCoverage.Data.CodeUnit [ Required ];
11+
12+
ForeignKey CodeUnitFK(CodeUnit) References TestCoverage.Data.CodeUnit(Hash) [ OnDelete = cascade ];
13+
14+
Storage Default
15+
{
16+
<Data name="ByCodeUnitDefaultData">
17+
<Value name="1">
18+
<Value>%%CLASSNAME</Value>
19+
</Value>
20+
<Value name="2">
21+
<Value>ExecutableLines</Value>
22+
</Value>
23+
<Value name="3">
24+
<Value>CoveredLines</Value>
25+
</Value>
26+
<Value name="4">
27+
<Value>ExecutableMethods</Value>
28+
</Value>
29+
<Value name="5">
30+
<Value>CoveredMethods</Value>
31+
</Value>
32+
<Value name="6">
33+
<Value>RtnLine</Value>
34+
</Value>
35+
<Value name="7">
36+
<Value>Time</Value>
37+
</Value>
38+
<Value name="8">
39+
<Value>TotalTime</Value>
40+
</Value>
41+
<Value name="9">
42+
<Value>Run</Value>
43+
</Value>
44+
<Value name="10">
45+
<Value>CodeUnit</Value>
46+
</Value>
47+
</Data>
48+
<DataLocation>^TestCoverage.Data.Agg.ByCUD</DataLocation>
49+
<DefaultData>ByCodeUnitDefaultData</DefaultData>
50+
<IdLocation>^TestCoverage.Data.Agg.ByCUD</IdLocation>
51+
<IndexLocation>^TestCoverage.Data.Agg.ByCUI</IndexLocation>
52+
<StreamLocation>^TestCoverage.Data.Agg.ByCUS</StreamLocation>
53+
<Type>%Library.CacheStorage</Type>
54+
}
55+
56+
}
57+
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
Class TestCoverage.Data.Aggregate.ByRun Extends TestCoverage.Data.Aggregate.Base
2+
{
3+
4+
Index Run On Run [ Unique ];
5+
6+
Property Run As TestCoverage.Data.Run [ Required ];
7+
8+
ForeignKey RunFK(Run) References TestCoverage.Data.Run() [ OnDelete = cascade ];
9+
10+
Storage Default
11+
{
12+
<Data name="ByRunDefaultData">
13+
<Value name="1">
14+
<Value>%%CLASSNAME</Value>
15+
</Value>
16+
<Value name="2">
17+
<Value>ExecutableLines</Value>
18+
</Value>
19+
<Value name="3">
20+
<Value>CoveredLines</Value>
21+
</Value>
22+
<Value name="4">
23+
<Value>ExecutableMethods</Value>
24+
</Value>
25+
<Value name="5">
26+
<Value>CoveredMethods</Value>
27+
</Value>
28+
<Value name="6">
29+
<Value>RtnLine</Value>
30+
</Value>
31+
<Value name="7">
32+
<Value>Time</Value>
33+
</Value>
34+
<Value name="8">
35+
<Value>TotalTime</Value>
36+
</Value>
37+
<Value name="9">
38+
<Value>Run</Value>
39+
</Value>
40+
</Data>
41+
<DataLocation>^TestCoverage.Data.Agg.ByRunD</DataLocation>
42+
<DefaultData>ByRunDefaultData</DefaultData>
43+
<IdLocation>^TestCoverage.Data.Agg.ByRunD</IdLocation>
44+
<IndexLocation>^TestCoverage.Data.Agg.ByRunI</IndexLocation>
45+
<StreamLocation>^TestCoverage.Data.Agg.ByRunS</StreamLocation>
46+
<Type>%Library.CacheStorage</Type>
47+
}
48+
49+
}
50+

cls/TestCoverage/Data/CodeSubUnit.cls

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
Class TestCoverage.Data.CodeSubUnit Extends %Persistent [ Abstract ]
2+
{
3+
4+
Relationship Parent As TestCoverage.Data.CodeUnit [ Cardinality = parent, Inverse = SubUnits ];
5+
6+
/// Bitstring representing which lines are part of this section (method, branch, etc.) of the code
7+
Property Mask As TestCoverage.DataType.Bitstring;
8+
9+
/// Cyclomatic complexity of the code subunit
10+
Property Complexity As %Integer [ InitialExpression = 1 ];
11+
12+
Method UpdateComplexity() As %Status
13+
{
14+
Quit $$$OK
15+
}
16+
17+
Storage Default
18+
{
19+
<Data name="CodeSubUnitDefaultData">
20+
<Value name="1">
21+
<Value>%%CLASSNAME</Value>
22+
</Value>
23+
<Value name="2">
24+
<Value>Mask</Value>
25+
</Value>
26+
<Value name="3">
27+
<Value>Complexity</Value>
28+
</Value>
29+
</Data>
30+
<DataLocation>{%%PARENT}("SubUnits")</DataLocation>
31+
<DefaultData>CodeSubUnitDefaultData</DefaultData>
32+
<IdLocation>^TestCoverage.Data.CodeUnitC("SubUnits")</IdLocation>
33+
<IndexLocation>^TestCoverage.Data.CodeSubUnitI</IndexLocation>
34+
<StreamLocation>^TestCoverage.Data.CodeSubUnitS</StreamLocation>
35+
<Type>%Library.CacheStorage</Type>
36+
}
37+
38+
}
39+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
Class TestCoverage.Data.CodeSubUnit.Method Extends TestCoverage.Data.CodeSubUnit
2+
{
3+
4+
Property Name As %Dictionary.CacheIdentifier [ Required ];
5+
6+
Property DisplaySignature As %String(MAXLEN = "");
7+
8+
Method UpdateComplexity() As %Status
9+
{
10+
Set tSC = $$$OK
11+
Try {
12+
// Get int lines mapped to this method's mask.
13+
// As an optimization, find start/end of mask
14+
Set tMaskStart = $BitFind(..Mask,1,0,1)
15+
Set tMaskEnd = $BitFind(..Mask,1,0,-1)
16+
17+
// Get lines mapped to this method's mask
18+
// Get unique by map.ToLine to avoid issues with mapping of embedded SQL
19+
// (all lines of the generated query map back to the class line defining it)
20+
Set tResult = ##class(%SQL.Statement).%ExecDirect(,
21+
"select distinct by (map.ToLine) element_key ""Line"", Lines ""Code"" from TestCoverage_Data.CodeUnit_Lines intcode "_
22+
"join TestCoverage_Data.CodeUnitMap map "_
23+
" on map.FromHash = intcode.CodeUnit "_
24+
" and map.FromLine = intcode.element_key "_
25+
"where intcode.CodeUnit->Type = 'INT' "_
26+
" and map.ToHash = ? "_
27+
" and map.ToLine >= ? and map.ToLine <= ? "_
28+
"order by map.FromLine",..Parent.Hash,tMaskStart,tMaskEnd)
29+
If (tResult.%SQLCODE < 0) {
30+
Throw ##class(%Exception.SQL).CreateFromSQLCODE(tResult.%SQLCODE,tResult.%Message)
31+
}
32+
Set tCodeStream = ##class(%Stream.TmpCharacter).%New()
33+
While tResult.%Next(.tSC) {
34+
$$$ThrowOnError(tSC)
35+
$$$ThrowOnError(tCodeStream.WriteLine(tResult.%Get("Code")))
36+
}
37+
$$$ThrowOnError(tSC)
38+
39+
Set ..Complexity = ##class(TestCoverage.Utils.ComplexityParser).%New(tCodeStream).GetComplexity()
40+
$$$ThrowOnError(..%Save(0))
41+
} Catch e {
42+
Set tSC = e.AsStatus()
43+
}
44+
Quit tSC
45+
}
46+
47+
Storage Default
48+
{
49+
<Data name="MethodDefaultData">
50+
<Subscript>"Method"</Subscript>
51+
<Value name="1">
52+
<Value>Name</Value>
53+
</Value>
54+
<Value name="2">
55+
<Value>DisplaySignature</Value>
56+
</Value>
57+
</Data>
58+
<DefaultData>MethodDefaultData</DefaultData>
59+
<Type>%Library.CacheStorage</Type>
60+
}
61+
62+
}
63+

0 commit comments

Comments
 (0)