1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-01 08:21:15 +03:00

Moves network_ui_test to an external repo

This commit is contained in:
Ben Thomasson 2018-03-19 15:21:49 -04:00
parent abb95fdad6
commit 96b3ebd31e
No known key found for this signature in database
GPG Key ID: 5818EF4CC895D5F5
22 changed files with 13 additions and 1039 deletions

View File

@ -1,6 +1,5 @@
from channels.routing import route from channels.routing import route
from awx.network_ui.routing import channel_routing as network_ui_routing from awx.network_ui.routing import channel_routing as network_ui_routing
from awx.network_ui_test.routing import channel_routing as network_ui_test_routing
channel_routing = [ channel_routing = [
@ -11,4 +10,3 @@ channel_routing = [
channel_routing += network_ui_routing channel_routing += network_ui_routing
channel_routing += network_ui_test_routing

View File

@ -1,221 +0,0 @@
Network UI Test
===============
Network UI Test is an event driven test framework for testing the Network UI.
This tool works by setting up the UI with a pre-test run snapshot, replaying a
set of events to the Network UI, catching exceptions and then comparing the
state of the system after the events to a post test run snapshot. Test results
and test code coverage are stored for each test run through the system. This
allows us to determine which lines of test are run during each test and which
lines are run under any test. This can be very helpful during development to
determine that the code is executed as expected given a certain input, to find
code that needs additional tests, and to find code that is not be run under any
of the given inputs (a.k.a. dead code).
Using this test framework it is fairly easy to achieve 90%+ code coverage under
test with a few days of work recording and crafting tests.
Test Steps
----------
The tests suppported by this test framework perform the following steps:
* Reset code coverage records
* Recreate a pre-test snapshot on the UI
* Replay a set of events to the UI
* Check for exceptions thrown by the UI
* Check the state of the system based on the post-test snapshot
* Report the pass/fail/error status of the test by test name, code version, and client id
* Report the code coverage per test result
* Repeat for next test
Test Case Data Schema
---------------------
The tests are completely data driven and the data for the test snapshots and events are stored in a JSON
structure in the `TestCase` model under the `test_case_data` field that has the following structure:
{
"event_trace": [],
"fsm_trace": [],
"snapshots": [
{
"devices": [],
"inventory_toolbox": [],
"links": [],
"message_id": 4,
"msg_type": "Snapshot",
"order": 0,
"sender": 0,
"trace_id": 2
},
{
"devices": [],
"inventory_toolbox": [],
"links": [],
"message_id": 31,
"msg_type": "Snapshot",
"order": 1,
"sender": 0,
"trace_id": 2
}
]
}
The pre-test snapshot has order 0 and the post-test snapshot has order 1.
Network UI Test Models
----------------------
![Models](designs/models.png)
* FSMTrace - The record of an event that happend during an FSM trace. This is used in the recording phase of test case data.
* EventTrace - The record of an event that happened during an event trace. This is used in the recording phase of test case data.
* Coverage - Per-line test case coverage as returned by istanbul JS code coverage tool.
* TopologySnapshot - A snapshot of the state of the network UI before or after the test was run.
* TestCase - The definition of a test case with a given name using the test case data schema defined above.
* Result - One of passed, failed, errored, skipped, aborted, not run, or blocked.
* TestResult - The record of one test case being run at a certain code location by a client at a certain time.
* CodeUnderTest - The record of what exactly was the code being tested at the time the test was run.
Messages
--------
JSON messages are passed over the `/network_ui/test` websocket between the test client and the test server.
The protocol that is used for all messages is in ABNF (RFC5234):
message_type = 'MultipleMessage' / 'MouseEvent' / 'MouseWheelEvent' / 'KeyEvent' / 'StartRecording' / 'StopRecording' / 'ViewPort' / 'FSMTrace' / 'ChannelTrace' / 'Snapshot' / 'EnableTest' / 'DisableTest' / 'StartTest' / 'TestCompleted' / 'TestResult' / 'Coverage'
message_data = '{' 'msg_type' ': ' message_type ', ' key-value *( ', ' key-value ) '}'
message = '[ id , ' posint ']' / '[ topology_id , ' posint ']' / '[' message_type ', ' message_data ']'
See https://github.com/AndyA/abnfgen/blob/master/andy/json.abnf for the rest of the JSON ABNF.
See [designs/messages.yml](designs/messages.yml) for the allowable keys and values for each message type.
Loading Tests
-------------
Tests can be imported from exported data dumps from the `awx-manage datadump` command. From the Tower shell run
the following command:
awx-manage loaddata /awx_devel/network_ui_test_cases_2018_03_12.json
This will load the tests from the `network_ui_test_cases_2018_03_12.json` file.
Exporting Tests
---------------
Use the standard Django dumpdata command to dump the test case data to a file:
awx-manage dumpdata network_ui_test.TestCase > /awx_devel/network_ui_test_cases_YYYY_MM_DD.json
Writing Tests Manually or Generating Tests
------------------------------------------
Use the empty test case schema above and add messages to the event_trace list. Then upload the
JSON test data to the upload test URL below.
Recording Tests
---------------
Tests can be reruns of previously recorded manual interactions with the network UI. To start a recording
open the JavaScript console and run this command:
scope.onRecordButton()
To stop the recording run this command:
scope.onRecordButton()
To download the recording use this command:
scope.onDownloadRecordingButton()
After downloading the recording upload the data using the following URL to make a test of it.
Uploading Tests
---------------
Go to the URL: https://SERVER:PORT/network_ui_test/upload_test and use the form to upload test data
and choose a test case name.
Instrumenting a UI Build
------------------------
Code coverage is collected automatically if the UI is built with instrumented code. Add this section to the
rules in awx/ui/build/webpack.base.js to build instrumented code.
{
test: /\.js$/,
use: {
loader: 'istanbul-instrumenter-loader',
options: { esModules: true }
},
enforce: 'pre',
include: [
/src\/network-ui\//
]
},
Then run:
make ui-devel
or:
make ui-docker
To rebuild the code with istanbul instrumentation.
Running Tests
-------------
To kick off tests in the web browser navigate to the Network UI page under an inventory and open the JavaScript console.
Run this command in the JavaScript console:
scope.onRunTestsButton();
Building a Coverage Report
--------------------------
To build a coverage report for the last set of tests that were run as in above. Edit the `tools/Makefile` and change
`SERVER` to match your Tower server. Then run the following command from the `tools` directory:
make coverage
This will download all the coverage data from all the test results that were previously recorded and build a coverage
report with istantbul. You can then view the report at the address http://localhost:9000 or http://server:PORT
where you executed the command.
http://localhost:9000/coverage will contain the coverage for all test cases merged together.
http://localhost:9000/Load/coverage will contain the coverage for just loading the page.
http://localhost:9000/TestX/coverage will contain the coverage for just the TestX test where
TestX is the name of one of the tests run previously.
3.3 Hardening Road Map
----------------------
* Add post-test snapshot comparison
* Add FSM trace comparison
* Add an ability to run a single test
* Add an ability to run a subset of the tests
* Add a big red light when event recording is on.

View File

@ -1 +0,0 @@
# Copyright (c) 2017 Red Hat, Inc

View File

@ -1,184 +0,0 @@
# Copyright (c) 2018 Red Hat, Inc
# In consumers.py
from channels import Group, Channel
from channels.sessions import channel_session
from awx.network_ui.models import Topology, Client
from awx.network_ui.models import TopologyInventory
from awx.network_ui_test.models import FSMTrace, EventTrace, Coverage, TopologySnapshot
from awx.network_ui_test.models import TestCase, TestResult, CodeUnderTest, Result
import urlparse
import logging
from django.utils.dateparse import parse_datetime
import json
# Connected to websocket.connect
logger = logging.getLogger("awx.network_ui_test.consumers")
def parse_inventory_id(data):
inventory_id = data.get('inventory_id', ['null'])
try:
inventory_id = int(inventory_id[0])
except ValueError:
inventory_id = None
if not inventory_id:
inventory_id = None
return inventory_id
class TestPersistence(object):
def parse_message_text(self, message_text, client_id):
data = json.loads(message_text)
if len(data) == 2:
message_type = data.pop(0)
message_value = data.pop(0)
if isinstance(message_value, list):
logger.error("Message has no sender")
return None, None
if isinstance(message_value, dict) and client_id != message_value.get('sender'):
logger.error("client_id mismatch expected: %s actual %s", client_id, message_value.get('sender'))
return None, None
return message_type, message_value
else:
logger.error("Invalid message text")
return None, None
def handle(self, message):
topology_id = message.get('topology')
assert topology_id is not None, "No topology_id"
client_id = message.get('client')
assert client_id is not None, "No client_id"
message_type, message_value = self.parse_message_text(message['text'], client_id)
if message_type is None:
return
handler = self.get_handler(message_type)
if handler is not None:
try:
handler(message_value, topology_id, client_id)
except Exception:
Group("client-%s" % client_id).send({"text": json.dumps(["Error", "Server Error"])})
raise
else:
logger.warning("Unsupported message %s: no handler", message_type)
def get_handler(self, message_type):
return getattr(self, "on{0}".format(message_type), None)
def onMultipleMessage(self, message_value, topology_id, client_id):
for message in message_value['messages']:
handler = self.get_handler(message['msg_type'])
if handler is not None:
handler(message, topology_id, client_id)
else:
logger.warning("Unsupported message %s", message['msg_type'])
def onCoverageRequest(self, coverage, topology_id, client_id):
pass
def onTestResult(self, test_result, topology_id, client_id):
xyz, _, rest = test_result['code_under_test'].partition('-')
commits_since, _, commit_hash = rest.partition('-')
commit_hash = commit_hash.strip('g')
x, y, z = [int(i) for i in xyz.split('.')]
code_under_test, _ = CodeUnderTest.objects.get_or_create(version_x=x,
version_y=y,
version_z=z,
commits_since=int(commits_since),
commit_hash=commit_hash)
tr = TestResult(id=test_result['id'],
result_id=Result.objects.get(name=test_result['result']).pk,
test_case_id=TestCase.objects.get(name=test_result['name']).pk,
code_under_test_id=code_under_test.pk,
client_id=client_id,
time=parse_datetime(test_result['date']))
tr.save()
def onCoverage(self, coverage, topology_id, client_id):
Coverage(test_result_id=TestResult.objects.get(id=coverage['result_id'], client_id=client_id).pk,
coverage_data=json.dumps(coverage['coverage'])).save()
def onStartRecording(self, recording, topology_id, client_id):
pass
def onStopRecording(self, recording, topology_id, client_id):
pass
def write_event(self, event, topology_id, client_id):
if event.get('save', True):
EventTrace(trace_session_id=event['trace_id'],
event_data=json.dumps(event),
message_id=event['message_id'],
client_id=client_id).save()
onViewPort = write_event
onMouseEvent = write_event
onTouchEvent = write_event
onMouseWheelEvent = write_event
onKeyEvent = write_event
def onFSMTrace(self, message_value, diagram_id, client_id):
FSMTrace(trace_session_id=message_value['trace_id'],
fsm_name=message_value['fsm_name'],
from_state=message_value['from_state'],
to_state=message_value['to_state'],
order=message_value['order'],
client_id=client_id,
message_type=message_value['recv_message_type'] or "none").save()
def onSnapshot(self, snapshot, topology_id, client_id):
TopologySnapshot(trace_session_id=snapshot['trace_id'],
snapshot_data=json.dumps(snapshot),
order=snapshot['order'],
client_id=client_id,
topology_id=topology_id).save()
@channel_session
def ws_connect(message):
# Accept connection
data = urlparse.parse_qs(message.content['query_string'])
inventory_id = parse_inventory_id(data)
topology_ids = list(TopologyInventory.objects.filter(inventory_id=inventory_id).values_list('topology_id', flat=True))
topology_id = None
if len(topology_ids) > 0:
topology_id = topology_ids[0]
if topology_id is not None:
topology = Topology.objects.get(topology_id=topology_id)
else:
topology = Topology(name="topology", scale=1.0, panX=0, panY=0)
topology.save()
TopologyInventory(inventory_id=inventory_id, topology_id=topology.topology_id).save()
topology_id = topology.topology_id
message.channel_session['topology_id'] = topology_id
client = Client()
client.save()
message.channel_session['client_id'] = client.pk
Group("client-%s" % client.pk).add(message.reply_channel)
message.reply_channel.send({"text": json.dumps(["id", client.pk])})
send_tests(message.reply_channel)
def send_tests(channel):
for name, test_case_data in TestCase.objects.all().values_list('name', 'test_case_data'):
channel.send({"text": json.dumps(["TestCase", [name, json.loads(test_case_data)]])})
@channel_session
def ws_message(message):
Channel('test_persistence').send({"text": message['text'],
"topology": message.channel_session['topology_id'],
"client": message.channel_session['client_id']})
@channel_session
def ws_disconnect(message):
pass

View File

@ -1,18 +0,0 @@
messages:
- {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: 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: 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]}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

View File

@ -1,168 +0,0 @@
app: awx.network_ui_test
external_models: []
models:
- fields:
- name: client_id
pk: true
type: AutoField
name: Client
x: -518
y: 138
- fields:
- name: fsm_trace_id
pk: true
type: AutoField
- len: 200
name: fsm_name
type: CharField
- len: 200
name: from_state
type: CharField
- len: 200
name: to_state
type: CharField
- len: 200
name: message_type
type: CharField
- name: client
ref: Client
ref_field: client_id
type: ForeignKey
- default: 0
name: trace_session_id
type: IntegerField
- default: 0
name: order
type: IntegerField
name: FSMTrace
x: -872
y: 507
- fields:
- name: event_trace_id
pk: true
type: AutoField
- name: client
ref: Client
ref_field: client_id
type: ForeignKey
- default: 0
name: trace_session_id
type: IntegerField
- name: event_data
type: TextField
- name: message_id
type: IntegerField
name: EventTrace
x: -1087
y: 202
- fields:
- name: coverage_id
pk: true
type: AutoField
- name: coverage_data
type: TextField
- name: test_result
ref: TestResult
ref_field: test_result_id
type: ForeignKey
name: Coverage
x: -1068
y: -4
- fields:
- name: topology_snapshot_id
pk: true
type: AutoField
- name: client
ref: Client
ref_field: client_id
type: ForeignKey
- name: topology_id
type: IntegerField
- name: trace_session_id
type: IntegerField
- name: snapshot_data
ref: TopologySnapshot
ref_field: snapshot_data
type: TextField
- name: order
type: IntegerField
name: TopologySnapshot
x: -1123
y: -277
- fields:
- name: test_case_id
pk: true
type: AutoField
- len: 200
name: name
ref: TestCase
ref_field: name
type: CharField
- name: test_case_data
type: TextField
name: TestCase
x: -1642
y: -38
- fields:
- name: result_id
pk: true
type: AutoField
- len: 20
name: name
type: CharField
name: Result
x: -1610
y: 120
- fields:
- name: code_under_test_id
pk: true
ref: CodeUnderTest
ref_field: code_under_test_id
type: AutoField
- name: version_x
type: IntegerField
- name: version_y
type: IntegerField
- name: version_z
type: IntegerField
- name: commits_since
type: IntegerField
- len: 40
name: commit_hash
type: CharField
name: CodeUnderTest
x: -1612
y: 259
- fields:
- name: test_result_id
pk: true
type: AutoField
- name: test_case
ref: TestCase
ref_field: test_case_id
type: ForeignKey
- name: result
ref: Result
ref_field: result_id
type: ForeignKey
- name: code_under_test
ref: CodeUnderTest
ref_field: code_under_test_id
type: ForeignKey
- name: time
type: DateTimeField
- default: 0
name: id
type: IntegerField
- name: client
ref: Client
ref_field: client_id
type: ForeignKey
name: TestResult
x: -1336
y: -49
modules: []
view:
panX: 213.729555519212
panY: 189.446959094643
scaleXY: 0.69

View File

@ -1,102 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2018-03-06 18:40
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('network_ui', '0001_initial'),
]
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(blank=True, max_length=40)),
],
),
migrations.CreateModel(
name='Coverage',
fields=[
('coverage_id', models.AutoField(primary_key=True, serialize=False)),
('coverage_data', models.TextField()),
],
),
migrations.CreateModel(
name='EventTrace',
fields=[
('event_trace_id', models.AutoField(primary_key=True, serialize=False)),
('trace_session_id', models.IntegerField(default=0)),
('event_data', models.TextField()),
('message_id', models.IntegerField()),
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Client')),
],
),
migrations.CreateModel(
name='FSMTrace',
fields=[
('fsm_trace_id', models.AutoField(primary_key=True, serialize=False)),
('fsm_name', models.CharField(blank=True, max_length=200)),
('from_state', models.CharField(blank=True, max_length=200)),
('to_state', models.CharField(blank=True, max_length=200)),
('message_type', models.CharField(blank=True, max_length=200)),
('trace_session_id', models.IntegerField(default=0)),
('order', models.IntegerField(default=0)),
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Client')),
],
),
migrations.CreateModel(
name='Result',
fields=[
('result_id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(blank=True, max_length=20)),
],
),
migrations.CreateModel(
name='TestCase',
fields=[
('test_case_id', models.AutoField(primary_key=True, serialize=False)),
('name', models.CharField(blank=True, 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()),
('id', models.IntegerField(default=0)),
('client', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui.Client')),
('code_under_test', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui_test.CodeUnderTest')),
('result', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui_test.Result')),
('test_case', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui_test.TestCase')),
],
),
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')),
],
),
migrations.AddField(
model_name='coverage',
name='test_result',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='network_ui_test.TestResult'),
),
]

View File

@ -1,29 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2018-03-06 22:43
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_test', 'Result')
for result in results:
Result.objects.get_or_create(name=result)
class Migration(migrations.Migration):
dependencies = [
('network_ui_test', '0001_initial'),
]
operations = [
migrations.RunPython(
code=populate_result_types,
),
]

View File

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.7 on 2018-03-06 23:06
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_test', 'TestCase')
TestCase.objects.get_or_create(name="Load", test_case_data=json.dumps(dict(runnable=False)))
class Migration(migrations.Migration):
dependencies = [
('network_ui_test', '0002_auto_20180306_2243'),
]
operations = [
migrations.RunPython(
code=add_load_test_case,
),
]

View File

@ -1,73 +0,0 @@
from django.db import models
class FSMTrace(models.Model):
fsm_trace_id = models.AutoField(primary_key=True,)
fsm_name = models.CharField(max_length=200, blank=True)
from_state = models.CharField(max_length=200, blank=True)
to_state = models.CharField(max_length=200, blank=True)
message_type = models.CharField(max_length=200, blank=True)
client = models.ForeignKey('network_ui.Client',)
trace_session_id = models.IntegerField(default=0,)
order = models.IntegerField(default=0,)
class EventTrace(models.Model):
event_trace_id = models.AutoField(primary_key=True,)
client = models.ForeignKey('network_ui.Client',)
trace_session_id = models.IntegerField(default=0,)
event_data = models.TextField()
message_id = models.IntegerField()
class Coverage(models.Model):
coverage_id = models.AutoField(primary_key=True,)
coverage_data = models.TextField()
test_result = models.ForeignKey('TestResult',)
class TopologySnapshot(models.Model):
topology_snapshot_id = models.AutoField(primary_key=True,)
client = models.ForeignKey('network_ui.Client',)
topology_id = models.IntegerField()
trace_session_id = models.IntegerField()
snapshot_data = models.TextField('TopologySnapshot',)
order = models.IntegerField()
class TestCase(models.Model):
test_case_id = models.AutoField(primary_key=True,)
name = models.CharField('TestCase', max_length=200, blank=True)
test_case_data = models.TextField()
class Result(models.Model):
result_id = models.AutoField(primary_key=True,)
name = models.CharField(max_length=20, blank=True)
class CodeUnderTest(models.Model):
code_under_test_id = models.AutoField('CodeUnderTest', primary_key=True,)
version_x = models.IntegerField()
version_y = models.IntegerField()
version_z = models.IntegerField()
commits_since = models.IntegerField()
commit_hash = models.CharField(max_length=40, blank=True)
class TestResult(models.Model):
test_result_id = models.AutoField(primary_key=True,)
test_case = models.ForeignKey('TestCase',)
result = models.ForeignKey('Result',)
code_under_test = models.ForeignKey('CodeUnderTest',)
time = models.DateTimeField()
id = models.IntegerField(default=0,)
client = models.ForeignKey('network_ui.Client',)

View File

@ -1,12 +0,0 @@
# Copyright (c) 2017 Red Hat, Inc
from channels.routing import route
from awx.network_ui_test.consumers import ws_connect, ws_message, ws_disconnect, TestPersistence
channel_routing = [
route("websocket.connect", ws_connect, path=r"^/network_ui/test"),
route("websocket.receive", ws_message, path=r"^/network_ui/test"),
route("websocket.disconnect", ws_disconnect, path=r"^/network_ui/test"),
route("test_persistence", TestPersistence().handle),
]

View File

@ -1,5 +0,0 @@
<form action="/network_ui_test/upload_test" method="post" enctype="multipart/form-data">
{% csrf_token %}
{{form}}
<button type="submit">Upload Test</button>
</form>

View File

@ -1,13 +0,0 @@
SERVER = "https://meganuke:8043"
PORT = "9000"
.PHONY: clean coverage
clean:
git clean -fdX .
git clean -fd .
coverage:
./coverage_report.py ${SERVER}
python -m SimpleHTTPServer ${PORT}

View File

@ -1,51 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
Usage:
coverage_report [options] <server>
Options:
-h, --help Show this page
--debug Show debug logging
--verbose Show verbose logging
"""
from docopt import docopt
import logging
import sys
import os
import requests
import subprocess
logger = logging.getLogger('coverage_report')
TESTS_API = '/network_ui_test/tests'
def main(args=None):
if args is None:
args = sys.argv[1:]
parsed_args = docopt(__doc__, args)
if parsed_args['--debug']:
logging.basicConfig(level=logging.DEBUG)
elif parsed_args['--verbose']:
logging.basicConfig(level=logging.INFO)
else:
logging.basicConfig(level=logging.WARNING)
server = parsed_args['<server>']
tests = requests.get(server + TESTS_API, verify=False).json()
for test in tests['tests']:
if not os.path.exists(test['name']):
os.mkdir(test['name'])
with open(test['name'] + "/coverage.json", 'w') as f:
f.write(requests.get(server + test['coverage'], verify=False).text)
for test in tests['tests']:
subprocess.Popen('istanbul report html', shell=True, cwd=test['name']).wait()
subprocess.Popen('istanbul report html', shell=True).wait()
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

View File

@ -1,2 +0,0 @@
requests
docopt

View File

@ -1,14 +0,0 @@
# Copyright (c) 2017 Red Hat, Inc
from django.conf.urls import url
from awx.network_ui_test import views
app_name = 'network_ui_test'
urlpatterns = [
url(r'^tests$', views.tests, name='tests'),
url(r'^upload_test$', views.upload_test, name='upload_test'),
url(r'^download_coverage/(?P<pk>[0-9]+)$', views.download_coverage, name='download_coverage'),
url(r'^download_trace$', views.download_trace, name='download_trace'),
url(r'^download_recording$', views.download_recording, name='download_recording'),
]

View File

@ -1,112 +0,0 @@
# Copyright (c) 2017 Red Hat, Inc
from django.shortcuts import render
from django import forms
from django.http import JsonResponse, HttpResponse, HttpResponseRedirect
from django.core.exceptions import ObjectDoesNotExist
import yaml
import json
# Create your views here.
from .models import FSMTrace, EventTrace, TopologySnapshot
from .models import TestCase, TestResult, Coverage
class FSMTraceForm(forms.Form):
topology_id = forms.IntegerField()
trace_id = forms.IntegerField()
client_id = forms.IntegerField()
def download_trace(request):
form = FSMTraceForm(request.GET)
if form.is_valid():
topology_id = form.cleaned_data['topology_id']
trace_id = form.cleaned_data['trace_id']
client_id = form.cleaned_data['client_id']
data = list(FSMTrace.objects.filter(trace_session_id=trace_id,
client_id=client_id).order_by('order').values())
response = HttpResponse(yaml.safe_dump(data, default_flow_style=False),
content_type="application/force-download")
response['Content-Disposition'] = 'attachment; filename="trace_{0}_{1}_{2}.yml"'.format(topology_id, client_id, trace_id)
return response
else:
return HttpResponse(form.errors)
class RecordingForm(forms.Form):
topology_id = forms.IntegerField()
trace_id = forms.IntegerField()
client_id = forms.IntegerField()
def download_recording(request):
form = RecordingForm(request.GET)
if form.is_valid():
topology_id = form.cleaned_data['topology_id']
trace_id = form.cleaned_data['trace_id']
client_id = form.cleaned_data['client_id']
data = dict()
data['event_trace'] = [json.loads(x) for x in EventTrace
.objects.filter(trace_session_id=trace_id, client_id=client_id)
.order_by('message_id')
.values_list('event_data', flat=True)]
data['fsm_trace'] = list(FSMTrace
.objects
.filter(trace_session_id=trace_id, client_id=client_id)
.order_by('order')
.values())
data['snapshots'] = [json.loads(x) for x in TopologySnapshot
.objects.filter(trace_session_id=trace_id, client_id=client_id)
.order_by('order')
.values_list('snapshot_data', flat=True)]
response = HttpResponse(json.dumps(data, sort_keys=True, indent=4),
content_type="application/force-download")
response['Content-Disposition'] = 'attachment; filename="trace_{0}_{1}_{2}.yml"'.format(topology_id, client_id, trace_id)
return response
else:
return HttpResponse(form.errors)
def tests(request):
tests = list(TestCase.objects.all().values('test_case_id', 'name'))
for x in tests:
x['coverage'] = "/network_ui_test/download_coverage/{0}".format(x['test_case_id'])
return JsonResponse(dict(tests=tests))
def create_test(name, data):
try:
test_case = TestCase.objects.get(name=name)
test_case.test_case_data=json.dumps(data)
test_case.save()
except ObjectDoesNotExist:
TestCase(name=name, test_case_data=json.dumps(data)).save()
class UploadTestForm(forms.Form):
name = forms.CharField()
file = forms.FileField()
def upload_test(request):
if request.method == 'POST':
form = UploadTestForm(request.POST, request.FILES)
if form.is_valid():
name = form.cleaned_data['name']
data = json.loads(request.FILES['file'].read())
create_test(name, data)
return HttpResponseRedirect('/network_ui_test/tests')
else:
form = UploadTestForm()
return render(request, 'network_ui_test/upload_test.html', {'form': form})
def download_coverage(request, pk):
latest_tr = TestResult.objects.filter(test_case_id=pk).order_by('-time')[0]
coverage = Coverage.objects.get(test_result_id=latest_tr.pk)
response = HttpResponse(coverage.coverage_data,
content_type="application/json")
return response

View File

@ -279,8 +279,7 @@ INSTALLED_APPS = (
'awx.ui', 'awx.ui',
'awx.sso', 'awx.sso',
'solo', 'solo',
'awx.network_ui', 'awx.network_ui'
'awx.network_ui_test',
) )
INTERNAL_IPS = ('127.0.0.1',) INTERNAL_IPS = ('127.0.0.1',)

View File

@ -35,6 +35,7 @@ var NetworkUIController = function($scope,
$scope.api_token = ''; $scope.api_token = '';
$scope.disconnected = false; $scope.disconnected = false;
$scope.tests_enabled = false;
$scope.topology_id = 0; $scope.topology_id = 0;
// Create a web socket to connect to the backend server // Create a web socket to connect to the backend server
@ -54,9 +55,17 @@ var NetworkUIController = function($scope,
$scope.control_socket = new ReconnectingWebSocket(protocol + "://" + window.location.host + "/network_ui/topology?inventory_id=" + $scope.inventory_id, $scope.control_socket = new ReconnectingWebSocket(protocol + "://" + window.location.host + "/network_ui/topology?inventory_id=" + $scope.inventory_id,
null, null,
{debug: false, reconnectInterval: 300}); {debug: false, reconnectInterval: 300});
$scope.test_socket = new ReconnectingWebSocket(protocol + "://" + window.location.host + "/network_ui/test?inventory_id=" + $scope.inventory_id, if ($scope.tests_enabled) {
null, $scope.test_socket = new ReconnectingWebSocket(protocol + "://" + window.location.host + "/network_ui/test?inventory_id=" + $scope.inventory_id,
{debug: false, reconnectInterval: 300}); null,
{debug: false, reconnectInterval: 300});
} else {
$scope.test_socket = {
on_message: util.noop,
send: util.noop
};
}
} else { } else {
$scope.control_socket = { $scope.control_socket = {
on_message: util.noop on_message: util.noop

View File

@ -16,7 +16,6 @@ urlpatterns = [
url(r'^sso/', include('awx.sso.urls', namespace='sso')), url(r'^sso/', include('awx.sso.urls', namespace='sso')),
url(r'^sso/', include('social_django.urls', namespace='social')), url(r'^sso/', include('social_django.urls', namespace='social')),
url(r'^network_ui/', include('awx.network_ui.urls', namespace='network_uiui', app_name='network_ui')), url(r'^network_ui/', include('awx.network_ui.urls', namespace='network_uiui', app_name='network_ui')),
url(r'^network_ui_test/', include('awx.network_ui_test.urls', namespace='network_ui_test', app_name='network_ui_test')),
url(r'^(?:api/)?400.html$', handle_400), url(r'^(?:api/)?400.html$', handle_400),
url(r'^(?:api/)?403.html$', handle_403), url(r'^(?:api/)?403.html$', handle_403),
url(r'^(?:api/)?404.html$', handle_404), url(r'^(?:api/)?404.html$', handle_404),