diff --git a/CHANGELOG.md b/CHANGELOG.md index c0efbb5..7793fc0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.2.0] - Unreleased + +### Added +- #42: Added a listener interface and manager with an associated user parameter, allowing the user to broadcast output on test method/case/suite completion. ## [3.1.1] - 2024-07-31 diff --git a/cls/TestCoverage/Listeners/ListenerInterface.cls b/cls/TestCoverage/Listeners/ListenerInterface.cls new file mode 100644 index 0000000..44479a7 --- /dev/null +++ b/cls/TestCoverage/Listeners/ListenerInterface.cls @@ -0,0 +1,8 @@ +Class TestCoverage.Listeners.ListenerInterface Extends %RegisteredObject +{ + +Method Broadcast(pMessage As %DynamicObject) As %Status [ Abstract ] +{ +} + +} diff --git a/cls/TestCoverage/Listeners/ListenerManager.cls b/cls/TestCoverage/Listeners/ListenerManager.cls new file mode 100644 index 0000000..a76f64b --- /dev/null +++ b/cls/TestCoverage/Listeners/ListenerManager.cls @@ -0,0 +1,48 @@ +Class TestCoverage.Listeners.ListenerManager Extends %RegisteredObject +{ + +Property listeners As list Of TestCoverage.Listeners.ListenerInterface; + +Method BroadCastToAll(pMessage As %DynamicObject) As %Status +{ + set tSC = $$$OK + try { + for i = 1:1:..listeners.Count() { + set tListener = ..listeners.GetAt(i) + $$$ThrowOnError(tListener.Broadcast(pMessage)) + } + } + catch e { + Set tSC = e.AsStatus() + } + quit tSC +} + +Method AddListener(pListener As TestCoverage.Listeners.ListenerInterface) As %Status +{ + set tSC = $$$OK + try { + do ..listeners.Insert(pListener) + } catch e { + set tSC = e.AsStatus() + } + quit tSC +} + +Method RemoveListener(pListener As TestCoverage.Listeners.ListenerInterface) As %Status +{ + set tSC = $$$OK + try { + set tIndex = ..listeners.FindOref(pListener) + if (tIndex = "") { + Set tMsg = "Listener not found" + $$$ThrowStatus($$$ERROR($$$GeneralError,tMsg)) + } + do ..listeners.RemoveAt(tIndex) + } catch e { + set tSC = e.AsStatus() + } + quit tSC +} + +} diff --git a/cls/TestCoverage/Manager.cls b/cls/TestCoverage/Manager.cls index 13aec20..53a7ea4 100644 --- a/cls/TestCoverage/Manager.cls +++ b/cls/TestCoverage/Manager.cls @@ -64,6 +64,9 @@ Property Hashes [ MultiDimensional ]; Property Monitor As TestCoverage.Utils.LineByLineMonitor [ InitialExpression = {##class(TestCoverage.Utils.LineByLineMonitor).%New()}, Private ]; +/// keeps track of all the listeners that may need information broadcasted about the unit test progress +Property ListenerManager As TestCoverage.Listeners.ListenerManager; + /// Runs unit tests that have been loaded, with code coverage enabled.
/// Note that if coverage is to be tracked for lots of code, it may be necessary to increase the "gmheap" setting /// (under Configuration - Additional Settings - Advanced Memory in the Management Portal).
@@ -563,6 +566,7 @@ ClassMethod OnBeforeAllTests(manager As TestCoverage.Manager, dir As %String, By Set tCoverageRoutines = $Get(userparam("CoverageRoutines")) Set tCoverageDetail = $Get(userparam("CoverageDetail")) Set tSourceNamespace = $Get(userparam("SourceNamespace"),$Namespace) + Set tListenerManager = $Get(userparam("ListenerManager")) Set tProcessIDs = $Get(userparam("ProcessIDs"),$ListBuild($Job)) If (tProcessIDs = "*") { Set tProcessIDs = "" @@ -584,6 +588,7 @@ ClassMethod OnBeforeAllTests(manager As TestCoverage.Manager, dir As %String, By Set manager.ProcessIDs = tProcessIDs Set manager.Timing = tTiming Do manager.SetCoverageTargets(tCoverageClasses,tCoverageRoutines,1) + Set manager.ListenerManager = tListenerManager If (tCoverageDetail '= "") { If (tCoverageDetail '= +tCoverageDetail) { // If we were passed a display value... @@ -619,6 +624,10 @@ ClassMethod OnBeforeAllTests(manager As TestCoverage.Manager, dir As %String, By } If (manager.CoverageDetail = 0) { + if (manager.ListenerManager) { + set tObj = {"message": "Starting tests"} + Do manager.ListenerManager.BroadCastToAll(tObj) + } Set tSC = manager.StartCoverageTracking() $$$ThrowOnError(tSC) } @@ -637,6 +646,10 @@ ClassMethod OnAfterAllTests(manager As TestCoverage.Manager, dir As %String, ByR Try { If (manager.CoverageDetail = 0) { Set tSC = manager.EndCoverageTracking() + if (manager.ListenerManager) { + set tObj = {"message": "All tests complete"} + Do manager.ListenerManager.BroadCastToAll(tObj) + } } Do manager.Monitor.Stop() } Catch e { @@ -673,9 +686,15 @@ Method OnBeforeTestSuite(dir As %String, suite As %String, testspec As %String, Set ..CurrentTestSuite = $Case(suite,"":"(root)",:suite) Set ..CurrentTestClass = "" Set ..CurrentTestMethod = "" + if (..ListenerManager) { + set tObj = {"message": "Starting test suite: "} + do tObj.%Set("suite", suite) + Do ..ListenerManager.BroadCastToAll(tObj) + } If (..CoverageDetail = 1) { Set tSC = ..StartCoverageTracking() } + } Catch e { Set tSC = e.AsStatus() } @@ -688,9 +707,15 @@ Method OnAfterTestSuite(dir As %String, suite As %String, testspec As %String, B { Set tSC = $$$OK Try { + If (..CoverageDetail = 1) { Set tSC = ..EndCoverageTracking($Case(suite,"":"(root)",:suite)) } + if (..ListenerManager) { + set tObj = {"message": "Finished test suite: "} + do tObj.%Set("suite", suite) + Do ..ListenerManager.BroadCastToAll(tObj) + } } Catch e { Set tSC = e.AsStatus() } @@ -705,6 +730,12 @@ Method OnBeforeTestCase(suite As %String, class As %String) As %Status Try { Set ..CurrentTestClass = class Set ..CurrentTestMethod = "" + if (..ListenerManager) { + set tObj = {"message": "Starting test case: "} + do tObj.%Set("suite", suite) + do tObj.%Set("class", class) + Do ..ListenerManager.BroadCastToAll(tObj) + } If (..CoverageDetail = 2) { Set tSC = ..StartCoverageTracking() } @@ -723,6 +754,12 @@ Method OnAfterTestCase(suite As %String, class As %String) As %Status If (..CoverageDetail = 2) { Set tSC = ..EndCoverageTracking(suite, class) } + if (..ListenerManager) { + set tObj = {"message": "Finished test case: "} + do tObj.%Set("suite", suite) + do tObj.%Set("class", class) + Do ..ListenerManager.BroadCastToAll(tObj) + } } Catch e { Set tSC = e.AsStatus() } @@ -736,6 +773,13 @@ Method OnBeforeOneTest(suite As %String, class As %String, method As %String) As Set tSC = $$$OK Try { Set ..CurrentTestMethod = method + if (..ListenerManager) { + set tObj = {"message": "Starting test method: "} + do tObj.%Set("suite", suite) + do tObj.%Set("class", class) + do tObj.%Set("method", method) + Do ..ListenerManager.BroadCastToAll(tObj) + } If (..CoverageDetail = 3) { Set tSC = ..StartCoverageTracking() } @@ -754,6 +798,13 @@ Method OnAfterOneTest(suite As %String, class As %String, method As %String) As If (..CoverageDetail = 3) { Set tSC = ..EndCoverageTracking(suite, class, method) } + if (..ListenerManager) { + set tObj = {"message": "Finished test method: "} + do tObj.%Set("suite", suite) + do tObj.%Set("class", class) + do tObj.%Set("method", method) + Do ..ListenerManager.BroadCastToAll(tObj) + } } Catch e { Set tSC = e.AsStatus() }