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

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

%requires reflection
%requires ../../../../qlib/Util.qm
%requires ../../../../qlib/FsUtil.qm
%requires ../../../../qlib/QUnit.qm

%exec-class ExceptionTest

class ExceptionTest inherits QUnit::Test {
    private {
        hash<ExceptionInfo> ex;
    }

    constructor() : QUnit::Test("Exception test", "1.0") {
        addTestCase("on_error thread_exit test", \onErrorThreadExitTest());
        addTestCase("rethrow test", \rethrowTest());
        addTestCase("return test", \returnTest());
        addTestCase("Test simple try/catch block", \testSimpleTryCatch());
        addTestCase("Test rethrow", \testRethrow());
        addTestCase("misc tests", \miscTests());
        set_return_value(main());
    }

    onErrorThreadExitTest() {
        TmpFile tmp();
        tmp.file.write("on_error printf(\"OOPS: %y\n\", $1); thread_exit;");
        tmp.file.close();
        int rc = system("qore " + tmp.path);
        assertEq(0, rc);
    }

    rethrowTest() {
        hash<ExceptionInfo> ex0;
        # test catch block enrichment
        try {
            try {
                throw "TEST", "test1", "test2";
            } catch (hash<ExceptionInfo> ex1) {
                rethrow ex1.err + "-NEW", ex1.desc + "-new", ex1.arg + "-new";
            }
        } catch (hash<ExceptionInfo> ex2) {
            ex0 = ex2;
        }
        assertEq("TEST-NEW", ex0.err);
        assertEq("test1-new", ex0.desc);
        assertEq("test2-new", ex0.arg);
        assertNothing(ex0.next);
        remove ex0;
        try {
            try {
                throw "TEST", "test1", "test2";
            } catch (hash<ExceptionInfo> ex1) {
                rethrow ex1.err + "-NEW", ex1.desc + "-new";
            }
        } catch (hash<ExceptionInfo> ex2) {
            ex0 = ex2;
        }
        assertEq("TEST-NEW", ex0.err);
        assertEq("test1-new", ex0.desc);
        assertEq("test2", ex0.arg);
        assertNothing(ex0.next);
        remove ex0;
        try {
            try {
                throw "TEST", "test1", "test2";
            } catch (hash<ExceptionInfo> ex1) {
                rethrow ex1.err + "-NEW";
            }
        } catch (hash<ExceptionInfo> ex2) {
            ex0 = ex2;
        }
        assertEq("TEST-NEW", ex0.err);
        assertEq("test1", ex0.desc);
        assertEq("test2", ex0.arg);
        assertNothing(ex0.next);
        # test on_error enrichment with a block stmt
        remove ex0;
        try {
            on_error {
                rethrow $1.err + "-NEW", $1.desc + "-new", $1.arg + "-new";
            }
            throw "TEST", "test1", "test2";
        } catch (hash<ExceptionInfo> ex1) {
            ex0 = ex1;
        }
        assertEq("TEST-NEW", ex0.err);
        assertEq("test1-new", ex0.desc);
        assertEq("test2-new", ex0.arg);
        assertNothing(ex0.next);
        remove ex0;
        try {
            on_error {
                rethrow $1.err + "-NEW", $1.desc + "-new";
            }
            throw "TEST", "test1", "test2";
        } catch (hash<ExceptionInfo> ex1) {
            ex0 = ex1;
        }
        assertEq("TEST-NEW", ex0.err);
        assertEq("test1-new", ex0.desc);
        assertEq("test2", ex0.arg);
        assertNothing(ex0.next);
        remove ex0;
        try {
            on_error {
                rethrow $1.err + "-NEW";
            }
            throw "TEST", "test1", "test2";
        } catch (hash<ExceptionInfo> ex1) {
            ex0 = ex1;
        }
        assertEq("TEST-NEW", ex0.err);
        assertEq("test1", ex0.desc);
        assertEq("test2", ex0.arg);
        assertNothing(ex0.next);
        # test on_error enrichment with a single stmt
        remove ex0;
        try {
            on_error rethrow $1.err + "-NEW", $1.desc + "-new", $1.arg + "-new";
            throw "TEST", "test1", "test2";
        } catch (hash<ExceptionInfo> ex1) {
            ex0 = ex1;
        }
        assertEq("TEST-NEW", ex0.err);
        assertEq("test1-new", ex0.desc);
        assertEq("test2-new", ex0.arg);
        assertNothing(ex0.next);
        remove ex0;
        try {
            on_error rethrow $1.err + "-NEW", $1.desc + "-new";
            throw "TEST", "test1", "test2";
        } catch (hash<ExceptionInfo> ex1) {
            ex0 = ex1;
        }
        assertEq("TEST-NEW", ex0.err);
        assertEq("test1-new", ex0.desc);
        assertEq("test2", ex0.arg);
        assertNothing(ex0.next);
        remove ex0;
        try {
            on_error rethrow $1.err + "-NEW";
            throw "TEST", "test1", "test2";
        } catch (hash<ExceptionInfo> ex1) {
            ex0 = ex1;
        }
        assertEq("TEST-NEW", ex0.err);
        assertEq("test1", ex0.desc);
        assertEq("test2", ex0.arg);
        assertNothing(ex0.next);
    }

    returnTest() {
        try {
            error();
            assertTrue(False);
        } catch (hash<ExceptionInfo> ex) {
            # make sure that the exception refers to the line of the method missing the return statement
            # by usig reflection, we don't have to hardcode the line
            int line = Class::forName("ExceptionTest").getNormalMethod("error").getVariant().getSourceLocation().line;
            assertEq(line, ex.line);
        }
    }

    bool error() {
        if (False) {
            return True;
        }
    }

    testSimpleTryCatch() {
        try  {
            throw "testing", "123", "test";
        } catch (hash<ExceptionInfo> ex1) {
            ex = ex1;
        }
        assertEq("testing", ex.err);
        assertEq("123", ex.desc);
        assertEq("test", ex.arg);
        assertEq("Qore", ex.lang);
    }

    testRethrow() {
        try {
            try {
                throw "TEST-ERROR", "this is a test";
            } catch () {
                rethrow;
            }
        } catch (hash<ExceptionInfo> ex1) {
            ex = ex1;
        }
        assertEq("TEST-ERROR", ex.err);
        assertEq("this is a test", ex.desc);
        assertEq("User", ex.type);
        assertEq("Qore", ex.lang);
    }

    miscTests() {
        Program p(PO_NEW_STYLE);
        assertThrows("PARSE-EXCEPTION", \p.parse(), ("try {} catch (Exception e) {}", ""));

        int a = 1;
        try {
            throw a;
        } catch (hash<ExceptionInfo> ex) {
            assertEq(1, ex.err);
        }
    }
}
