# Minimalistic framework for automated testing in Nasal
#
# Copyright (C) 2014 Anton Gomez Alvedro
#
# 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 2 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.


# TestSuite
#
# Tests are organized in test suites. Each test suite contains an arbitrary
# number of tests, and two special methods "setup" and "cleanup". Setup is
# called before every test, and cleanup is called after every test.
#
# In order to define a test suite, you have to create an object parented to
# TestSuite. The testing framework will identify any method in your object whose
# name starts with "test_" as a test case to be run.
#
# Important: The order in which test cases and test suites are executed
# is undefined!
#
# Example:
#
# var MyTestSuite = {
#
#	parents: [TestSuite],
#
#	setup: func {
#		Stuff to do before every test...
#		This is optional. You don't need to provide it if you don't use it.
#	},
#
#	cleanup: func {
#		Stuff to do after every test...
#		Also optional.
#	},
#
#	my_auxiliary_function: func {
#		Methods that do not start with "test_" will not be executed by the
#		test runner. You can define as many auxiliary functions in the test
#		suite as you wish.
#	},
#
#	test_trivial_test: func {
#		This is a real test (starts with "test_"), and it will be run by the
#	    framework when test_run() is called.#
#	}
# };

var TestSuite = {
	setup: func 0,
	cleanup: func 0
};

# run_tests([namespace])
#
# Executes all test suites found in the given namespace. If no namespace is
# specified, then the namespace where run_tests is defined is used by default.
#
# An effective way to work with the framework is to just include the framework
# from your test files:
#
#	io.include(".../test.nas");
#
# and then execute a script like this in the Nasal Console:
#
#	delete(globals, "test");
#	io.load_nasal(".../my_test_suite.nas", "test");
#	test.run_tests();
#
# What this script does is: it empties the "test" namespace and then loads your
# script into that namespace. The test framework will be loaded in there as
# well if it was io.include'd in my_test_suite.nas. Finally, all test suites
# in the "test" namespace are executed.

var run_tests = func(namespace=nil) {

	var ns = namespace != nil ? namespace : closure(run_tests, 1);

	var passed = 0;
	var failed = 0;
	var err = [];

	foreach(var suite_name; keys(ns)) {
		var suite = ns[suite_name];

		if (!isa(suite, TestSuite))
			continue;

		print("Running test suite ", suite_name);

		foreach (var test_name; keys(suite)) {

			if (find("test_", test_name) != 0)
				continue;

			# Run the test case
			setsize(err, 0);
			contains(suite, "setup") and call(suite.setup, [], suite, err);
			size(err) == 0 and call(suite[test_name], [], suite, err);
			size(err) == 0 and contains(suite, "cleanup") and call(suite.cleanup, [], suite, err);

			if (size(err) == 0) {
				passed += 1;
				continue;
			}

			failed += 1;
			print("Test ", test_name, " FAILED\n");
			debug.printerror(err);
		}
	}

	print(sprintf("\n%d tests run. %d passed, %d failed",
	              passed + failed, passed, failed));
}


var assert_prop_exists = func (prop) {
	assert(props.globals.getNode(prop) != nil,
	       sprintf("Property %s does not exist", prop));
}


var fail_if_prop_exists = func (prop) {
	assert(props.globals.getNode(prop) == nil,
	       sprintf("Property %s exists", prop));
}