#!/usr/bin/env qore
# -*- mode: qore; indent-tabs-mode: nil -*-

%new-style
%enable-all-warnings
%require-types
%strict-args

%disable-warning module-only

%include ../../../../qlib/QUnit.qm

%exec-class ResultsTest

public class ResultsTest inherits QUnit::Test {
    throwsException(string type, string detail) {
        throw type, detail;
    }

    constructor() : Test("ResultsTest", "1.0") {
        # Tests for TestResult* classes
        addTestCase("Boolean results comparisons", \testBoolResults(), NOTHING);
        addTestCase("Value result type comparisons", \testValueResults(), NOTHING);
        addTestCase("Partial hash match type comparisons", \testPartialHashResult(), NOTHING);
        addTestCase("Exception results comparisons", \testExceptionResults(), NOTHING);
        # Misc functionality
        addTestCase("Verify the testAssertion() method", \testAssertions(), NOTHING);
        addTestCase("Test various printouts", \testPrintouts(), NOTHING);
        # Comparison functions
        addTestCase("Exercise the equals() comparator", \testEquals(), NOTHING);
        addTestCase("Exercise the regexpMatches() comparator", \testRegexp(), NOTHING);
        addTestCase("Exercise the equalsIterated() comparator", \testIteration(), NOTHING);

        set_return_value(main());
    }

    testBoolResults() {
        QUnit::TestResultSuccess s();
        QUnit::TestResultFailure f();

        # Success/Failure
        testAssertion("success equality", \s.equals(), (s,));
        testAssertion("failure equality", \f.equals(), (f,));

        # Compare two different class instances (not using RESULT_(SUCCESS|FAILURE))
        testAssertion("success instance equality", \s.equals(), (new QUnit::TestResultSuccess(),));
        testAssertion("failure instance equality", \f.equals(), (new QUnit::TestResultFailure(),));

        # Compare to predefined constants
        testAssertion("constant success equality", \s.equals(), (RESULT_SUCCESS,));
        testAssertion("constant failure equality", \f.equals(), (RESULT_FAILURE,));

        # Negative comparisons
        testAssertion("success inequality", \s.equals(), (f,), RESULT_FAILURE);
        testAssertion("failure inequality", \f.equals(), (s,), RESULT_FAILURE);
    }

    testValueResults() {
        QUnit::TestResultValue n(NOTHING);
        QUnit::TestResultValue i(30);
        QUnit::TestResultValue a((1, 2, 3, 4));
        QUnit::TestResultValue h((1: 2, 3: 4));

        # NOTHING does not equal anything but nothing
        testAssertion("NOTHING equality", \n.equals(), (new QUnit::TestResultValue(NOTHING)));
        testAssertion("NOTHING inequality", \n.equals(), (i,), RESULT_FAILURE);
        testAssertion("NOTHING inequality", \n.equals(), (a,), RESULT_FAILURE);

        # Integer equality
        testAssertion("integer equality", \i.equals(), (new QUnit::TestResultValue(15+15),));
        testAssertion("integer inequality", \i.equals(), (new QUnit::TestResultValue(15+16),), RESULT_FAILURE);

        # List equality
        testAssertion("list equality", \a.equals(), (new QUnit::TestResultValue((1,2,3,4)),));
        # Lists are fixed order
        testAssertion("list ordering", \a.equals(), (new QUnit::TestResultValue((2,1,3,4)),), RESULT_FAILURE);

        # Hash equality
        testAssertion("hash equality", \h.equals(), (new QUnit::TestResultValue((1:2,3:4)),));
        # Hashes are unordered
        testAssertion("hash ordering", \h.equals(), (new QUnit::TestResultValue((3:4,1:2)),));
        testAssertion("hash ordering failure", \h.equals(), (new QUnit::TestResultValue((1:2)),), RESULT_FAILURE);
    }

    testPartialHashResult() {
        code returnHash = hash sub (hash h) { return h; };

        hash testHash = (
            "a" : "b",
            "c" : "d"
        );

        QUnit::TestResultPartialHashMatch expectedResult;
        QUnit::TestResultValue actualResult(testHash);
        # Match items
        # Match "a"
        testAssertion("partial hash match 1", returnHash, (testHash), new QUnit::TestResultPartialHashMatch(("a" : "b")));
        # Match "c"
        testAssertion("partial hash match 2", returnHash, (testHash), new QUnit::TestResultPartialHashMatch(("c": "d")));
        # Match all
        testAssertion("partial hash match 3", returnHash, (testHash), new QUnit::TestResultPartialHashMatch(testHash));
        # Match in a different way
        expectedResult = new QUnit::TestResultPartialHashMatch(("a": "b"));
        testAssertion("partial hash match 4", \expectedResult.equals(), (actualResult));
        # No match items
        # Match has keys that the input has not
        expectedResult = new QUnit::TestResultPartialHashMatch(("d": "x"));
        testAssertion("partial hash no match 1", \expectedResult.equals(), (actualResult), RESULT_FAILURE);
        # Values of a key don't match
        expectedResult = new QUnit::TestResultPartialHashMatch(("a": "x"));
        testAssertion("partial hash no match 2", \expectedResult.equals(), (actualResult), RESULT_FAILURE);
        # Match against wrong type
        testAssertion("partial hash no match 3", \expectedResult.equals(), (new TestResultValue("1234"),), RESULT_FAILURE);
    }

    testExceptionResults() {
        # Base Exceptions
        testAssertion("exception equality", \throwsException(), ("DUMMY-EXCEPTION", "dummy detail"), new QUnit::TestResultExceptionType("DUMMY-EXCEPTION"));
        testAssertion("exception substr equality", \throwsException(), ("DUMMY-EXCEPTION", "dummy detail"), new QUnit::TestResultExceptionSubstring("DUMMY-EXCEPTION", "umm"));
        testAssertion("exception detail equality", \throwsException(), ("DUMMY-EXCEPTION", "dummy detail"), new QUnit::TestResultExceptionDetail("DUMMY-EXCEPTION", "dummy detail"));
        testAssertion("exception regexp equality", \throwsException(), ("DUMMY-EXCEPTION", "dummy detail"), new QUnit::TestResultExceptionRegexp("DUMMY-EXCEPTION", "[a-z]ummy.*"));

        # Reflectiveness of regexp matching ( a == b && b == a )
        QUnit::TestResultExceptionRegexp r("DUMMY-EXCEPTION", "[a-z]ummy.*");
        QUnit::TestResultExceptionDetail d("DUMMY-EXCEPTION", "dummy detail");
        testAssertion("regexp exception matching", \r.equals(), (d,));
        testAssertion("regexp exception matching reflective", \d.equals(), (r,));
    }

    bool returnsTrue() {
        return True;
    }

    bool returnsFalse() {
        return False;
    }

    returnsNothing() {
        return;
    }

    list returnsList(list l) {
        return l;
    }

    testAssertions() {
        # Test basic properties
        testAssertion("assertion success", \returnsTrue(), list());
        testAssertion("assertion failure", \returnsFalse(), list(), RESULT_FAILURE);
        testAssertion("assertion nothing", \returnsNothing(), list(), new TestResultValue(NOTHING));

        # Check the return value of the assertion
        list lret = testAssertion("assertion list", \returnsList(), (list(1,2,3),), new TestResultValue((1,2,3)));
        bool bret = testAssertion("assertion bool", \equals(), (lret, (1,2,3)));
        assertEq(True, bret);
        testAssertion("assertion return value", \returnsTrue());
    }

    testPrintouts() {
        # TODO: Capturing output would be needed for this?
    }

    testEquals() {
        # True == True, succeeds
        testAssertion("equality", \equals(), (True, True));
        # True == False, fails
        testAssertion("inequality", \equals(), (True, False), RESULT_FAILURE);
        # True != False, throws exception
        testAssertion("wrapped exception", \testAssertion(), ("foo", \equals(), (True, False)), new QUnit::TestResultExceptionRegexp("TEST-EXCEPTION", "Expected.*Success.*\n.*Actual.*Failure"));
        # Comparing incompatible values, throws exception
        testAssertion("wrapped inequality", \testAssertion(), ("bar", \equals(), (True, 20)), new QUnit::TestResultExceptionRegexp("TEST-EXCEPTION", "Expected.*Success.*\n.*Actual.*Failure"));
    }

    testRegexp() {
        # Simple success/failure
        testAssertion("regex match", \regexpMatches(), ("abcd", "[a-d]{4}"));
        testAssertion("regex nomatch", \regexpMatches(), ("abcdefgh", "zz"), RESULT_FAILURE);
        # Multiline regexps
        # newlines cannot be substituted by '.'
        testAssertion("regex newline failure", \regexpMatches(), ("abcd\nefgh", "abcd.efgh"), RESULT_FAILURE);
        testAssertion("regex newline success", \regexpMatches(), ("abcd\nefgh\n", "a.*\ne.*h"));
        # Match many lines
        testAssertion("regex multiline match", \regexpMatches(), ("a\nb\nc\nd\ne", "a(.*\n)*e"));
    }

    testIteration() {
        list left = (0, 1, 2, 3, 4);
        list right = (0, 1, 2, 3, 4);

        testAssertion("iterated equality", \equalsIterated(), (new ListIterator(left), new ListIterator(right)));

        # Left has more
        left += 5;
        testAssertion("iterated too many left", \equalsIterated(), (new ListIterator(left), new ListIterator(right)), RESULT_FAILURE);
        # Right has more
        right += (5, 6);
        testAssertion("iterated too many right", \equalsIterated(), (new ListIterator(left), new ListIterator(right)), RESULT_FAILURE);

        # Add a mismatch
        left += 7;
        testAssertion("iterated mismatch", \equalsIterated(), (new ListIterator(left), new ListIterator(right)), RESULT_FAILURE);
    }
}
