/** * 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 = "" }