lib_unit
// This source code is subject to the terms of the Mozilla Public License 2.0 at https://mozilla.org/MPL/2.0/
// © robbatt

//@version=5

import robbatt/lib_log/6 as LOG

// @description functions for unit testing
library("lib_unit", overlay = true)

////////////////////////////
// 			MODEL
////////////////////////////

export type Test
	bool strict = true // if true: throw runtime error, else write to log
	bool verbose = true // if true: print Actual value, Expected value AND message, if false: ONLY message
	LOG.Logger logger = na


export method init(Test this) =>
	if na(this.logger)
		this.logger := LOG.Logger.new(name = 'assert', min_level = 3, color_logs = true)
	this.logger


////////////////////////////
//         CORE
////////////////////////////

assert(Test test, expression, expected, string message, bool condition = true) =>
	test.init()
	if condition 
		error_msg = test.verbose ? str.format('Actual: {0} | Expected: {1} | Message: {2}', expression, expected, message) : message
		if test.strict
			runtime.error(error_msg)
		test.logger.error(error_msg)


////////////////////////////
//#region   TRUE / FALSE
////////////////////////////

// @function assert that expression is true, if it's false a runtime error will be thrown
// @param expression The value to be true
// @param message The message to print in the runtime error
export method is_true(Test this, bool expression, string message) =>
	assert(this, expression, true, message, not (expression == true))
// @function assert that expression is false, if it's true a runtime error will be thrown
// @param expression The value to be false
// @param message The message to print in the runtime error
export method is_false(Test this, bool expression, string message) =>
	assert(this, expression, false, message, not (expression == false))

////////////////////////////
//#endregion   TRUE / FALSE
////////////////////////////


////////////////////////////
//#region     EQUALS
////////////////////////////

_equals(Test test, expression, expected, message) =>
    if expression != expected
        assert(test, expression, expected, message)
_equals_array(Test test, expression, expected, message) =>
	if not na(expression) and not na(expected)		
		size1 = array.size(expression)
		size2 = array.size(expected)
		if size1 != size2
			assert(test, expression, expected, message)
		for [i,e1] in expected
			e2 = expression.get(i)
			if e1 != e2
				assert(test, expression, expected, message)
	else if not(na(expression) and na(expected))
		assert(test, expression, expected, message)
	// both na is OK

// @function assert if expression and expected are equal, if they don't match a runtime error will be thrown
// @param expression The value to test
// @param expected The expected value
// @param message The message to print in the runtime error
export method equals(Test this, string expression, string expected, string message) =>
	_equals(this, expression, expected, message)
// @function assert if expression and expected are equal, if they don't match a runtime error will be thrown
// @param expression The value to test
// @param expected The expected value
// @param message The message to print in the runtime error
export method equals(Test this, int expression, int expected, string message) =>
	_equals(this, expression, expected, message)
// @function assert if expression and expected are equal, if they don't match a runtime error will be thrown
// @param expression The value to test
// @param expected The expected value
// @param message The message to print in the runtime error
export method equals(Test this, float expression, float expected, string message) =>
	_equals(this, expression, expected, message)
// @function assert if expression and expected are equal, if they don't match a runtime error will be thrown. This version is testing length, order and values
// @param expression The array to test 
// @param expected The expected array
// @param message The message to print in the runtime error
export method equals(Test this, string[] expression, string[] expected, string message) =>
	_equals_array(this, expression, expected, message)
// @function assert if expression and expected are equal, if they don't match a runtime error will be thrown. This version is testing length, order and values
// @param expression The array to test 
// @param expected The expected array
// @param message The message to print in the runtime error
export method equals(Test this, int[] expression, int[] expected, string message) =>
	_equals_array(this, expression, expected, message)
// @function assert if expression and expected are equal, if they don't match a runtime error will be thrown. This version is testing length, order and values
// @param expression The array to test 
// @param expected The expected array
// @param message The message to print in the runtime error
export method equals(Test this, float[] expression, float[] expected, string message) =>
	_equals_array(this, expression, expected, message)

////////////////////////////
//#endregion    EQUALS
////////////////////////////


////////////////////////////
//#region     NOT NA
////////////////////////////

_not_na(Test test, expression, string message) =>
	if na(expression)
		assert(test, expression, 'not na', message)

// @function assert if expression is not na, if it is a runtime error will be thrown.
// @param expression The value to test 
// @param message The message to print in the runtime error
export method not_na(Test this, string expression, string message) =>
	_not_na(this, expression, message)
// @function assert if expression is not na, if it is a runtime error will be thrown.
// @param expression The value to test 
// @param message The message to print in the runtime error
export method not_na(Test this, int expression, string message) =>
	_not_na(this, expression, message)
// @function assert if expression is not na, if it is a runtime error will be thrown.
// @param expression The value to test 
// @param message The message to print in the runtime error
export method not_na(Test this, float expression, string message) =>
	_not_na(this, expression, message)
// @function assert if expression is not na, if it is a runtime error will be thrown.
// @param expression The value to test 
// @param message The message to print in the runtime error
export method not_na(Test this, string[] expression, string message) =>
	_not_na(this, expression, message)
// @function assert if expression is not na, if it is a runtime error will be thrown.
// @param expression The value to test 
// @param message The message to print in the runtime error
export method not_na(Test this, int[] expression, string message) =>
	_not_na(this, expression, message)
// @function assert if expression is not na, if it is a runtime error will be thrown.
// @param expression The value to test 
// @param message The message to print in the runtime error
export method not_na(Test this, float[] expression, string message) =>
	_not_na(this, expression, message)
// @function assert if expression is not na, if it is a runtime error will be thrown.
// @param expression The value to test 
// @param message The message to print in the runtime error

////////////////////////////
//#endregion    NOT NA
////////////////////////////


////////////////////////////
//#region    COMPARE
////////////////////////////

_gt(Test test, expression1, expression2, message) =>
	assert(test, expression1, expression2, message, not (expression1 > expression2))
_gte(Test test, expression1, expression2, message) =>
	assert(test, expression1, expression2, message, not (expression1 >= expression2))
_lt(Test test, expression1, expression2, message) =>
	assert(test, expression1, expression2, message, not (expression1 < expression2))
_lte(Test test, expression1, expression2, message) =>
	assert(test, expression1, expression2, message, not (expression1 <= expression2))

// @function assert that expression1 > expression2, if it is not, a runtime error will be thrown.
// @param expression1 The value that should be greater
// @param expression2 The value that should be lesser
// @param message The message to print in the runtime error
export method gt(Test this, int expression1, int expression2, string message) =>
	_gt(this, expression1, expression2, message)
// @function assert that expression1 > expression2, if it is not, a runtime error will be thrown.
// @param expression1 The value that should be greater
// @param expression2 The value that should be lesser
// @param message The message to print in the runtime error
export method gt(Test this, float expression1, int expression2, string message) =>
	_gt(this, expression1, expression2, message)
// @function assert that expression1 >= expression2, if it is not, a runtime error will be thrown.
// @param expression1 The value that should be greater or equal
// @param expression2 The value that should be lesser or equal
// @param message The message to print in the runtime error
export method gte(Test this, int expression1, int expression2, string message) =>
	_gte(this, expression1, expression2, message)
// @function assert that expression1 >= expression2, if it is not, a runtime error will be thrown.
// @param expression1 The value that should be greater or equal 
// @param expression2 The value that should be lesser or equal
// @param message The message to print in the runtime error
export method gte(Test this, float expression1, int expression2, string message) =>
	_gte(this, expression1, expression2, message)
// @function assert that expression1 < expression2, if it is not, a runtime error will be thrown.
// @param expression1 The value that should be lesser
// @param expression2 The value that should be greater
// @param message The message to print in the runtime error
export method lt(Test this, int expression1, int expression2, string message) =>
	_lt(this, expression1, expression2, message)
// @function assert that expression1 < expression2, if it is not, a runtime error will be thrown.
// @param expression1 The value that should be lesser
// @param expression2 The value that should be greater
// @param message The message to print in the runtime error
export method lt(Test this, float expression1, int expression2, string message) =>
	_lt(this, expression1, expression2, message)
// @function assert that expression1 <= expression2, if it is not, a runtime error will be thrown.
// @param expression1 The value that should be lesser or equal
// @param expression2 The value that should be greater or equal
// @param message The message to print in the runtime error
export method lte(Test this, int expression1, int expression2, string message) =>
	_lte(this, expression1, expression2, message)
// @function assert that expression1 <= expression2, if it is not, a runtime error will be thrown.
// @param expression1 The value that should be lesser or equal 
// @param expression2 The value that should be greater or equal
// @param message The message to print in the runtime error
export method lte(Test this, float expression1, int expression2, string message) =>
	_lte(this, expression1, expression2, message)

////////////////////////////
//#endregion    COMPARE
////////////////////////////



////////////////////////////
//          TEST
////////////////////////////

Test assert = Test.new(strict = false, verbose = true)

if bar_index < 10000
	value = close
	value1= close[1]
	assert.equals(1+1,	2,	'1+1 should equal 2')
	assert.equals(2,	1,	'1 should not be 2')
	var row1 = array.from(1,2,3,4,5)
	var row2 = array.from(1,2,3,4,5)
	var rowx = array.from(1,4,3,2,5)
	assert.equals(row1, row2, 'two arrays match')
	assert.equals(row1, rowx, 'these arrays dont match')
	assert.not_na(value, 'should not be na')
	assert.not_na(value, 'should not be na')
	assert.not_na(row1, 'should not be na')

	string NA = na
	assert.is_true(na(NA), 'should be true')
	assert.is_false(not na(NA), 'should be false')
	NA := 'ABC'
	assert.is_false(na(NA), 'should be false, na() returns 0/1 it seems')
	// NA := na
	assert.is_false(na(NA), 'should be false, na() returns 0/1 it seems2')

plot(1, '1')