diff --git a/Aircraft/Generic/Systems/Tests/FailureMgr/test_all.nas b/Aircraft/Generic/Systems/Tests/FailureMgr/test_all.nas new file mode 100644 index 000000000..bcb22be47 --- /dev/null +++ b/Aircraft/Generic/Systems/Tests/FailureMgr/test_all.nas @@ -0,0 +1,7 @@ +# Aggregation of all tests for the Failure Manager + +io.include("Aircraft/Generic/Systems/Tests/FailureMgr/test_cycle_counter.nas"); +io.include("Aircraft/Generic/Systems/Tests/FailureMgr/test_altitude_trigger.nas"); +io.include("Aircraft/Generic/Systems/Tests/FailureMgr/test_mcbf_trigger.nas"); +io.include("Aircraft/Generic/Systems/Tests/FailureMgr/test_mtbf_trigger.nas"); +io.include("Aircraft/Generic/Systems/Tests/FailureMgr/test_failure_mode.nas"); diff --git a/Aircraft/Generic/Systems/Tests/FailureMgr/test_altitude_trigger.nas b/Aircraft/Generic/Systems/Tests/FailureMgr/test_altitude_trigger.nas new file mode 100644 index 000000000..f7cf9b0f3 --- /dev/null +++ b/Aircraft/Generic/Systems/Tests/FailureMgr/test_altitude_trigger.nas @@ -0,0 +1,155 @@ +# AltitudeTrigger unit tests +# +# 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. + +io.include("Aircraft/Generic/Systems/Tests/test.nas"); +io.include("Aircraft/Generic/Systems/failures.nas"); + +var TestAltitudeTrigger = { + + parents: [TestSuite], + + setup: func { + props.globals.initNode("/test"); + }, + + cleanup: func { + me.trigger = nil; + props.globals.getNode("/test").remove(); + }, + + test_binding: func { + + setprop("/test/foreign-property", 25); + + me.trigger = AltitudeTrigger.new(100, 200); + me.trigger.bind("/test/"); + + assert_prop_exists("/test/reset"); + assert_prop_exists("/test/min-altitude-ft"); + assert_prop_exists("/test/min-altitude-ft"); + + me.trigger.unbind(); + + fail_if_prop_exists("/test/reset"); + fail_if_prop_exists("/test/min-altitude-ft"); + fail_if_prop_exists("/test/min-altitude-ft"); + + assert_prop_exists("/test/foreign-property"); + }, + + test_props_are_read_on_reset: func { + + me.trigger = AltitudeTrigger.new(100, 200); + me.trigger.bind("/test/"); + + assert(me.trigger.params["min-altitude-ft"] == 100); + assert(me.trigger.params["max-altitude-ft"] == 200); + + setprop("/test/min-altitude-ft", 1000); + setprop("/test/max-altitude-ft", 2000); + + assert(me.trigger.params["min-altitude-ft"] == 100); + assert(me.trigger.params["max-altitude-ft"] == 200); + + me.trigger.reset(); + + assert(me.trigger.params["min-altitude-ft"] == 1000); + assert(me.trigger.params["max-altitude-ft"] == 2000); + }, + + test_trigger_fires_within_min_and_max: func { + + me.trigger = AltitudeTrigger.new(100, 200); + + me.trigger._altitude_prop = "/test/fake-altitude-ft"; + assert(!me.trigger.fired); + + setprop("/test/fake-altitude-ft", 0); + assert(me.trigger.update() == 0); + assert(!me.trigger.fired); + + setprop("/test/fake-altitude-ft", 300); + assert(me.trigger.update() == 0); + assert(!me.trigger.fired); + + setprop("/test/fake-altitude-ft", 150); + assert(me.trigger.update() == 1); + assert(me.trigger.fired); + }, + + test_trigger_accepts_nil_max: func { + + me.trigger = AltitudeTrigger.new(500, nil); + me.trigger._altitude_prop = "/test/fake-altitude-ft"; + assert(!me.trigger.fired); + + setprop("/test/fake-altitude-ft", -250); + assert(me.trigger.update() == 0); + assert(!me.trigger.fired); + + setprop("/test/fake-altitude-ft", 0); + assert(me.trigger.update() == 0); + assert(!me.trigger.fired); + + setprop("/test/fake-altitude-ft", 250); + assert(me.trigger.update() == 0); + assert(!me.trigger.fired); + + setprop("/test/fake-altitude-ft", 750); + assert(me.trigger.update() == 1); + assert(me.trigger.fired); + }, + + test_trigger_accepts_nil_min: func { + + me.trigger = AltitudeTrigger.new(nil, 500); + me.trigger._altitude_prop = "/test/fake-altitude-ft"; + me.trigger.bind("/test/trigger/"); + + assert(!me.trigger.fired); + + setprop("/test/fake-altitude-ft", 750); + assert(me.trigger.update() == 0); + assert(!me.trigger.fired); + + setprop("/test/fake-altitude-ft", 500); + assert(me.trigger.update() == 0); + assert(!me.trigger.fired); + + setprop("/test/fake-altitude-ft", 250); + assert(me.trigger.update() == 1); + assert(me.trigger.fired); + + me.trigger.reset(); + + setprop("/test/fake-altitude-ft", -250); + assert(me.trigger.update() == 1); + assert(me.trigger.fired); + }, + + test_trigger_dies_if_both_params_are_nil: func { + call(AltitudeTrigger.new, [nil, nil], AltitudeTrigger, var err = []); + assert(size(err) > 0); + }, + + test_to_str: func { + me.trigger = AltitudeTrigger.new(100, 200); + call(me.trigger.to_str, [], me.trigger, var err = []); + assert(size(err) == 0); + } +}; diff --git a/Aircraft/Generic/Systems/Tests/FailureMgr/test_cycle_counter.nas b/Aircraft/Generic/Systems/Tests/FailureMgr/test_cycle_counter.nas new file mode 100644 index 000000000..30c0e4a8e --- /dev/null +++ b/Aircraft/Generic/Systems/Tests/FailureMgr/test_cycle_counter.nas @@ -0,0 +1,124 @@ +# CycleCounter unit tests +# +# 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. + +io.include("Aircraft/Generic/Systems/Tests/test.nas"); +io.include("Aircraft/Generic/Systems/failures.nas"); + +var TestCycleCounter = { + + parents: [TestSuite], + + setup: func { + props.globals.initNode("/test"); + }, + + cleanup: func { + props.globals.getNode("/test").remove(); + me.counter = nil; + }, + + _shake_that_prop: func (pattern=nil) { + + if (pattern == nil) + pattern = [0, -10, 10, -10, 10, -10, 10, 0]; + + setprop("/test/property", pattern[0]); + me.counter.reset(); + + var i = 0; + var value = pattern[0]; + var target = pattern[1]; + var delta = 0; + + while(i < size(pattern) - 1) { + + target = pattern[i+1]; + delta = pattern[i+1] > pattern[i] ? 1 : -1; + + while(value != target) { + value += delta; + setprop("/test/property", value); + } + + i += 1; + } + }, + + test_cycles_dont_grow_while_disabled: func { + me.counter = CycleCounter.new("/test/property"); + me._shake_that_prop(); + assert(me.counter.cycles == 0); + }, + + test_cycles_grow_while_enabled: func { + me.counter = CycleCounter.new("/test/property"); + + me._shake_that_prop(); + assert(me.counter.cycles == 0); + + me.counter.enable(); + + me._shake_that_prop(); + assert(me.counter.cycles == 3); + }, + + test_reset: func { + me.counter = CycleCounter.new("/test/property"); + me.counter.enable(); + + me._shake_that_prop(); + assert(me.counter.cycles > 0); + + me.counter.reset(); + assert(me.counter.cycles == 0); + }, + + test_callback_every_half_cycle: func { + var count = 0; + + me.counter = CycleCounter.new( + property: "/test/property", + on_update: func (cycles) { count += 1 }); + + me.counter.enable(); + me._shake_that_prop(); + + assert(count == 6); + }, + + test_callback_reports_cycle_count: func { + var count = 0; + var cb = func (cycles) { + count += 1; + assert(cycles == count * 0.5); + }; + + me.counter = CycleCounter.new( + property: "/test/property", on_update: cb); + + me.counter.enable(); + me._shake_that_prop(); + }, + + test_counter_works_for_binary_props: func { + me.counter = CycleCounter.new("/test/property"); + me.counter.enable(); + me._shake_that_prop([0, 1, 0, 1, 0, 1]); + assert(me.counter.cycles == 2); + } +}; diff --git a/Aircraft/Generic/Systems/Tests/FailureMgr/test_failure_mode.nas b/Aircraft/Generic/Systems/Tests/FailureMgr/test_failure_mode.nas new file mode 100644 index 000000000..d6fd9bd71 --- /dev/null +++ b/Aircraft/Generic/Systems/Tests/FailureMgr/test_failure_mode.nas @@ -0,0 +1,113 @@ +# AltitudeTrigger unit tests +# +# 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. + +io.include("Aircraft/Generic/Systems/Tests/test.nas"); +io.include("Aircraft/Generic/Systems/failures.nas"); + +var TestFailureMode = { + + parents: [TestSuite], + + setup: func { + props.globals.initNode("/test"); + }, + + cleanup: func { + me.mode = nil; + props.globals.getNode("/test").remove(); + }, + + test_binding: func { + var actuator = { parents: [FailureMgr.FailureActuator] }; + setprop("/test/foreign-property", 25); + + me.mode = FailureMgr.FailureMode.new( + id: "instruments/compass", + description: "a description", + actuator: actuator); + + me.mode.bind("/test/"); + assert_prop_exists("/test/instruments/compass/failure-level"); + + me.mode.unbind(); + fail_if_prop_exists("/test/instruments/compass/failure-level"); + fail_if_prop_exists("/test/instruments/compass"); + assert_prop_exists("/test/foreign-property"); + }, + + test_set_failure_level_calls_actuator: func { + var level = 0; + var actuator = { + parents: [FailureMgr.FailureActuator], + set_failure_level: func (l) { level = l }, + }; + + me.mode = FailureMgr.FailureMode.new( + id: "instruments/compass", + description: "a description", + actuator: actuator); + me.mode.bind("/test/"); + + me.mode.set_failure_level(1); + assert(level == 1); + }, + + test_actuator_gets_called_from_prop: func { + var level = 0; + var actuator = { + parents: [FailureMgr.FailureActuator], + set_failure_level: func (l) { level = l }, + }; + + me.mode = FailureMgr.FailureMode.new( + id: "instruments/compass", + description: "a description", + actuator: actuator); + + me.mode.bind("/test/"); + setprop("/test/instruments/compass/failure-level", 1); + assert(level == 1); + }, + + test_setting_level_from_nasal_is_shown_in_prop: func { + var level = 0; + var actuator = { + parents: [FailureMgr.FailureActuator], + set_failure_level: func (l) { level = l }, + }; + + me.mode = FailureMgr.FailureMode.new( + id: "instruments/compass", + description: "a description", + actuator: actuator); + + me.mode.bind("/test/"); + + me.mode.set_failure_level(1); + assert(level == 1); + + var prop_value = getprop("/test/instruments/compass/failure-level"); + assert(prop_value == 1); + + me.mode.set_failure_level(0.5); + assert(level == 0.5); + + prop_value = getprop("/test/instruments/compass/failure-level"); + assert(prop_value == 0.5); + } +}; diff --git a/Aircraft/Generic/Systems/Tests/FailureMgr/test_mcbf_trigger.nas b/Aircraft/Generic/Systems/Tests/FailureMgr/test_mcbf_trigger.nas new file mode 100644 index 000000000..14a7d511a --- /dev/null +++ b/Aircraft/Generic/Systems/Tests/FailureMgr/test_mcbf_trigger.nas @@ -0,0 +1,116 @@ +# McbfTrigger unit tests +# +# 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. + +io.include("Aircraft/Generic/Systems/Tests/test.nas"); +io.include("Aircraft/Generic/Systems/failures.nas"); + +var TestMcbfTrigger = { + + parents: [TestSuite], + + setup: func { + props.globals.initNode("/test"); + }, + + cleanup: func { + me.trigger = nil; + props.globals.getNode("/test").remove(); + }, + + _do_one_cycle: func (prop) { + setprop(prop, 10); + setprop(prop, -10); + setprop(prop, 0); + }, + + test_binding: func { + setprop("/test/property", 0); + + me.trigger = McbfTrigger.new("/test/property", 3); + me.trigger.bind("/test/"); + + assert_prop_exists("/test/reset"); + assert_prop_exists("/test/mcbf"); + + me.trigger.unbind(); + fail_if_prop_exists("/test/reset"); + fail_if_prop_exists("/test/mcbf"); + assert_prop_exists("/test/property"); + }, + + test_trigger_fires_after_activation_cycles: func { + setprop("/test/property", 25); + me.trigger = McbfTrigger.new("/test/property", 3); + me.trigger.activation_cycles = 3; + me.trigger.enable(); + + assert(!me.trigger.fired); + + for (var i = 1; i < 5; i += 1) { + me._do_one_cycle("/test/property"); + assert(me.trigger.fired == (i > 3)); + } + }, + + test_trigger_notifies_observer_once: func { + var observer_called = 0; + var on_fire = func observer_called += 1; + + setprop("/test/property", 25); + me.trigger = McbfTrigger.new("/test/property", 3); + me.trigger.activation_cycles = 3; + me.trigger.on_fire = on_fire; + me.trigger.enable(); + + assert(!me.trigger.fired); + + for (var i = 1; i < 5; i += 1) + me._do_one_cycle("/test/property"); + + assert(observer_called == 1); + }, + + test_reset: func { + setprop("/test/property", 25); + me.trigger = McbfTrigger.new("/test/property", 3); + me.trigger.activation_cycles = 3; + me.trigger.bind("/test"); + me.trigger.enable(); + + for (var i = 1; i < 5; i += 1) + me._do_one_cycle("/test/property"); + + assert(me.trigger.fired); + + me.trigger.reset(); + me.trigger.activation_cycles = 3; + + assert(!me.trigger.fired); + + for (var i = 1; i < 5; i += 1) { + me._do_one_cycle("/test/property"); + assert(me.trigger.fired == (i > 3)); + } + }, + + test_to_str: func { + me.trigger = McbfTrigger.new("/test/property", 3); + call(me.trigger.to_str, [], me.trigger, var err = []); + assert(size(err) == 0); + } +}; diff --git a/Aircraft/Generic/Systems/Tests/FailureMgr/test_mtbf_trigger.nas b/Aircraft/Generic/Systems/Tests/FailureMgr/test_mtbf_trigger.nas new file mode 100644 index 000000000..dd54d2b81 --- /dev/null +++ b/Aircraft/Generic/Systems/Tests/FailureMgr/test_mtbf_trigger.nas @@ -0,0 +1,87 @@ +# MtbfTrigger unit tests +# +# 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. + +io.include("Aircraft/Generic/Systems/Tests/test.nas"); +io.include("Aircraft/Generic/Systems/failures.nas"); + +var TestMtbfTrigger = { + + parents: [TestSuite], + + setup: func { + props.globals.initNode("/test"); + }, + + cleanup: func { + me.trigger = nil; + props.globals.getNode("/test").remove(); + }, + + test_binding: func { + + setprop("/test/foreign-property", 25); + + me.trigger = MtbfTrigger.new(60); + me.trigger.bind("/test/"); + + assert_prop_exists("/test/reset"); + assert_prop_exists("/test/mtbf"); + + me.trigger.unbind(); + + fail_if_prop_exists("/test/reset"); + fail_if_prop_exists("/test/mtbf"); + + assert_prop_exists("/test/foreign-property"); + }, + + test_props_are_read_on_reset: func { + + me.trigger = MtbfTrigger.new(60); + me.trigger.bind("/test/"); + assert(me.trigger.params["mtbf"] == 60); + + setprop("/test/mtbf", 120); + assert(me.trigger.params["mtbf"] == 60); + + me.trigger.reset(); + assert(me.trigger.params["mtbf"] == 120); + }, + + test_trigger_fires_after_fire_time: func { + + me.trigger = MtbfTrigger.new(60); + me.trigger._time_prop = "/test/fake-time-sec"; + me.trigger.fire_time = 60; + assert(!me.trigger.fired); + + setprop("/test/fake-time-sec", 50); + assert(me.trigger.update() == 0); + assert(!me.trigger.fired); + + setprop("/test/fake-time-sec", 70); + assert(me.trigger.update() == 1); + assert(me.trigger.fired); + }, + + test_to_str: func { + me.trigger = MtbfTrigger.new(60); + call(me.trigger.to_str, [], me.trigger, var err = []); + assert(size(err) == 0); + } +}; diff --git a/Aircraft/Generic/Systems/Tests/test.nas b/Aircraft/Generic/Systems/Tests/test.nas new file mode 100644 index 000000000..1d46d6766 --- /dev/null +++ b/Aircraft/Generic/Systems/Tests/test.nas @@ -0,0 +1,137 @@ +# 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() +# +# Executes all test suites found in the namespace where run_tests is defined. +# 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 () { + + var ns = 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)); +}