// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*-
/*
   This program 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, either version 3 of the License, or
   (at your option) any later version.

   This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

//
/// @file   AP_Test.h
/// @brief  A simple unit test framework.
///
/// AP_Test provides the usual test start, condition validation and reporting
/// functions in a compact form.
///
/// Each test must be contained within a block; either a standalone function or
/// a block within a function.  The TEST macro is used to start a test; it creates
/// the local object which will track the results of the test and saves the name
/// for later reporting.  Only one test may be performed within each block.
///
/// Within the test, use the REQUIRE macro to describe a condition that must be
/// met for the test to pass.  If the condition within the macro is not met,
/// the condition will be output as a diagnostic and the test will be considered
/// to have failed.
///
/// The test ends at the end of the block, and the result of the test will be
/// output as a diagnostic.
///
/// Optionally at the end of the test suite, the Test::report method may be used
/// to summarize the results of all of the tests that were performed.
///

/// Unit test state and methods.
///
class Test
{
public:
    /// Constructor - creates a new test.
    ///
    /// Normally called by the TEST macro.
    ///
    /// @param  name    The name of the test being started.
    ///
    Test(const char *name);

    /// Destructor - ends the test.
    ///
    ~Test();

    /// Perform a success check.
    ///
    /// @param  expr    If false, the test has failed.
    /// @param  source  The expression source; emitted in the diagnostic
    ///                 indicating test failure.
    ///
    void        require(bool expr, const char *source);

    /// Report the overall number of tests/pass/fails.
    ///
    static void report();

private:
    const char  *_name;         ///< name of the current test
    bool _fail;                 ///< set if any ::require calls indicate the test failed
    static int16_t _passed;             ///< global pass count
    static int16_t _failed;             ///< global fail count
};

/// Constructor
///
Test::Test(const char *name) :
    _name(name),
    _fail(false)
{
}

/// Destructor
///
Test::~Test()
{
    Serial.printf("%s: %s\n", _fail ? "FAILED" : "passed", _name);
    if (_fail) {
        _failed++;
    } else {
        _passed++;
    }
}

/// Success check
///
void
Test::require(bool expr, const char *source)
{
    if (!expr) {
        _fail = true;
        Serial.printf("%s: fail: %s\n", _name, source);
    }
}

/// Summary report
///
void
Test::report()
{
    Serial.printf("\n%d passed  %d failed\n", _passed, _failed);
}

int16_t Test::_passed = 0;
int16_t Test::_failed = 0;

/// Start a new test.
///
/// This should be invoked at the beginning of a block, before any REQUIRE
/// statements.  A new test called name is started, and subsequent REQUIRE
/// statements will be applied to the test.  The test will continue until
/// the end of the block (or until the _test object that is created otherwise
/// goes out of scope).
///
#define TEST(name)      Test _test(# name)

/// Attach an expression to the test's success criteria.
///
/// The expression expr must evaluate true for the test to pass.  If
/// it does not, the text of the expression is output as a diagnostic
/// and the test is marked as a failure.
///
#define REQUIRE(expr)   _test.require(expr, # expr)

