1
0
Fork 0

Basic unit tests for the failure management framework

Includes a simple automated testing framework for Nasal
(Aircraft/Generic/Systems/Tests/test.nas) and a collection of unit tests
for the failure manager, mostly for the different triggers.
This commit is contained in:
Anton Gomez Alvedro 2014-05-13 20:54:08 +02:00 committed by Philosopher
parent 5df7a784e5
commit 081602633a
7 changed files with 739 additions and 0 deletions

View file

@ -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");

View file

@ -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);
}
};

View file

@ -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);
}
};

View file

@ -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);
}
};

View file

@ -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);
}
};

View file

@ -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);
}
};

View file

@ -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));
}