Anton Tarasenko
4 years ago
6 changed files with 1327 additions and 177 deletions
@ -0,0 +1,294 @@ |
|||||||
|
/** |
||||||
|
* Class for storing and processing the information about how well testing |
||||||
|
* against a certain issue went. |
||||||
|
* Copyright 2020 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class IssueSummary extends AcediaObject; |
||||||
|
|
||||||
|
// Each issue is uniquely identified by these values. |
||||||
|
var private class<TestCase> ownerCase; |
||||||
|
var private string context; |
||||||
|
var private string description; |
||||||
|
|
||||||
|
// Records, in chronological order, results of the tests that were |
||||||
|
// run to test this issue. |
||||||
|
var private array<byte> successRecords; |
||||||
|
|
||||||
|
private final function byte BoolToByte(bool boolToConvert) |
||||||
|
{ |
||||||
|
if (boolToConvert) return 1; |
||||||
|
return 0; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Sets `TestCase`, context and description for the issue, |
||||||
|
* tracked in this summary. |
||||||
|
* |
||||||
|
* Can only be successfully called once, but will fail if passed a `none` |
||||||
|
* class reference to `TestCase`. |
||||||
|
* |
||||||
|
* @param targetCase `TestCase`, in which issue, |
||||||
|
* relevant to this summary, is defined. |
||||||
|
* @param targetContext Context, in which this issue, |
||||||
|
* relevant to this summary, is defined. |
||||||
|
* @param targetDescription Description of the issue relevant to |
||||||
|
* this summary. |
||||||
|
* @return `true` if `TestCase`, context and description were successfully set, |
||||||
|
* `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool SetIssue( |
||||||
|
class<TestCase> targetCase, |
||||||
|
string targetContext, |
||||||
|
string targetDescription |
||||||
|
) |
||||||
|
{ |
||||||
|
if (ownerCase != none) return false; |
||||||
|
if (initCase == none) return false; |
||||||
|
ownerCase = targetCase; |
||||||
|
context = targetContext; |
||||||
|
description = targetDescription; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns context for the issue in question. |
||||||
|
* |
||||||
|
* `TestCase` can be important for both displaying information about testing to |
||||||
|
* the user and distinguishing between two different issues with the same |
||||||
|
* description and context. |
||||||
|
* @see `TestCase` for more information. |
||||||
|
* |
||||||
|
* @return Test case that tested for relevant issue. |
||||||
|
*/ |
||||||
|
public final function class<TestCase> GetTestCase() |
||||||
|
{ |
||||||
|
return ownerCase; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns context for the issue in question. |
||||||
|
* |
||||||
|
* Context can be important for both displaying information about testing to |
||||||
|
* the user and distinguishing between two different issues with |
||||||
|
* the same description and in the same `TestCase`. |
||||||
|
* @see `TestCase` for more information. |
||||||
|
* |
||||||
|
* @return Context for relevant issue. |
||||||
|
*/ |
||||||
|
public final function string GetContext() |
||||||
|
{ |
||||||
|
if (ownerCase == none) return ""; |
||||||
|
return context; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns description for the issue in question. |
||||||
|
* |
||||||
|
* Description of an issue is the main way to distinguish between |
||||||
|
* different possibly arising problems. |
||||||
|
* Two different issues can have the same description if they are defined |
||||||
|
* in different `TestCase`s and/or in different context. |
||||||
|
* @see `TestCase` for more information. |
||||||
|
* |
||||||
|
* @return Description for the issue in question. |
||||||
|
*/ |
||||||
|
public final function string GetDescription() |
||||||
|
{ |
||||||
|
if (ownerCase == none) return ""; |
||||||
|
return description; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds result of another test (success or not) to the records of this summary. |
||||||
|
* |
||||||
|
* @param success `true` if test was successful and had passed, |
||||||
|
* `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function AddTestResult(bool success) |
||||||
|
{ |
||||||
|
successRecords[successRecords.length] = BoolToByte(success); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns total amount of test results recorded in caller summary. |
||||||
|
* Never a negative value. |
||||||
|
* |
||||||
|
* @return Amount of tests that were run. |
||||||
|
*/ |
||||||
|
public final function int GetTotalTestsAmount() |
||||||
|
{ |
||||||
|
return successRecords.length; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns total amount of recorded successful test results in caller summary. |
||||||
|
* Never a negative value. |
||||||
|
* |
||||||
|
* @return Amount of recorded successfully performed tests for |
||||||
|
* the relevant issue. |
||||||
|
*/ |
||||||
|
public final function int GetSuccessfulTestsAmount() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int counter; |
||||||
|
counter = 0; |
||||||
|
for (i = 0; i < successRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (successRecords[i] > 0) { |
||||||
|
counter += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
return counter; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns total amount of recorded failed test results in caller summary. |
||||||
|
* Never a negative value. |
||||||
|
* |
||||||
|
* @return Amount of recorded failed tests for the relevant issue. |
||||||
|
*/ |
||||||
|
public final function int GetFailedTestsAmount() |
||||||
|
{ |
||||||
|
return GetTotalTestsAmount() - GetSuccessfulTestsAmount(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns total success rate ("amount of successes" / "total amount of tests") |
||||||
|
* of recorded test results for relevant issue |
||||||
|
* (value between 0 and 1, including boundaries). |
||||||
|
* |
||||||
|
* If there are no test results recorded - returns `-1`. |
||||||
|
* |
||||||
|
* @return Success rate of recorded test results for the relevant issue |
||||||
|
* Returns values outside [0; 1] segment (specifically, negative values) |
||||||
|
* iff no test results at all were recorded. |
||||||
|
*/ |
||||||
|
public final function float GetSuccessRate() |
||||||
|
{ |
||||||
|
local int totalTestsAmount; |
||||||
|
totalTestsAmount = GetTotalTestsAmount(); |
||||||
|
if (totalTestsAmount <= 0) { |
||||||
|
return -1; |
||||||
|
} |
||||||
|
return GetSuccessfulTestsAmount() / totalTestsAmount; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks whether all tests recorded in this summary have passed. |
||||||
|
* |
||||||
|
* @return `true` if all tests for relevant issue have passed, |
||||||
|
* `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool HasPassedAllTests() |
||||||
|
{ |
||||||
|
return (GetFailedTestsAmount() <= 0); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns boolean array of test results: each element recording whether test |
||||||
|
* was a success (`>0`) or a failure (`0`). |
||||||
|
* |
||||||
|
* All results in the array are in a chronological order of arrival. |
||||||
|
* |
||||||
|
* @return Returns copy of boolean array of recorded test results. |
||||||
|
*/ |
||||||
|
public final function array<byte> GetTestRecords() |
||||||
|
{ |
||||||
|
return successRecords; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns index numbers (starting from 1, not 0) of tests that ended in |
||||||
|
* a success, while performed for the same test case, context and issue. |
||||||
|
* So if tests went: [success, success, failure, success, failure], |
||||||
|
* method will return: [1, 2, 4]. |
||||||
|
* |
||||||
|
* All results in the array are in a chronological order of arrival. |
||||||
|
* |
||||||
|
* @return index numbers of successful tests. |
||||||
|
*/ |
||||||
|
public final function array<int> GetSuccessfulTests() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<int> result; |
||||||
|
for (i = 0; i < successRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (successRecords[i] > 0) { |
||||||
|
result[result.length] = i + 1; |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns index numbers (starting from 1, not 0) of tests that ended in |
||||||
|
* a failure, while performed for the same test case, context and issue. |
||||||
|
* So if tests went: [success, success, failure, success, failure], |
||||||
|
* method will return: [3, 5]. |
||||||
|
* |
||||||
|
* All results in the array are in a chronological order of arrival. |
||||||
|
* |
||||||
|
* @return index numbers of successful tests. |
||||||
|
*/ |
||||||
|
public final function array<int> GetFailedTests() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<int> result; |
||||||
|
for (i = 0; i < successRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (successRecords[i] == 0) { |
||||||
|
result[result.length] = i + 1; |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns a formatted text representation of the caller `IssueSummary` |
||||||
|
* in a following format: |
||||||
|
* "{$text_default <issue_description>} {$text_subtle [<failed_test_numbers>]}" |
||||||
|
* |
||||||
|
* @return Formatted string with text representation of the |
||||||
|
* caller `IssueSummary`. |
||||||
|
*/ |
||||||
|
public final function string ToString() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local string result; |
||||||
|
local array<int> failedTests; |
||||||
|
result = "{$text_default" @ GetDescription() $ "}"; |
||||||
|
if (GetFailedTestsAmount() <= 0) { |
||||||
|
return result; |
||||||
|
} |
||||||
|
result @= "{$text_subtle ["; |
||||||
|
failedTests = GetFailedTests(); |
||||||
|
for (i = 0; i < failedTests.length; i += 1) |
||||||
|
{ |
||||||
|
if (i < failedTests.length - 1) { |
||||||
|
result $= string(failedTests[i]) $ ", "; |
||||||
|
} |
||||||
|
else { |
||||||
|
result $= string(failedTests[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
return (result $ "]"); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,66 @@ |
|||||||
|
/** |
||||||
|
* Event generator for events related to testing. |
||||||
|
* Copyright 2020 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class TestingEvents extends Events |
||||||
|
abstract; |
||||||
|
|
||||||
|
static function CallTestingBegan(array< class<TestCase> > testQueue) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0; i < listeners.length; i += 1) |
||||||
|
{ |
||||||
|
class<TestingListenerBase>(listeners[i]) |
||||||
|
.static.TestingBegan(testQueue); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static function CallCaseTested( |
||||||
|
class<TestCase> testedCase, |
||||||
|
TestCaseSummary result) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0; i < listeners.length; i += 1) |
||||||
|
{ |
||||||
|
class<TestingListenerBase>(listeners[i]) |
||||||
|
.static.CaseTested(testedCase, result); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
static function CallTestingEnded( |
||||||
|
array< class<TestCase> > testQueue, |
||||||
|
array<TestCaseSummary> results) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array< class<Listener> > listeners; |
||||||
|
listeners = GetListeners(); |
||||||
|
for (i = 0; i < listeners.length; i += 1) |
||||||
|
{ |
||||||
|
class<TestingListenerBase>(listeners[i]) |
||||||
|
.static.TestingEnded(testQueue, results); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedListener = class'TestingListenerBase' |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
/** |
||||||
|
* Listener for events related to testing. |
||||||
|
* Copyright 2020 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class TestingListenerBase extends Listener |
||||||
|
abstract; |
||||||
|
|
||||||
|
static function TestingBegan(array< class<TestCase> > testQueue) {} |
||||||
|
|
||||||
|
static function CaseTested(class<TestCase> testQueue, TestCaseSummary result) {} |
||||||
|
|
||||||
|
static function TestingEnded( |
||||||
|
array< class<TestCase> > testedCase, |
||||||
|
array<TestCaseSummary> results) {} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
relatedEvents = class'TestingEvents' |
||||||
|
} |
@ -0,0 +1,253 @@ |
|||||||
|
/** |
||||||
|
* This service allows to separate running separate `TestCase`s in separate |
||||||
|
* ticks, which helps to avoid hang ups or false infinite loop detection. |
||||||
|
* Copyright 2020 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class TestingService extends Service |
||||||
|
config(AcediaSystem); |
||||||
|
|
||||||
|
// All test cases, loaded from all available packages. |
||||||
|
// Always use `default` copy of this array. |
||||||
|
var private array< class<TestCase> > registeredTestCases; |
||||||
|
|
||||||
|
// Will be `true` if we have yet more tests to run |
||||||
|
// (either during current or following ticks) |
||||||
|
var private bool runningTests; |
||||||
|
// Queue with all test cases for the current/next testing |
||||||
|
var private array< class<TestCase> > testCasesToRun; |
||||||
|
// Track which test case we need to execute during next tick |
||||||
|
var private int nextTestCase; |
||||||
|
|
||||||
|
// Record test results during the last test run here. |
||||||
|
// After testing has finished - copy them into it's default value |
||||||
|
// `default.summarizedResults` to be available even after `TestingService` |
||||||
|
// shuts down. |
||||||
|
var private array<TestCaseSummary> summarizedResults; |
||||||
|
|
||||||
|
// Configuration variables that tell Acedia what tests to run |
||||||
|
// (and whether to run any at all) on start up. |
||||||
|
var public config const bool runTestsOnStartUp; |
||||||
|
var public config const bool filterTestsByName; |
||||||
|
var public config const bool filterTestsByGroup; |
||||||
|
var public config const string requiredName; |
||||||
|
var public config const string requiredGroup; |
||||||
|
|
||||||
|
// Shortcut to `TestingEvents`, so that we don't have to write |
||||||
|
// class'TestingEvents' every time. |
||||||
|
var const class<TestingEvents> events; |
||||||
|
|
||||||
|
/** |
||||||
|
* Registers another `TestCase` class for later testing. |
||||||
|
* |
||||||
|
* @return `true` if registration was successful. |
||||||
|
*/ |
||||||
|
public final static function bool RegisterTestCase(class<TestCase> newTestCase) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
if (newTestCase == none) return false; |
||||||
|
|
||||||
|
for (i = 0; i < default.registeredTestCases.length; i += 1) |
||||||
|
{ |
||||||
|
if (default.registeredTestCases[i] == newTestCase) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
// Warn if there are test cases with the same name and group |
||||||
|
if ( !(default.registeredTestCases[i].static.GetGroup() |
||||||
|
~= newTestCase.static.GetGroup())) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if ( !(default.registeredTestCases[i].static.GetName() |
||||||
|
~= newTestCase.static.GetName())) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
default._.logger.Warning("Two different test cases with name \"" |
||||||
|
$ newTestCase.static.GetName() $ "\" in the same group \"" |
||||||
|
$ newTestCase.static.GetGroup() $ "\"have been registered:" |
||||||
|
@ "\"" $ string(newTestCase) $ "\" and \"" |
||||||
|
$ string(default.registeredTestCases[i]) |
||||||
|
$ "\". This can lead to issues and it is not something you can fix," |
||||||
|
@ "- contact developers of the relevant packages."); |
||||||
|
} |
||||||
|
default.registeredTestCases[default.registeredTestCases.length] = |
||||||
|
newTestCase; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks whether service is still in the process of running tests. |
||||||
|
* |
||||||
|
* @return `true` if there are still some tests that are scheduled, but |
||||||
|
* were not yet ran and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final static function bool IsRunningTests() |
||||||
|
{ |
||||||
|
local TestingService myInstance; |
||||||
|
myInstance = TestingService(class'TestingService'.static.GetInstance()); |
||||||
|
if (myInstance == none) return false; |
||||||
|
|
||||||
|
return myInstance.runningTests; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the results of the last tests run. |
||||||
|
* |
||||||
|
* If no tests were run - returns an empty array. |
||||||
|
* |
||||||
|
* @return Results of the last tests run. |
||||||
|
*/ |
||||||
|
public final static function array<TestCaseSummary> GetLastResults() |
||||||
|
{ |
||||||
|
return default.summarizedResults; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds all tests to the testing queue. |
||||||
|
* |
||||||
|
* To actually run them use `Run()`. |
||||||
|
* To only run certain tests, - filter them by `FilterByName()` |
||||||
|
* and `FilterByGroup()` |
||||||
|
* |
||||||
|
* Will do nothing if service is already in the process of testing |
||||||
|
* (`IsRunningTests() == true`). |
||||||
|
* |
||||||
|
* @return Caller `TestService` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function TestingService PrepareTests() |
||||||
|
{ |
||||||
|
if (runningTests) { |
||||||
|
return self; |
||||||
|
} |
||||||
|
testCasesToRun = default.registeredTestCases; |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Filters tests in current queue to only those that have a specific name. |
||||||
|
* Should be used after `PrepareTests()` call, but before `Run()`. |
||||||
|
* |
||||||
|
* Will do nothing if service is already in the process of testing |
||||||
|
* (`IsRunningTests() == true`). |
||||||
|
* |
||||||
|
* @return Caller `TestService` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function TestingService FilterByName(string caseName) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array< class<TestCase> > preFiltered; |
||||||
|
if (runningTests) { |
||||||
|
return self; |
||||||
|
} |
||||||
|
preFiltered = testCasesToRun; |
||||||
|
testCasesToRun.length = 0; |
||||||
|
for (i = 0; i < preFiltered.length; i += 1) |
||||||
|
{ |
||||||
|
if (preFiltered[i].static.GetName() ~= caseName) { |
||||||
|
testCasesToRun[testCasesToRun.length] = preFiltered[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Filters tests in current queue to only those that belong to |
||||||
|
* a specific group. Should be used after `PrepareTests()` call, |
||||||
|
* but before `Run()`. |
||||||
|
* |
||||||
|
* Will do nothing if service is already in the process of testing |
||||||
|
* (`IsRunningTests() == true`). |
||||||
|
* |
||||||
|
* @return Caller `TestService` to allow for method chaining. |
||||||
|
*/ |
||||||
|
public final function TestingService FilterByGroup(string caseGroup) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array< class<TestCase> > preFiltered; |
||||||
|
if (runningTests) { |
||||||
|
return self; |
||||||
|
} |
||||||
|
preFiltered = testCasesToRun; |
||||||
|
testCasesToRun.length = 0; |
||||||
|
for (i = 0; i < preFiltered.length; i += 1) |
||||||
|
{ |
||||||
|
if (preFiltered[i].static.GetGroup() ~= caseGroup) { |
||||||
|
testCasesToRun[testCasesToRun.length] = preFiltered[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
return self; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Makes `TestingService` run all tests in a current queue. |
||||||
|
* |
||||||
|
* Queue musty be build before hand: start with `PrepareTests()` call and |
||||||
|
* optionally use `FilterByName()` / `FilterByGroup()` before |
||||||
|
* `Run()` method call. |
||||||
|
* |
||||||
|
* @return `false` if service is already performing the testing |
||||||
|
* and `true` otherwise. Note that `TestingService` might be inactive even |
||||||
|
* after `Run()` call that returns `true`, if the testing queue was empty. |
||||||
|
*/ |
||||||
|
public final function bool Run() |
||||||
|
{ |
||||||
|
if (runningTests) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
nextTestCase = 0; |
||||||
|
runningTests = true; |
||||||
|
summarizedResults.length = 0; |
||||||
|
events.static.CallTestingBegan(testCasesToRun); |
||||||
|
if (testCasesToRun.length <= 0) { |
||||||
|
runningTests = false; |
||||||
|
events.static.CallTestingEnded(testCasesToRun, summarizedResults); |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private final function DoTestingStep() |
||||||
|
{ |
||||||
|
local TestCaseSummary newResult; |
||||||
|
if (nextTestCase >= testCasesToRun.length) |
||||||
|
{ |
||||||
|
runningTests = false; |
||||||
|
default.summarizedResults = summarizedResults; |
||||||
|
events.static.CallTestingEnded(testCasesToRun, summarizedResults); |
||||||
|
return; |
||||||
|
} |
||||||
|
testCasesToRun[nextTestCase].static.PerformTests(); |
||||||
|
newResult = testCasesToRun[nextTestCase].static.GetSummary(); |
||||||
|
events.static.CallCaseTested(testCasesToRun[nextTestCase], newResult); |
||||||
|
summarizedResults[summarizedResults.length] = newResult; |
||||||
|
nextTestCase += 1; |
||||||
|
} |
||||||
|
|
||||||
|
event Tick(float delta) |
||||||
|
{ |
||||||
|
// This will destroy us on the next tick after we were |
||||||
|
// either created or finished performing tests |
||||||
|
if (!runningTests) { |
||||||
|
Destroy(); |
||||||
|
return; |
||||||
|
} |
||||||
|
DoTestingStep(); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
runTestsOnStartUp = false |
||||||
|
events = class'TestingEvents' |
||||||
|
} |
@ -0,0 +1,540 @@ |
|||||||
|
/** |
||||||
|
* Class for storing and processing the information about how well testing |
||||||
|
* for a certain `TestCase` went. That information is stored as |
||||||
|
* a collection of `IssueSummary`s, that can be accessed all at once |
||||||
|
* or by their context. |
||||||
|
* `TestCaseSummary` must be initialized for some `TestCase` before it can |
||||||
|
* be used for anything (unlike `IssueSummary`). |
||||||
|
* Copyright 2020 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class TestCaseSummary extends AcediaObject; |
||||||
|
|
||||||
|
// Case for which this summary was initialized. |
||||||
|
// `none` if it was not. |
||||||
|
var private class<TestCase> ownerCase; |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* We will store issue summaries for different contexts separately. |
||||||
|
* INVARIANT: any function that adds records to `contextRecords` |
||||||
|
* must guarantee that: |
||||||
|
* 1. No two distinct records will have the same `context`; |
||||||
|
* 2. All the `IssueSummary`s in `issueSummaries` array have different |
||||||
|
* issue descriptions. |
||||||
|
* Comparisons of `string`s for two above conditions are case-insensitive. |
||||||
|
*/ |
||||||
|
struct ContextRecord |
||||||
|
{ |
||||||
|
var string context; |
||||||
|
var array<IssueSummary> issueSummaries; |
||||||
|
}; |
||||||
|
var private array<ContextRecord> contextRecords; |
||||||
|
|
||||||
|
// String literals used for displaying array of test case summaries |
||||||
|
var private const string indent; |
||||||
|
var private const string reportHeader; |
||||||
|
var private const string reportSuccessfulEnding; |
||||||
|
var private const string reportUnsuccessfulEnding; |
||||||
|
|
||||||
|
/** |
||||||
|
* Initializes caller summary for given `TestCase` class. |
||||||
|
* Can only be successfully done once, but will fail if |
||||||
|
* passed a `none` reference. |
||||||
|
* |
||||||
|
* @param targetCase `TestCase` class for which this summary will be |
||||||
|
* recording test results. |
||||||
|
* @return `true` if initialization was successful and `false otherwise |
||||||
|
* (either summary already initialized or passed reference is `none`). |
||||||
|
*/ |
||||||
|
public final function bool Initialize(class<TestCase> targetCase) |
||||||
|
{ |
||||||
|
if (ownerCase != none) return false; |
||||||
|
if (targetCase == none) return false; |
||||||
|
ownerCase = targetCase; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns index of a context record with a given description |
||||||
|
* (`context`) in `contextRecords`. |
||||||
|
* Creates one if missing. Never fails. |
||||||
|
* |
||||||
|
* @param context Context that desired record must match. |
||||||
|
* @return Index of the context record that matches `context`. |
||||||
|
* Returned index is always valid. |
||||||
|
*/ |
||||||
|
private final function int TouchContext(string context) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local ContextRecord newRecord; |
||||||
|
// Try to find existing record with given context description |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (context ~= contextRecords[i].context) { |
||||||
|
return i; |
||||||
|
} |
||||||
|
} |
||||||
|
// If there is none - make a new one |
||||||
|
newRecord.context = context; |
||||||
|
contextRecords[contextRecords.length] = newRecord; |
||||||
|
return (contextRecords.length - 1); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Finds indices of a context record and an `IssueSummary` in |
||||||
|
* a nested array that have matching `context` |
||||||
|
* and `issueDescription`. |
||||||
|
* Creates records and/or `IssueSummary` if missing. Never fails. |
||||||
|
* |
||||||
|
* @param context Context description that |
||||||
|
* desired record must match. |
||||||
|
* @param issueDescription Issue description that |
||||||
|
* desired `IssueSummary`must match. |
||||||
|
* @param recordIndex Index of the context record that matches |
||||||
|
* `context` description will be recorded here. |
||||||
|
* Returned value is always valid. Passed value is discarded. |
||||||
|
* @param recordIndex Index of the `IssueSummary` that matches |
||||||
|
* `issueDescription` description will be recorded here. |
||||||
|
* Returned value is always valid. Passed value is discarded. |
||||||
|
*/ |
||||||
|
private final function TouchIssue( |
||||||
|
string context, |
||||||
|
string issueDescription, |
||||||
|
out int recordIndex, |
||||||
|
out int issueIndex |
||||||
|
) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<IssueSummary> issueSummaries; |
||||||
|
recordIndex = TouchContext(context); |
||||||
|
issueSummaries = contextRecords[recordIndex].issueSummaries; |
||||||
|
// Try to find existing issue summary with a given description |
||||||
|
for (i = 0; i < issueSummaries.length; i += 1) |
||||||
|
{ |
||||||
|
if (issueSummaries[i] == none) continue; |
||||||
|
if (issueDescription ~= issueSummaries[i].GetDescription()) |
||||||
|
{ |
||||||
|
issueIndex = i; |
||||||
|
return; |
||||||
|
} |
||||||
|
} |
||||||
|
// If there is none - add a new one |
||||||
|
issueIndex = issueSummaries.length; |
||||||
|
issueSummaries[issueIndex] = new class'IssueSummary'; |
||||||
|
issueSummaries[issueIndex].SetIssue(ownerCase, context, issueDescription); |
||||||
|
contextRecords[recordIndex].issueSummaries = issueSummaries; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if caller summary was correctly initialized. |
||||||
|
* |
||||||
|
* @return `true` if summary was correctly initialized and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool IsInitialized() |
||||||
|
{ |
||||||
|
return (ownerCase != none); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adds result of another test (success or not) to the records of this summary. |
||||||
|
* |
||||||
|
* @param context Context under which test was performed. |
||||||
|
* @param issueDescription Description of issue, |
||||||
|
* for which test was performed. |
||||||
|
* @param success `true` if test was successful and had passed, |
||||||
|
* `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function AddTestResult( |
||||||
|
string context, |
||||||
|
string issueDescription, |
||||||
|
bool success |
||||||
|
) |
||||||
|
{ |
||||||
|
local int recordIndex, issueIndex; |
||||||
|
TouchIssue(context, issueDescription, recordIndex, issueIndex); |
||||||
|
contextRecords[recordIndex] |
||||||
|
.issueSummaries[issueIndex] |
||||||
|
.AddTestResult(success); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns all contexts, for which caller summary has any records of tests |
||||||
|
* being performed. |
||||||
|
* |
||||||
|
* To check if particular context exists you can use `DoesContextExists()`. |
||||||
|
* |
||||||
|
* @return Array of `string`s, each representing one of the contexts, |
||||||
|
* used in tests. |
||||||
|
* Guarantees no duplicates (equality without accounting for case). |
||||||
|
*/ |
||||||
|
public final function array<string> GetContexts() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<string> result; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) { |
||||||
|
result[result.length] = contextRecords[i].context; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks if given context has any records about performing tests |
||||||
|
* (whether they ended in success or a failure) under it. |
||||||
|
* |
||||||
|
* To get an array of all existing contexts use `GetContexts()`. |
||||||
|
* |
||||||
|
* @param context A context to check for existing in records. |
||||||
|
* @return `true` if there was a record about a test being performed under |
||||||
|
* a given context and `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool DoesContextExists(string context) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (contextRecords[i].context ~= context) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* `IssueSummary`s for every issue that was tested and recorded in |
||||||
|
* the caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* @return Array of `IssueSummary`s for every tested and recorded issue. |
||||||
|
*/ |
||||||
|
public final function array<IssueSummary> GetIssueSummaries() |
||||||
|
{ |
||||||
|
local int i, j; |
||||||
|
local array<IssueSummary> recordedSummaries; |
||||||
|
local array<IssueSummary> result; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
recordedSummaries = contextRecords[i].issueSummaries; |
||||||
|
for (j = 0; j < recordedSummaries.length; j += 1) { |
||||||
|
result[result.length] = recordedSummaries[j]; |
||||||
|
} |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns `IssueSummary`s for every issue that was tested under |
||||||
|
* a given context and recorded in caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* @param context Context under which issues of interest were tested. |
||||||
|
* @return Array of `IssueSummary`s for every issue that was tested under |
||||||
|
* given context. |
||||||
|
*/ |
||||||
|
public final function array<IssueSummary> GetIssueSummariesForContext( |
||||||
|
string context |
||||||
|
) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local array<IssueSummary> emptyResult; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (contextRecords[i].context ~= context) { |
||||||
|
return contextRecords[i].issueSummaries; |
||||||
|
} |
||||||
|
} |
||||||
|
return emptyResult; |
||||||
|
} |
||||||
|
|
||||||
|
// Counts total amount of tests performed under the contexts |
||||||
|
// corresponding to `contextRecords[recordIndex]` record. |
||||||
|
private final function int GetTotalTestsAmountForRecord(int recordIndex) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int result; |
||||||
|
local array<IssueSummary> issueSummaries; |
||||||
|
issueSummaries = contextRecords[recordIndex].issueSummaries; |
||||||
|
result = 0; |
||||||
|
for (i = 0; i < issueSummaries.length; i += 1) |
||||||
|
{ |
||||||
|
if (issueSummaries[i] == none) continue; |
||||||
|
result += issueSummaries[i].GetTotalTestsAmount(); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Total amount of performed tests, recorded in caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* If you are interested in amount of test under a specific context, - |
||||||
|
* use `GetTotalTestsAmountForContext()` instead. |
||||||
|
* |
||||||
|
* @return Total amount of performed tests. |
||||||
|
*/ |
||||||
|
public final function int GetTotalTestsAmount() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int result; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
result += GetTotalTestsAmountForRecord(i); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Total amount of tests, performed under a context `context` and |
||||||
|
* recorded in caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* If you are interested in total amount of test under all contexts, - |
||||||
|
* use `GetTotalTestsAmount()` instead. |
||||||
|
* |
||||||
|
* @param context Context for which method must count amount of |
||||||
|
* performed tests. |
||||||
|
* @return Total amount of tests, performed under given context. |
||||||
|
* If given context does not exist in records, - returns `-1`. |
||||||
|
*/ |
||||||
|
public final function int GetTotalTestsAmountForContext(string context) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (context ~= contextRecords[i].context) { |
||||||
|
return GetTotalTestsAmountForRecord(i); |
||||||
|
} |
||||||
|
} |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// Counts total amount of successful tests performed under the contexts |
||||||
|
// corresponding to `contextRecords[recordIndex]` record. |
||||||
|
private final function int GetSuccessfulTestsAmountForRecord(int recordIndex) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int result; |
||||||
|
local array<IssueSummary> issueSummaries; |
||||||
|
issueSummaries = contextRecords[recordIndex].issueSummaries; |
||||||
|
result = 0; |
||||||
|
for (i = 0; i < issueSummaries.length; i += 1) |
||||||
|
{ |
||||||
|
if (issueSummaries[i] == none) continue; |
||||||
|
result += issueSummaries[i].GetSuccessfulTestsAmount(); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Total amount of successfully performed tests, |
||||||
|
* recorded in caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* If you are interested in amount of successful test under a specific context, |
||||||
|
* - use `GetSuccessfulTestsAmountForContext()` instead. |
||||||
|
* |
||||||
|
* @return Total amount of successfully performed tests. |
||||||
|
*/ |
||||||
|
public final function int GetSuccessfulTestsAmount() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int result; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
result += GetSuccessfulTestsAmountForRecord(i); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Total amount of tests, performed under a context `context` and |
||||||
|
* recorded in caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* If you are interested in total amount of successful test under all contexts, |
||||||
|
* - use `GetSuccessfulTestsAmount()` instead. |
||||||
|
* |
||||||
|
* @param context Context for which we method must count amount of |
||||||
|
* successful tests. |
||||||
|
* @return Total amount of successful tests, performed under given context. |
||||||
|
* If given context does not exist in records, - returns `-1`. |
||||||
|
*/ |
||||||
|
public final function int GetSuccessfulTestsAmountForContext(string context) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (context ~= contextRecords[i].context) { |
||||||
|
return GetSuccessfulTestsAmountForRecord(i); |
||||||
|
} |
||||||
|
} |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
// Counts total amount of tests, failed under the contexts |
||||||
|
// corresponding to `contextRecords[recordIndex]` record. |
||||||
|
private final function int GetFailedTestsAmountForRecord(int recordIndex) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int result; |
||||||
|
local array<IssueSummary> issueSummaries; |
||||||
|
issueSummaries = contextRecords[recordIndex].issueSummaries; |
||||||
|
result = 0; |
||||||
|
for (i = 0; i < issueSummaries.length; i += 1) |
||||||
|
{ |
||||||
|
if (issueSummaries[i] == none) continue; |
||||||
|
result += issueSummaries[i].GetFailedTestsAmount(); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Total amount of failed tests, recorded in caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* If you are interested in amount of failed test under a specific context, - |
||||||
|
* use `GetFailedTestsAmountForContext()` instead. |
||||||
|
* |
||||||
|
* @return Total amount of failed tests. |
||||||
|
*/ |
||||||
|
public final function int GetFailedTestsAmount() |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local int result; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
result += GetFailedTestsAmountForRecord(i); |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Total amount of failed tests, performed under a context `context` and |
||||||
|
* recorded in caller `TestCaseSummary`. |
||||||
|
* |
||||||
|
* If you are interested in total amount of failed test under all contexts, - |
||||||
|
* use `GetFailedTestsAmount()` instead. |
||||||
|
* |
||||||
|
* @param context Context for which method must count amount of |
||||||
|
* failed tests. |
||||||
|
* @return Total amount of failed tests, performed under given context. |
||||||
|
* If given context does not exist in records, - returns `-1`. |
||||||
|
*/ |
||||||
|
public final function int GetFailedTestsAmountForContext(string context) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
for (i = 0; i < contextRecords.length; i += 1) |
||||||
|
{ |
||||||
|
if (context ~= contextRecords[i].context) { |
||||||
|
return GetFailedTestsAmountForRecord(i); |
||||||
|
} |
||||||
|
} |
||||||
|
return -1; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks whether all tests recorded in this summary have passed. |
||||||
|
* |
||||||
|
* @return `true` if all tests have passed, `false` otherwise. |
||||||
|
*/ |
||||||
|
public final function bool HasPassedAllTests() |
||||||
|
{ |
||||||
|
return (GetFailedTestsAmount() <= 0); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks whether all tests, performed under given context and |
||||||
|
* recorded in this summary, have passed. |
||||||
|
* |
||||||
|
* @return `true` if all tests under given context have passed, |
||||||
|
* `false` otherwise. |
||||||
|
* If given context does not exists - it did not fail any tests. |
||||||
|
*/ |
||||||
|
public final function bool HasPassedAllTestsForContext(string context) |
||||||
|
{ |
||||||
|
return (GetFailedTestsAmountForContext(context) <= 0); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Generates a text summary for a set of results, given as array of |
||||||
|
* `TestCaseSummary`s (exactly how results are returned by `TestingService`). |
||||||
|
* |
||||||
|
* @param summaries `TestCase` summaries (obtained as a result of testing) |
||||||
|
* that we want to display. |
||||||
|
* @return Test representation of `summaries` as an array of |
||||||
|
* formatted strings, where each string corresponds to it's own line. |
||||||
|
*/ |
||||||
|
public final static function array<string> GenerateStringSummary( |
||||||
|
array<TestCaseSummary> summaries) |
||||||
|
{ |
||||||
|
local int i; |
||||||
|
local bool allTestsPassed; |
||||||
|
local array<string> result; |
||||||
|
allTestsPassed = true; |
||||||
|
result[0] = default.reportHeader; |
||||||
|
for (i = 0; i < summaries.length; i += 1) |
||||||
|
{ |
||||||
|
if (summaries[i] == none) continue; |
||||||
|
summaries[i].AppendCaseSummary(result); |
||||||
|
allTestsPassed = allTestsPassed && summaries[i].HasPassedAllTests(); |
||||||
|
} |
||||||
|
if (allTestsPassed) { |
||||||
|
result[result.length] = default.reportSuccessfulEnding; |
||||||
|
} |
||||||
|
else { |
||||||
|
result[result.length] = default.reportUnsuccessfulEnding; |
||||||
|
} |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
// Add text representation of caller `TestCase` to the existing array `result`. |
||||||
|
private final function AppendCaseSummary(out array<string> result) |
||||||
|
{ |
||||||
|
local int i, j; |
||||||
|
local array<string> contexts; |
||||||
|
local string testCaseAnnouncement; |
||||||
|
local array<IssueSummary> issues; |
||||||
|
if (ownerCase == none) return; |
||||||
|
// Announce case |
||||||
|
testCaseAnnouncement = "{$text_default Test case {$text_emphasis"; |
||||||
|
if (ownerCase.static.GetGroup() != "") { |
||||||
|
testCaseAnnouncement @= "[" $ ownerCase.static.GetGroup() $ "]"; |
||||||
|
} |
||||||
|
testCaseAnnouncement @= ownerCase.static.GetName() $ "}:}"; |
||||||
|
if (GetFailedTestsAmount() > 0) { |
||||||
|
testCaseAnnouncement @= "{$text_failure failed}!"; |
||||||
|
} |
||||||
|
else { |
||||||
|
testCaseAnnouncement @= "{$text_ok passed}!"; |
||||||
|
} |
||||||
|
result[result.length] = testCaseAnnouncement; |
||||||
|
// Report failed tests |
||||||
|
contexts = GetContexts(); |
||||||
|
for (i = 0;i < contexts.length; i += 1) |
||||||
|
{ |
||||||
|
if (GetFailedTestsAmountForContext(contexts[i]) <= 0) continue; |
||||||
|
result[result.length] = "{$text_warning " $ contexts[i] $ "}"; |
||||||
|
issues = GetIssueSummariesForContext(contexts[i]); |
||||||
|
for (j = 0; j < issues.length; j += 1) |
||||||
|
{ |
||||||
|
if (issues[j] == none) continue; |
||||||
|
if (issues[j].GetFailedTestsAmount() <= 0) continue; |
||||||
|
result[result.length] = indent $ issues[j].ToString(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties |
||||||
|
{ |
||||||
|
indent = " " |
||||||
|
reportHeader = "{$text_default ############################## {$text_emphasis Test summary} ###############################}" |
||||||
|
reportSuccessfulEnding = "{$text_default ########################### {$text_ok All tests have passed!} ############################}" |
||||||
|
reportUnsuccessfulEnding = "{$text_default ########################## {$text_failure Some tests have failed :(} ###########################}" |
||||||
|
} |
Reference in new issue