Adds network UI test framework

This adds a test framework to drive UI tests from the client
instead of injecting events from the websocket.  Tests consist
of a pair of snapshots (before and after the test) and
a list of UI events to process.  Tests are run using a FSM
in the client that controls the resetting of state to the snapshot,
injecting the events into the UI, recording test coverage,
and reporting tests to the server.

* Adds design for event trace table
* Adds design for a coverage tracking table
* Adds models for EventTrace and Coverage
* Adds trace_id to recording messages
* Adds design for TopologySnapshot table
* Adds order to TopologySnapshot table
* Adds TopologySnapshot table
* Adds Snapshot message when recordings are started and stoppped
* Adds models for tracking test cases and test results
* Adds designs for a test runner FSM
* Updates test management commands with new schema
* Adds download recording button
* Adds models to track tests
* Adds ui test runner
* Adds id and client to TestResult design
* Adds id and client to TestResult
* Update message types
* Stores test results and code coverage from the test runner
* Adds tool to generate a test coverage report
* Adds APIs for tests and code coverage
* Adds per-test-case coverage reports
* Breaks out coverage for loading the modules from the tests
* Re-raises server-side errors
* Captures errors during tests
* Adds defaults for host name and host type
* Disables test FSM trace storage
* Adds support for sending server error message to the client
* Resets the UI flags, history, and toolbox contents between tests
* Adds istanbul instrumentation to network-ui
This commit is contained in:
Ben Thomasson
2018-01-08 11:34:23 -05:00
parent eeaf7c257c
commit bf7f4ee1e1
38 changed files with 1534 additions and 97 deletions

View File

@@ -15,6 +15,7 @@ var stream_fsm = require('./stream.fsm.js');
var group = require('./group.js');
var buttons = require('./buttons.js');
var time = require('./time.js');
var test_fsm = require('./test.fsm.js');
var util = require('./util.js');
var models = require('./models.js');
var messages = require('./messages.js');
@@ -22,7 +23,7 @@ var svg_crowbar = require('./svg-crowbar.js');
var ReconnectingWebSocket = require('reconnectingwebsocket');
var NetworkUIController = function($scope, $document, $location, $window, $http,
$q, $state, ProcessErrors) {
$q, $state, ProcessErrors, ConfigService) {
window.scope = $scope;
var i = 0;
@@ -96,6 +97,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$scope.group_id_seq = util.natural_numbers(0);
$scope.message_id_seq = util.natural_numbers(0);
$scope.stream_id_seq = util.natural_numbers(0);
$scope.test_result_id_seq = util.natural_numbers(0);
$scope.overall_toolbox_collapsed = false;
$scope.time_pointer = -1;
$scope.frame = 0;
@@ -108,6 +110,14 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$scope.groups = [];
$scope.processes = [];
$scope.configurations = [];
$scope.tests = [];
$scope.current_tests = [];
$scope.current_test = null;
$scope.testing = false;
$scope.version = null;
$scope.test_events = [];
$scope.test_results = [];
$scope.test_errors = [];
$scope.streams = [];
$scope.view_port = {'x': 0,
'y': 0,
@@ -118,7 +128,9 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$scope.trace_id = $scope.trace_id_seq();
$scope.send_trace_message = function (message) {
console.log(message);
if (!$scope.recording) {
return;
}
message.sender = $scope.client_id;
message.trace_id = $scope.trace_id;
message.message_id = $scope.message_id_seq();
@@ -148,6 +160,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$scope.site_controller = new fsm.FSMController($scope, "site_fsm", site_fsm.Disable, $scope);
$scope.buttons_controller = new fsm.FSMController($scope, "buttons_fsm", buttons.Start, $scope);
$scope.time_controller = new fsm.FSMController($scope, "time_fsm", time.Start, $scope);
$scope.test_controller = new fsm.FSMController($scope, "test_fsm", test_fsm.Start, $scope);
$scope.app_toolbox_controller = new fsm.FSMController($scope, "toolbox_fsm", toolbox_fsm.Start, $scope);
//App Toolbox Setup
@@ -185,6 +198,12 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
let host = hosts[i];
console.log(host);
host.data = jsyaml.safeLoad(host.variables);
if (host.data.type == undefined) {
host.data.type = 'unknown';
}
if (host.data.name == undefined) {
host.data.name = host.name;
}
var device = new models.Device(0, host.data.name, 0, 0, host.data.type, host.id, host.variables);
device.icon = true;
$scope.inventory_toolbox.items.push(device);
@@ -288,9 +307,13 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$scope.mode_controller.delegate_channel = new fsm.Channel($scope.mode_controller,
$scope.time_controller,
$scope);
$scope.test_controller.delegate_channel = new fsm.Channel($scope.test_controller,
$scope.mode_controller,
$scope);
$scope.first_channel = new fsm.Channel(null,
$scope.mode_controller,
$scope.test_controller,
$scope);
var getMouseEventResult = function (mouseEvent) {
@@ -464,7 +487,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$scope.onMouseDown = function ($event) {
$scope.normalize_mouse_event($event);
if ($scope.recording) {
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
$scope.last_event = $event;
$scope.first_channel.send('MouseDown', $event);
@@ -475,7 +498,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$scope.onMouseUp = function ($event) {
$scope.normalize_mouse_event($event);
if ($scope.recording) {
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
$scope.last_event = $event;
$scope.first_channel.send('MouseUp', $event);
@@ -486,7 +509,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$scope.onMouseLeave = function ($event) {
$scope.normalize_mouse_event($event);
if ($scope.recording) {
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
$scope.onMouseLeaveResult = getMouseEventResult($event);
$scope.cursor.hidden = true;
@@ -496,7 +519,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$scope.onMouseMove = function ($event) {
$scope.normalize_mouse_event($event);
if ($scope.recording) {
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
//var coords = getCrossBrowserElementCoords($event);
$scope.cursor.hidden = false;
@@ -513,7 +536,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$scope.onMouseOver = function ($event) {
$scope.normalize_mouse_event($event);
if ($scope.recording) {
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type));
$scope.send_control_message(new messages.MouseEvent($scope.client_id, $event.x, $event.y, $event.type, $scope.trace_id));
}
$scope.onMouseOverResult = getMouseEventResult($event);
$scope.cursor.hidden = false;
@@ -529,11 +552,11 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
var deltaY = $event.deltaY;
// console.log([$event, delta, deltaX, deltaY]);
if ($scope.recording) {
$scope.send_control_message(new messages.MouseWheelEvent($scope.client_id, delta, deltaX, deltaY, $event.type, $event.originalEvent.metaKey));
$scope.send_control_message(new messages.MouseWheelEvent($scope.client_id, delta, deltaX, deltaY, $event.type, $event.originalEvent.metaKey, $scope.trace_id));
}
$scope.last_event = $event;
$scope.first_channel.send('MouseWheel', [$event, delta, deltaX, deltaY]);
event.preventDefault();
$event.preventDefault();
};
$scope.onKeyDown = function ($event) {
@@ -545,7 +568,8 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
$event.altKey,
$event.shiftKey,
$event.ctrlKey,
$event.metaKey));
$event.metaKey,
$scope.trace_id));
}
$scope.last_event = $event;
$scope.last_key = $event.key;
@@ -653,6 +677,7 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
};
$scope.onRenameContextButton = function (button) {
console.log(button.name);
$scope.context_menus[0].enabled = false;
$scope.first_channel.send("LabelEdit", {});
};
@@ -690,14 +715,31 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
console.log(button.name);
$scope.recording = ! $scope.recording;
if ($scope.recording) {
$scope.trace_id = $scope.trace_id_seq();
$scope.send_control_message(new messages.MultipleMessage($scope.client_id,
[new messages.StartRecording($scope.client_id),
[new messages.StartRecording($scope.client_id, $scope.trace_id),
new messages.ViewPort($scope.client_id,
$scope.current_scale,
$scope.panX,
$scope.panY)]));
$scope.panY,
$scope.trace_id),
new messages.Snapshot($scope.client_id,
$scope.devices,
$scope.links,
$scope.groups,
$scope.streams,
0,
$scope.trace_id)]));
} else {
$scope.send_control_message(new messages.StopRecording($scope.client_id));
$scope.send_control_message(new messages.MultipleMessage($scope.client_id,
[new messages.Snapshot($scope.client_id,
$scope.devices,
$scope.links,
$scope.groups,
$scope.streams,
1,
$scope.trace_id),
new messages.StopRecording($scope.client_id, $scope.trace_id)]));
}
};
@@ -789,6 +831,25 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
console.log(button.label);
window.open("/network_ui/download_trace?topology_id=" + $scope.topology_id + "&trace_id=" + $scope.trace_id + "&client_id=" + $scope.client_id);
};
$scope.onDownloadRecordingButton = function (button) {
console.log(button.label);
window.open("/network_ui/download_recording?topology_id=" + $scope.topology_id + "&trace_id=" + $scope.trace_id + "&client_id=" + $scope.client_id);
};
$scope.onUploadTestButton = function (button) {
console.log(button.name);
window.open("/network_ui/upload_test", "_top");
};
$scope.onRunTestsButton = function (button) {
console.log(button.name);
$scope.test_results = [];
$scope.current_tests = $scope.tests.slice();
$scope.first_channel.send("EnableTest", new messages.EnableTest());
};
// Buttons
var button_offset = 200;
@@ -802,6 +863,9 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
new models.Button("CONFIGURE", button_offset + 520, 48, 90, 30, $scope.onConfigureButton, $scope),
new models.Button("EXPORT YAML", button_offset + 620, 48, 120, 30, $scope.onExportYamlButton, $scope),
new models.Button("DOWNLOAD TRACE", button_offset + 750, 48, 150, 30, $scope.onDownloadTraceButton, $scope),
new models.Button("DOWNLOAD RECORDING", button_offset + 910, 48, 170, 30, $scope.onDownloadRecordingButton, $scope),
new models.Button("UPLOAD TEST", button_offset + 10, 88, 100, 30, $scope.onUploadTestButton, $scope),
new models.Button("RUN TESTS", button_offset + 120, 88, 100, 30, $scope.onRunTestsButton, $scope),
];
var LAYERS_X = 160;
@@ -1429,6 +1493,8 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
//Erase the existing state
$scope.devices = [];
$scope.links = [];
$scope.groups = [];
$scope.streams = [];
var device_map = {};
var device_interface_map = {};
@@ -1634,12 +1700,6 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
}
};
$scope.send_coverage = function () {
console.log("Sending coverage");
if (typeof(window.__coverage__) !== "undefined" && window.__coverage__ !== null) {
$scope.send_control_message(new messages.Coverage($scope.client_id, window.__coverage__));
}
};
$scope.control_socket.onmessage = function(message) {
@@ -1743,6 +1803,112 @@ var NetworkUIController = function($scope, $document, $location, $window, $http,
map.set(key, stream.offset + 1);
}
};
setInterval( function () {
var test_event = null;
if ($scope.test_events.length > 0) {
test_event = $scope.test_events.shift();
console.log(test_event);
test_event.sender = 0;
try {
$scope.first_channel.send(test_event.msg_type, test_event);
} catch (err) {
$scope.test_errors.push(err);
}
}
$scope.$apply();
}, 10);
ConfigService
.getConfig()
.then(function(config){
$scope.version = config.version;
});
$scope.reset_coverage = function() {
var i = null;
var coverage = null;
var f = null;
if (typeof(window.__coverage__) !== "undefined" && window.__coverage__ !== null) {
for (f in window.__coverage__) {
coverage = window.__coverage__[f];
for (i in coverage.b) {
coverage.b[i] = [0, 0];
}
for (i in coverage.f) {
coverage.f[i] = 0;
}
for (i in coverage.s) {
coverage.s[i] = 0;
}
}
}
};
$scope.reset_flags = function () {
$scope.debug = {'hidden': true};
$scope.hide_buttons = false;
$scope.hide_links = false;
$scope.hide_interfaces = false;
$scope.hide_groups = false;
};
$scope.reset_fsm_state = function () {
$scope.null_controller.state = null_fsm.Start;
$scope.null_controller.state.start($scope.null_controller);
$scope.hotkeys_controller.state = hotkeys.Start;
$scope.hotkeys_controller.state.start($scope.hotkeys_controller);
$scope.view_controller.state = view.Start;
$scope.view_controller.state.start($scope.view_controller);
$scope.device_detail_controller.state = device_detail_fsm.Start;
$scope.device_detail_controller.state.start($scope.device_detail_controller);
$scope.move_controller.state = move.Start;
$scope.move_controller.state.start($scope.move_controller);
$scope.link_controller.state = link.Start;
$scope.link_controller.state.start($scope.link_controller);
$scope.stream_controller.state = stream_fsm.Start;
$scope.stream_controller.state.start($scope.stream_controller);
$scope.group_controller.state = group.Start;
$scope.group_controller.state.start($scope.group_controller);
$scope.rack_controller.state = rack_fsm.Disable;
$scope.rack_controller.state.start($scope.rack_controller);
$scope.site_controller.state = site_fsm.Disable;
$scope.site_controller.state.start($scope.site_controller);
$scope.buttons_controller.state = buttons.Start;
$scope.buttons_controller.state.start($scope.buttons_controller);
$scope.time_controller.state = time.Start;
$scope.time_controller.state.start($scope.time_controller);
$scope.app_toolbox_controller.state = toolbox_fsm.Start;
$scope.app_toolbox_controller.state.start($scope.app_toolbox_controller);
$scope.inventory_toolbox_controller.state = toolbox_fsm.Start;
$scope.inventory_toolbox_controller.state.start($scope.inventory_toolbox_controller);
$scope.rack_toolbox_controller.state = toolbox_fsm.Start;
$scope.rack_toolbox_controller.state.start($scope.rack_toolbox_controller);
$scope.site_toolbox_controller.state = toolbox_fsm.Start;
$scope.site_toolbox_controller.state.start($scope.site_toolbox_controller);
$scope.mode_controller.state = mode_fsm.Start;
$scope.mode_controller.state.start($scope.mode_controller);
};
$scope.reset_history = function () {
$scope.history = [];
};
$scope.reset_toolboxes = function () {
$scope.app_toolbox.items = [];
$scope.app_toolbox.items.push(new models.Process(0, 'BGP', 'process', 0, 0));
$scope.app_toolbox.items.push(new models.Process(0, 'OSPF', 'process', 0, 0));
$scope.app_toolbox.items.push(new models.Process(0, 'STP', 'process', 0, 0));
$scope.app_toolbox.items.push(new models.Process(0, 'Zero Pipeline', 'process', 0, 0));
for(i = 0; i < $scope.app_toolbox.items.length; i++) {
$scope.app_toolbox.items[i].icon = true;
}
$scope.inventory_toolbox.items = [];
$scope.rack_toolbox.items = [];
$scope.site_toolbox.items = [];
};
};
exports.NetworkUIController = NetworkUIController;