/**
* Base class aimed to contain sets of unit tests for various components of
* Acedia and it's features.
* Currently provides bare-bones testing functions that check boolean
* variables for true/false and objects for whether they're `none` or not.
* Tests:
* ~ can be grouped by their "context",
* describing what they are testing;
* ~ test (or several tests) can be assigned an error message,
* describing what exactly went wrong.
* 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 .
*/
class TestCase extends AcediaObject
abstract;
// Name by which this set of unit tests can be referred to.
var protected const string caseName;
// Information about how well testing went for a particular context,
// i.e. subsets of tests for a particular functionality.
struct ContextSummary
{
// Text, human-readable description of the purpose of
// tests in this context.
var string description;
// `false` if at least one test failed, `true` otherwise.
var bool passed;
// How many test were performed.
var int testsPerformed;
// How many tests failed.
var int testsFailed;
// Error messages generated by failed tests.
var array errors;
};
// Collection of summaries for all contexts defined by the user so far.
struct Summary
{
var bool passed;
var array contextSummaries;
};
// Has function for defining context (`Context()`) been called.
var private bool userDefinedContext;
// Were all tests performed?
var private bool finishedTests;
// Error message that will be generated if some test will fail now.
var private string currentErrorMessage;
// Store complete summary here.
var private Summary currentSummary;
// For quick access store current context's summary here and update it in
// `currentSummary` once done.
var private ContextSummary activeContextSummary;
// Call this function to define a context for subsequent test
// (until another call).
public final static function Context(string description)
{
if ( default.userDefinedContext
|| default.activeContextSummary.testsPerformed > 0)
{
UpdateContextSummary(default.activeContextSummary);
}
default.userDefinedContext = true;
default.activeContextSummary = GetContextSummary(description);
default.currentErrorMessage = "";
}
// Call this function to define an error message for tests that
// would fail after it.
// Message is reset by another call of `Issue()` or
// by changing the context via `Context()`.
public final static function Issue(string errorMessage)
{
default.currentErrorMessage = errorMessage;
}
// All tests to be performed can be placed in this function,
// along with appropriate calls to `Context()` and `Issue()`.
// For an example see class `TEST_JSON`.
protected static function TESTS(){}
// Following functions provide simple test primitives,
public final static function TEST_ExpectTrue(bool result)
{
RecordTestResult(result, default.currentErrorMessage);
}
public final static function TEST_ExpectFalse(bool result)
{
RecordTestResult(!result, default.currentErrorMessage);
}
public final static function TEST_ExpectNone(Object object)
{
RecordTestResult(object == none, default.currentErrorMessage);
}
public final static function TEST_ExpectNotNone(Object object)
{
RecordTestResult(object != none, default.currentErrorMessage);
}
// Returns the summary of how testing went.
public final static function Summary GetSummary()
{
return default.currentSummary;
}
// Name by which this set of unit tests can be referred to.
public final static function string GetName()
{
return default.caseName;
}
// Creates brand new summary for context with a given description,
// marked as "passed" and zero tests done.
private final static function ContextSummary NewContextSummary
(
string description
)
{
local ContextSummary newSummary;
newSummary.passed = true;
newSummary.description = description;
newSummary.testsPerformed = 0;
newSummary.testsFailed = 0;
newSummary.errors.length = 0;
return newSummary;
}
// Returns index of summary with given description
// in our records (`currentSummary`).
// Return `-1` if there is no context with such description.
private final static function int GetContextSummaryIndex(string description)
{
local int i;
for (i = 0; i < default.currentSummary.contextSummaries.length; i += 1)
{
if ( default.currentSummary.contextSummaries[i].description
~= description)
{
return i;
}
}
return -1;
}
// Returns index summary with given description
// in our records.
// Return new context summary if there is no context with such description.
private final static function ContextSummary GetContextSummary
(
string description
)
{
local int index;
if (default.activeContextSummary.description ~= description)
{
return default.activeContextSummary;
}
index = GetContextSummaryIndex(description);
if (index < 0)
{
return NewContextSummary(description);
}
return default.currentSummary.contextSummaries[index];
}
// Rewrites summary (with the same name as a given summary)
// in `currentSummary` records.
// If there's no such record - adds a new one.
private final static function UpdateContextSummary
(
ContextSummary relevantSummary
)
{
local int index;
index = GetContextSummaryIndex(relevantSummary.description);
if (index < 0)
{
index = default.currentSummary.contextSummaries.length;
}
default.currentSummary.contextSummaries[index] = relevantSummary;
}
// Records (in current context summary) that another test was performed and
// succeeded/failed, along with given error message.
private final static function RecordTestResult
(
bool isSuccessful,
string errorMessage
)
{
local int i;
local int errorsAmount;
if (default.finishedTests) return;
default.activeContextSummary.testsPerformed += 1;
if (isSuccessful) return;
default.currentSummary.passed = false;
default.activeContextSummary.passed = false;
default.activeContextSummary.testsFailed += 1;
errorsAmount = default.activeContextSummary.errors.length;
for (i = 0; i < errorsAmount; i += 1)
{
if (default.activeContextSummary.errors[i] ~= errorMessage)
{
return;
}
}
default.activeContextSummary.errors[errorsAmount] = errorMessage;
}
// Calling this function will perform unit tests defined in `TESTS()`
// function of this test case and will prepare the summary,
// obtainable through `GetSummary()` function.
// Returns `true` if all tests have successfully passed
// and `false` otherwise.
public final static function bool PerformTests()
{
default.finishedTests = false;
default.userDefinedContext = false;
default.currentSummary.passed = true;
default.currentSummary.contextSummaries.length = 0;
default.activeContextSummary = NewContextSummary("");
TESTS();
UpdateContextSummary(default.activeContextSummary);
default.finishedTests = true;
return default.currentSummary.passed;
}
// TODO: Support for testing in stages to avoid infinite loop crashes.
// TODO: Add support for test scening: grabbing pawns, placing them, waiting.
// TODO: Expand scening support: triggering functions on client, moving.
// TODO: Expand scening support: zed spawning, aggro setting.
// TODO: Expand scening support: function calls (i.e. for CashToss),
// testing `FixDoshSpam` feature.
// TODO: Expand scening support: lag detection.
// TODO: Expand scening support: test `FixZedTime`.
// TODO: Expand scening support: aiming shooting, detecting damage.
// TODO: Expand scening support: testing `FixFFHack`.
// TODO: Testing infinite nade (partially), ammo selling, dualies cost.
defaultproperties
{
caseName = ""
}