Skip to content
Open
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- #992: Implement automatic history purge logic
- #973: Enables CORS and JWT configuration for WebApplications in module.xml
- #971: Adds support for JSON, YAML, and Toon formats via the -f flag and new -DUnitTest.*Output directives.

### Fixed
- #1001: The `unmap` and `enable` commands will now only activate CPF merge once after all namespaces have been configured instead after every namespace
Expand Down
1 change: 1 addition & 0 deletions preload/cls/IPM/Installer.cls
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ ClassMethod ZPMInit(
$$$QuitOnError(##class(%IPM.Repo.UniversalSettings).SetValue("UseStandalonePip", "", 0))
$$$QuitOnError(##class(%IPM.Repo.UniversalSettings).SetValue("SemVerPostRelease", 0, 0))
$$$QuitOnError(##class(%IPM.Repo.UniversalSettings).SetValue("DefaultLogEntryLimit",20, 0))
$$$QuitOnError(##class(%IPM.Repo.UniversalSettings).SetValue("TestReportFormat","toon", 0))
quit $$$OK
}

Expand Down
1 change: 1 addition & 0 deletions src/cls/IPM/Main.cls
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ Can also specify desired version to update to.
<modifier name="dev" dataAlias="DeveloperMode" dataValue="1" description="Sets the DeveloperMode flag for the module's lifecycle. Key consequences of this are that ^Sources will be configured for resources in the module, and installer methods will be called with the dev mode flag set." />
<modifier name="quiet" aliases="q" dataAlias="Verbose" dataValue="0" description="Produces minimal output from the command." />
<modifier name="verbose" aliases="v" dataAlias="Verbose" dataValue="1" description="Produces verbose output from the command." />
<modifier name="output-format" aliases="output,format,f" dataAlias="outputformat" value="true" description="Specifies the desired output format for the unit and integration test result (e.g., json, yaml, toon)." />
<modifier name="export-deps" value="true" valueList="0,1" dataAlias="ExportDependencies" description="If specified, controls whether dependencies are exported. If omitted, defaults to the value of the #EXPORTDEPENDENCIES in lifecycle class. This modifier is only used in &quot;Package&quot; and &quot;Publish&quot; lifecycles." />

</command>
Expand Down
17 changes: 16 additions & 1 deletion src/cls/IPM/Repo/UniversalSettings.cls
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ Parameter SemVerPostRelease = "SemVerPostRelease";
/// to retain IPM history records before they are eligible for cleanup.
Parameter HistoryRetain = "history_retain";

Parameter CONFIGURABLE = "trackingId,analytics,ColorScheme,TerminalPrompt,PublishTimeout,PipCaller,UseStandalonePip,SemVerPostRelease,DefaultLogEntryLimit,HistoryRetain";
/// Specifies the serialization format (JSON, TOON, YAML) for unit and integration test results in the shell.
Parameter TestReportFormat = "TestReportFormat";

Parameter CONFIGURABLE = "trackingId,analytics,ColorScheme,TerminalPrompt,PublishTimeout,PipCaller,UseStandalonePip,SemVerPostRelease,DefaultLogEntryLimit,HistoryRetain,TestReportFormat";

/// Returns configArray, that includes all configurable settings
ClassMethod GetAll(Output configArray) As %Status
Expand Down Expand Up @@ -190,4 +193,16 @@ ClassMethod GetHistoryRetain() As %Integer
return ..GetValue(..#HistoryRetain)
}

ClassMethod SetTestReportFormat(
val As %String,
overwrite As %Boolean = 1) As %Boolean
{
return ..SetValue(..#TestReportFormat, val, overwrite)
}

ClassMethod GetTestReportFormat() As %String
{
return ..GetValue(..#TestReportFormat)
}

}
53 changes: 42 additions & 11 deletions src/cls/IPM/ResourceProcessor/Test.cls
Original file line number Diff line number Diff line change
Expand Up @@ -189,23 +189,54 @@ Method OnPhase(
zkill ^UnitTestRoot
$$$ThrowOnError(tSC)

if $data(pParams("UnitTest","JUnitOutput"),tJUnitFile) {
set tPostfix = "-"_$zconvert(pPhase,"L")_"-"
if (..Package '= "") {
set tPostfix = tPostfix_$replace(..Package,".","-")_"-PKG"
} elseif (..Class '= "") {
set tPostfix = tPostfix_$replace(..Class,".","-")_"-CLS"
if $data(pParams("UnitTest"))>1 {
set outputType=""
for {
set outputType = $order(pParams("UnitTest",outputType),1,fileName)
quit:outputType=""
set tPostfix = "-"_$$$lcase(pPhase)_"-"
if (..Package '= "") {
set tPostfix = tPostfix_$replace(..Package,".","-")_"-PKG"
} elseif (..Class '= "") {
set tPostfix = tPostfix_$replace(..Class,".","-")_"-CLS"
}
set outputClass = "%IPM.Test."_outputType
if '$$$defClassDefined(outputClass) {
$$$ThrowOnError($$$ERROR($$$GeneralError,"The requested "_outputType_" output format does not exist."))
}
set extension = $select(outputType="JsonOutput":".json",outputType="ToonOutput":".toon",outputType="YamlOutput":".yaml",1:".xml")
set fileName = $piece(fileName,".",1,*-1)_tPostfix_extension
set tSC = $classmethod(outputClass,"ToFile",fileName)
$$$ThrowOnError(tSC)
}
set tJUnitFile = $piece(tJUnitFile,".",1,*-1)_tPostfix_".xml"
set tSC = ##class(%IPM.Test.JUnitOutput).ToFile(tJUnitFile)
$$$ThrowOnError(tSC)
}
write !
if $data(pParams("outputformat"),outputFormat)||('tVerbose) {
write !,"Test result summary",!
set defaultOutputFormat = ##class(%IPM.Repo.UniversalSettings).GetTestReportFormat()
if defaultOutputFormat'="" {
set outputFormat = defaultOutputFormat
}
else {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: should be one line, i.e. } else {

if $get(outputFormat)="" {
set outputFormat="Toon"
}
}

set outputClass = "%IPM.Test."_$zconvert(outputFormat,"w")_"Output"
if '$$$defClassDefined(outputClass) {
$$$ThrowOnError($$$ERROR($$$GeneralError,"The requested "_outputType_" output format does not exist."))
}
set defaultTestStatus = "failed"
set sc = $classmethod(outputClass,"OutputToDevice",,defaultTestStatus)
$$$ThrowOnError(sc)
write !
}
// By default, detect and report unit test failures as an error from this phase
if $get(pParams("UnitTest","FailuresAreFatal"),1) {
do ##class(%IPM.Test.Manager).OutputFailures()
set tSC = ##class(%IPM.Test.Manager).GetLastStatus()
$$$ThrowOnError(tSC)
set sc = ##class(%IPM.Test.Manager).GetLastStatus()
$$$ThrowOnError(sc)
}
write !
}
Expand Down
68 changes: 68 additions & 0 deletions src/cls/IPM/Test/Abstract.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/// The class serves as the base class for all the unit test result formatting.
Class %IPM.Test.Abstract Extends %RegisteredObject
{

ClassMethod ToFile(
FileName As %String,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: method parameters should be in camelCase, not PascalCase

CaseStatus As %String = "",
TestIndex As %Integer = {$order(^UnitTest.Result(""),-1)}) As %Status [ Abstract ]
{
}

ClassMethod OutputToDevice(
TestIndex As %Integer = {$order(^UnitTest.Result(""),-1)},
TestStatus As %String = "") [ Abstract ]
{
}

Query FilteredTestResults(
Instance As %Integer,
TestStatus) As %SQLQuery(ROWSPEC = "TotalCounts:%Integer,namespace:%String,duration:%String,testDateTime:%String,suiteName:%String,testcaseName:%String,methodName:%String,testMethod:%String,assertAction:%String,assertCounter:%Integer,assertDescription:%String,assertLocation:%String", SELECTMODE = "DISPLAY")
{
SELECT
count(*) as TotalCounts,
tinstance.Namespace AS namespace,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: no need for t- prefixes here either

tinstance.Duration AS duration,
tinstance.DateTime AS testDateTime,
tsuite.Name AS suiteName,
tcase.Name AS testcaseName,
tmethod.Name AS methodName,
tassert.TestMethod AS testMethod,
tassert.Action AS assertAction,
tassert.Counter AS assertCounter,
tassert.Description AS assertDescription,
tassert.Location AS assertLocation
FROM
%UnitTest_Result.TestInstance tinstance
JOIN %UnitTest_Result.TestSuite tsuite ON tsuite.TestInstance=tinstance.ID
JOIN %UnitTest_Result.TestCase tcase ON tcase.TestSuite=tsuite.ID
JOIN %UnitTest_Result.TestMethod tmethod ON tmethod.TestCase=tcase.ID
JOIN %UnitTest_Result.TestAssert tassert ON tassert.TestMethod=tmethod.ID
WHERE tinstance.ID=:Instance AND tassert.Status=:TestStatus
}

Query GetAllTestResults(Instance As %Integer) As %SQLQuery(ROWSPEC = "TotalCounts:%String,namespace:%String,duration:%String,testDateTime:%String,suiteName:%String,testcaseName:%String,methodName:%String,testMethod:%String,assertAction:%String,assertCounter:%Integer,assertDescription:%String,assertLocation:%String", SELECTMODE = "DISPLAY")
{
SELECT
count(*) as TotalCounts,
tinstance.Namespace AS namespace,
tinstance.Duration AS duration,
tinstance.DateTime AS testDateTime,
tsuite.Name AS suiteName,
tcase.Name AS testcaseName,
tmethod.Name AS methodName,
tassert.TestMethod AS testMethod,
tassert.Action AS assertAction,
tassert.Counter AS assertCounter,
tassert.Description AS assertDescription,
tassert.Location AS assertLocation
FROM
%UnitTest_Result.TestInstance tinstance
JOIN %UnitTest_Result.TestSuite tsuite ON tsuite.TestInstance=tinstance.ID
JOIN %UnitTest_Result.TestCase tcase ON tcase.TestSuite=tsuite.ID
JOIN %UnitTest_Result.TestMethod tmethod ON tmethod.TestCase=tcase.ID
JOIN %UnitTest_Result.TestAssert tassert ON tassert.TestMethod=tmethod.ID
WHERE tinstance.ID=:Instance
}

}
93 changes: 93 additions & 0 deletions src/cls/IPM/Test/JsonOutput.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
Class %IPM.Test.JsonOutput Extends %IPM.Test.Abstract
{

ClassMethod ToFile(
FileName As %String,
TestStatus As %String = "",
TestIndex As %Integer = {$order(^UnitTest.Result(""),-1)}) As %Status
{
set sc = $$$OK
try {
set fileStream = ##class(%Stream.FileCharacter).%New()
set fileStream.TranslateTable = "UTF8"
$$$ThrowOnError(fileStream.LinkToFile(FileName))
set responseJson = ..JSON(TestIndex, TestStatus)
if $isobject(responseJson) {
do fileStream.Write(responseJson.%ToJSON())
}
$$$ThrowOnError(fileStream.%Save())
} catch ex {
set sc = ex.AsStatus()
}
return sc
}

ClassMethod OutputToDevice(
TestIndex As %Integer = {$order(^UnitTest.Result(""),-1)},
pCaseStatus As %String = "") As %Status
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: remove p- prefix and change to camelCase

{
set sc = $$$OK
try {
set responseJson = ..JSON(TestIndex, pCaseStatus)
write !
if $isobject(responseJson) {
do responseJson.%ToJSON()
}
} catch ex {
set sc = ex.AsStatus()
}
return sc
}

ClassMethod JSON(
TestIndex As %Integer,
TestStatus As %String) As %DynamicObject
{
if TestStatus'="" {
set tResult = ..FilteredTestResultsFunc(TestIndex, TestStatus)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: remove the t-prefixes

} else {
set tResult = ..GetAllTestResultsFunc(TestIndex)
}
set unitTest = {}
set unitTest.results = []
set (previousID,currentSuite,currentTestcase,suiteObj,testcaseObj) = ""

while tResult.%Next() {
if previousID = "" {
set unitTest.id = TestIndex
set unitTest.namespace = tResult.namespace
set unitTest.duration = tResult.duration
set unitTest.testDateTime = tResult.testDateTime
}
set previousID = TestIndex
if tResult.suiteName '= currentSuite {
set currentSuite = tResult.suiteName
set suiteObj = {
"suiteName": (currentSuite),
"testcases": []
}
do unitTest.results.%Push(suiteObj)
set currentTestcase = ""
}
if tResult.testcaseName '= currentTestcase {
set currentTestcase = tResult.testcaseName
set testcaseObj = {
"testcaseName": (currentTestcase),
"methods": []
}
do suiteObj.testcases.%Push(testcaseObj)
}
set methodObj = {
"methodName": (tResult.methodName),
"testMethod": (tResult.testMethod),
"assertAction": (tResult.assertAction),
"assertCounter": (tResult.assertCounter),
"assertDescription": (tResult.assertDescription),
"assertLocation": (tResult.assertLocation)
}
do testcaseObj.methods.%Push(methodObj)
}
return unitTest
}

}
77 changes: 77 additions & 0 deletions src/cls/IPM/Test/ToonOutput.cls
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
Class %IPM.Test.ToonOutput Extends %IPM.Test.Abstract
{

ClassMethod ToFile(
FileName As %String,
TestStatus As %String = "",
TestIndex As %Integer = {$order(^UnitTest.Result(""),-1)}) As %Status
{
set sc = $$$OK
try {
set fileStream = ##class(%Stream.FileCharacter).%New()
set fileStream.TranslateTable = "UTF8"
$$$ThrowOnError(fileStream.LinkToFile(FileName))

if TestStatus'="" {
set tResult = ..FilteredTestResultsFunc(TestIndex, TestStatus)
} else {
set tResult = ..GetAllTestResultsFunc(TestIndex)
}

set currentID=""
while tResult.%Next() {
if currentID = "" {
set currentID = TestIndex
do fileStream.WriteLine("unitTest:")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lots of statuses that need checking here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. Updated the code!

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still need to check the %Status returned by the WriteLine() calls

do fileStream.WriteLine(" id: "_TestIndex)
do fileStream.WriteLine(" namespace: "_tResult.namespace)
do fileStream.WriteLine(" duration: "_tResult.duration)
do fileStream.WriteLine(" testDateTime: "_tResult.testDateTime)
do fileStream.WriteLine()
do fileStream.WriteLine("results["_tResult.TotalCounts_"]{suiteName,testcaseName,methodName,status,assertAction,assertCounter,assertDescription,assertLocation}:")
}
set data = " "_tResult.suiteName_","_tResult.testcaseName_","_tResult.methodName_","_TestIndex_","_
tResult.assertAction_","_tResult.assertCounter_","""_$translate(tResult.assertDescription,"""")_""","""_tResult.assertLocation_""""
do fileStream.WriteLine(data)
}
$$$ThrowOnError(fileStream.%Save())
} catch ex {
set sc = ex.AsStatus()
}
return sc
}

ClassMethod OutputToDevice(
TestIndex As %Integer = {$order(^UnitTest.Result(""),-1)},
TestStatus As %String = "") As %Status
{
set sc = $$$OK
try {
if TestStatus'="" {
set tResult = ..FilteredTestResultsFunc(TestIndex, TestStatus)
} else {
set tResult = ..GetAllTestResultsFunc(TestIndex)
}
set currentID=""
while tResult.%Next() {
if currentID = "" {
set currentID = TestIndex
write !,"unitTest:"
write !," id: "_TestIndex
write !," namespace: "_tResult.namespace
write !," duration: "_tResult.duration
write !," testDateTime: "_tResult.testDateTime
write !
write !,"results["_tResult.TotalCounts_"]{suiteName,testcaseName,methodName,status,assertAction,assertCounter,assertDescription,assertLocation}:"
}
set data = " "_tResult.suiteName_","_tResult.testcaseName_","_tResult.methodName_","_TestIndex_","_
tResult.assertAction_","_tResult.assertCounter_","""_$translate(tResult.assertDescription,"""")_""","""_tResult.assertLocation_""""
write !,data
}
} catch ex {
set sc = ex.AsStatus()
}
return sc
}

}
Loading