You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
542 lines
18 KiB
542 lines
18 KiB
/** |
|
* 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, smallIndent; |
|
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] = |
|
smallIndent $ "{$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 |
|
{ |
|
smallIndent = " " |
|
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 :(} ###########################}" |
|
} |