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

@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0026_auto_20180105_1403'),
]
operations = [
migrations.CreateModel(
name='EventTrace',
fields=[
('event_trace_id', models.AutoField(serialize=False, primary_key=True)),
('trace_session_id', models.IntegerField(default=0)),
('event_data', models.TextField()),
('message_id', models.IntegerField()),
('client', models.ForeignKey(to='network_ui.Client')),
],
),
]

View File

@@ -0,0 +1,23 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0027_eventtrace'),
]
operations = [
migrations.CreateModel(
name='Coverage',
fields=[
('coverage_id', models.AutoField(serialize=False, primary_key=True)),
('trace_session_id', models.IntegerField()),
('coverage_data', models.TextField()),
('client', models.ForeignKey(to='network_ui.Client')),
],
),
]

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2018-01-09 17:12
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0028_coverage'),
]
operations = [
migrations.CreateModel(
name='TopologySnapshot',
fields=[
('topology_snapshot_id', models.AutoField(primary_key=True, serialize=False)),
('topology_id', models.IntegerField()),
('trace_session_id', models.IntegerField()),
('snapshot_data', models.TextField(verbose_name=b'TopologySnapshot')),
('order', models.IntegerField()),
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Client')),
],
),
]

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2018-01-10 17:51
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0029_topologysnapshot'),
]
operations = [
migrations.CreateModel(
name='CodeUnderTest',
fields=[
('code_under_test_id', models.AutoField(primary_key=True, serialize=False, verbose_name=b'CodeUnderTest')),
('version_x', models.IntegerField()),
('version_y', models.IntegerField()),
('version_z', models.IntegerField()),
('commits_since', models.IntegerField()),
('commit_hash', models.CharField(max_length=40)),
],
),
migrations.CreateModel(
name='Result',
fields=[
('result_id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=20)),
],
),
migrations.CreateModel(
name='TestCase',
fields=[
('test_case_id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(max_length=200, verbose_name=b'TestCase')),
('test_case_data', models.TextField()),
],
),
migrations.CreateModel(
name='TestResult',
fields=[
('test_result_id', models.AutoField(primary_key=True, serialize=False)),
('time', models.DateTimeField()),
('code_under_test', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.CodeUnderTest')),
('result', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Result')),
('test_case', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.TestCase')),
],
),
migrations.RemoveField(
model_name='coverage',
name='client',
),
migrations.RemoveField(
model_name='coverage',
name='trace_session_id',
),
migrations.AddField(
model_name='coverage',
name='test_result',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='network_ui.TestResult'),
preserve_default=False,
),
]

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2018-01-10 17:52
from __future__ import unicode_literals
from django.db import migrations
results = ['passed',
'failed',
'errored',
'skipped',
'aborted',
'not run',
'blocked']
def populate_result_types(apps, schema_editor):
Result = apps.get_model('network_ui', 'Result')
for result in results:
Result.objects.get_or_create(name=result)
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0030_auto_20180110_1751'),
]
operations = [
migrations.RunPython(
code=populate_result_types,
),
]

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2018-01-12 21:35
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0031_auto_20180110_1752'),
]
operations = [
migrations.AddField(
model_name='testresult',
name='client',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='network_ui.Client'),
preserve_default=False,
),
migrations.AddField(
model_name='testresult',
name='id',
field=models.IntegerField(default=0),
),
]

View File

@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2018-01-12 22:02
from __future__ import unicode_literals
from django.db import migrations
import yaml
messages = yaml.load('''
messages:
- {msg_type: DeviceMove, fields: [msg_type, sender, id, x, y, previous_x, previous_y]}
- {msg_type: DeviceCreate, fields: [msg_type, sender, id, x, y, name, type, host_id]}
- {msg_type: DeviceDestroy, fields: [msg_type, sender, id, previous_x, previous_y, previous_name, previous_type, previous_host_id]}
- {msg_type: DeviceLabelEdit, fields: [msg_type, sender, id, name, previous_name]}
- {msg_type: DeviceSelected, fields: [msg_type, sender, id]}
- {msg_type: DeviceUnSelected, fields: [msg_type, sender, id]}
- {msg_type: InterfaceCreate, fields: [msg_type, sender, device_id, id, name]}
- {msg_type: InterfaceLabelEdit, fields: [msg_type, sender, id, device_id, name, previous_name]}
- {msg_type: LinkLabelEdit, fields: [msg_type, sender, id, name, previous_name]}
- {msg_type: LinkCreate, fields: [msg_type, id, sender, name, from_device_id, to_device_id, from_interface_id, to_interface_id]}
- {msg_type: LinkDestroy, fields: [msg_type, id, sender, name, from_device_id, to_device_id, from_interface_id, to_interface_id]}
- {msg_type: LinkSelected, fields: [msg_type, sender, id]}
- {msg_type: LinkUnSelected, fields: [msg_type, sender, id]}
- {msg_type: Undo, fields: [msg_type, sender, original_message]}
- {msg_type: Redo, fields: [msg_type, sender, original_message]}
- {msg_type: Deploy, fields: [msg_type, sender]}
- {msg_type: Destroy, fields: [msg_type, sender]}
- {msg_type: Discover, fields: [msg_type, sender]}
- {msg_type: Layout, fields: [msg_type, sender]}
- {msg_type: MultipleMessage, fields: [msg_type, sender, messages]}
- {msg_type: MouseEvent, fields: [msg_type, sender, x, y, type, trace_id]}
- {msg_type: MouseWheelEvent, fields: [msg_type, sender, delta, deltaX, deltaY, type, originalEvent, trace_id]}
- {msg_type: KeyEvent, fields: [msg_type, sender, key, keyCode, type, altKey, shiftKey, ctrlKey, metaKey, trace_id]}
- {msg_type: TouchEvent, fields: [msg_type, sender, type, touches]}
- {msg_type: StartRecording, fields: [msg_type, sender, trace_id]}
- {msg_type: StopRecording, fields: [msg_type, sender, trace_id]}
- {msg_type: ViewPort, fields: [msg_type, sender, scale, panX, panY, trace_id]}
- {msg_type: CopySite, fields: [msg_type, site]}
- {msg_type: GroupMove, fields: [msg_type, sender, id, x1, y1, x2, y2, previous_x1, previous_y1, previous_x2, previous_y2]}
- {msg_type: GroupCreate, fields: [msg_type, sender, id, x1, y1, x2, y2, name, type]}
- {msg_type: GroupDestroy, fields: [msg_type, sender, id, previous_x1, previous_y1, previous_x2, previous_y2, previous_name, previous_type]}
- {msg_type: GroupLabelEdit, fields: [msg_type, sender, id, name, previous_name]}
- {msg_type: GroupSelected, fields: [msg_type, sender, id]}
- {msg_type: GroupUnSelected, fields: [msg_type, sender, id]}
- {msg_type: GroupMembership, fields: [msg_type, sender, id, members]}
- {msg_type: TableCellEdit, fields: [msg_type, sender, sheet, col, row, old_value, new_value]}
- {msg_type: ProcessCreate, fields: [msg_type, id, name, type, device_id, x, y]}
- {msg_type: StreamCreate, fields: [msg_type, sender, id, from_id, to_id, label]}
- {msg_type: StreamDestroy, fields: [msg_type, sender, id, from_id, to_id, label]}
- {msg_type: StreamLabelEdit, fields: [msg_type, sender, id, label, previous_label]}
- {msg_type: StreamSelected, fields: [msg_type, sender, id]}
- {msg_type: StreamUnSelected, fields: [msg_type, sender, id]}
- {msg_type: FSMTrace, fields: [msg_type, order, sender, trace_id, fsm_name, from_state, to_state, recv_message_type]}
- {msg_type: ChannelTrace, fields: [msg_type, sender, trace_id, from_fsm, to_fsm, sent_message_type]}
- {msg_type: Snapshot, fields: [msg_type, sender, devices, links, groups, streams, order, trace_id]}
- {msg_type: EnableTest, fields: [msg_type]}
- {msg_type: DisableTest, fields: [msg_type]}
- {msg_type: StartTest, fields: [msg_type]}
- {msg_type: TestCompleted, fields: [msg_type]}
- {msg_type: TestResult, fields: [msg_type, sender, id, name, result, date, code_under_test]}
- {msg_type: Coverage, fields: [msg_type, sender, coverage, result_id]}
''')
def populate_message_types(apps, schema_editor):
MessageType = apps.get_model('network_ui', 'MessageType')
for message in messages['messages']:
MessageType.objects.get_or_create(name=message['msg_type'])
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0032_auto_20180112_2135'),
]
operations = [
migrations.RunPython(
code=populate_message_types,
),
]

View File

@@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2018-01-13 17:25
from __future__ import unicode_literals
from django.db import migrations
import json
def add_load_test_case(apps, schema_editor):
TestCase = apps.get_model('network_ui', 'TestCase')
TestCase.objects.get_or_create(name="Load", test_case_data=json.dumps(dict(runnable=False)))
class Migration(migrations.Migration):
dependencies = [
('network_ui', '0033_auto_20180112_2202'),
]
operations = [
migrations.RunPython(
code=add_load_test_case,
),
]