Working on tests for datagram sequencing, added autogenerated ==/!= operators

to mtc.
This commit is contained in:
Andrzej Kapolka 2014-02-09 16:26:53 -08:00
parent d5b8d1c0d6
commit 524ceb4ed2
10 changed files with 414 additions and 3 deletions

View file

@ -39,4 +39,5 @@ add_subdirectory(assignment-client)
add_subdirectory(domain-server)
add_subdirectory(interface)
add_subdirectory(pairing-server)
add_subdirectory(voxel-edit)
add_subdirectory(tests)
add_subdirectory(voxel-edit)

View file

@ -82,6 +82,14 @@ bool AttributeValue::operator==(void* other) const {
return _attribute && _attribute->equal(_value, other);
}
bool AttributeValue::operator!=(const AttributeValue& other) const {
return _attribute != other._attribute || (_attribute && !_attribute->equal(_value, other._value));
}
bool AttributeValue::operator!=(void* other) const {
return !_attribute || !_attribute->equal(_value, other);
}
OwnedAttributeValue::OwnedAttributeValue(const AttributePointer& attribute, void* value) :
AttributeValue(attribute, value) {
}

View file

@ -105,6 +105,9 @@ public:
bool operator==(const AttributeValue& other) const;
bool operator==(void* other) const;
bool operator!=(const AttributeValue& other) const;
bool operator!=(void* other) const;
protected:
AttributePointer _attribute;

View file

@ -415,12 +415,16 @@ public:
#define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \
Bitstream& operator<<(Bitstream& out, const X& obj); \
Bitstream& operator>>(Bitstream& in, X& obj); \
bool operator==(const X& first, const X& second); \
bool operator!=(const X& first, const X& second); \
static const int* _TypePtr##X = &X::Type;
#else
#define STRINGIFY(x) #x
#define DECLARE_STREAMABLE_METATYPE(X) Q_DECLARE_METATYPE(X) \
Bitstream& operator<<(Bitstream& out, const X& obj); \
Bitstream& operator>>(Bitstream& in, X& obj); \
bool operator==(const X& first, const X& second); \
bool operator!=(const X& first, const X& second); \
static const int* _TypePtr##X = &X::Type; \
_Pragma(STRINGIFY(unused(_TypePtr##X)))
#endif

10
tests/CMakeLists.txt Normal file
View file

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 2.8)
# add the test directories
file(GLOB TEST_SUBDIRS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/*)
foreach(DIR ${TEST_SUBDIRS})
if(IS_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${DIR})
add_subdirectory(${DIR})
endif()
endforeach()

View file

@ -0,0 +1,32 @@
cmake_minimum_required(VERSION 2.8)
set(TARGET_NAME metavoxel-tests)
set(ROOT_DIR ../..)
set(MACRO_DIR ${ROOT_DIR}/cmake/macros)
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
find_package(Qt5Network REQUIRED)
find_package(Qt5Script REQUIRED)
find_package(Qt5Widgets REQUIRED)
include(${MACRO_DIR}/SetupHifiProject.cmake)
setup_hifi_project(${TARGET_NAME} TRUE)
include(${MACRO_DIR}/AutoMTC.cmake)
auto_mtc(${TARGET_NAME} ${ROOT_DIR})
qt5_use_modules(${TARGET_NAME} Network Script Widgets)
#include glm
include(${MACRO_DIR}/IncludeGLM.cmake)
include_glm(${TARGET_NAME} ${ROOT_DIR})
# link in the shared libraries
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
link_hifi_library(metavoxels ${TARGET_NAME} ${ROOT_DIR})
link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR})

View file

@ -0,0 +1,178 @@
//
// MetavoxelTests.cpp
// metavoxel-tests
//
// Created by Andrzej Kapolka on 2/7/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#include <stdlib.h>
#include <DatagramSequencer.h>
#include <SharedUtil.h>
#include "MetavoxelTests.h"
MetavoxelTests::MetavoxelTests(int& argc, char** argv) :
QCoreApplication(argc, argv) {
}
static int highPriorityMessagesSent = 0;
static int unreliableMessagesSent = 0;
static int unreliableMessagesReceived = 0;
bool MetavoxelTests::run() {
qDebug() << "Running metavoxel tests...";
// seed the random number generator so that our tests are reproducible
srand(0xBAAAAABE);
// create two endpoints with the same header
QByteArray datagramHeader("testheader");
Endpoint alice(datagramHeader), bob(datagramHeader);
alice.setOther(&bob);
bob.setOther(&alice);
// perform a large number of simulation iterations
const int SIMULATION_ITERATIONS = 100000;
for (int i = 0; i < SIMULATION_ITERATIONS; i++) {
if (alice.simulate(i) || bob.simulate(i)) {
return true;
}
}
qDebug() << "Sent " << highPriorityMessagesSent << " high priority messages";
qDebug() << "Sent " << unreliableMessagesSent << " unreliable messages, received " << unreliableMessagesReceived;
qDebug() << "All tests passed!";
return false;
}
Endpoint::Endpoint(const QByteArray& datagramHeader) :
_sequencer(new DatagramSequencer(datagramHeader)),
_highPriorityMessagesToSend(0.0f) {
connect(_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendDatagram(const QByteArray&)));
connect(_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readMessage(Bitstream&)));
connect(_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)),
SLOT(handleHighPriorityMessage(const QVariant&)));
}
static QByteArray createRandomBytes() {
const int MIN_BYTES = 4;
const int MAX_BYTES = 16;
QByteArray bytes(randIntInRange(MIN_BYTES, MAX_BYTES), 0);
for (int i = 0; i < bytes.size(); i++) {
bytes[i] = rand();
}
return bytes;
}
static QVariant createRandomMessage() {
switch (randIntInRange(0, 2)) {
case 0: {
TestMessageA message = { randomBoolean(), rand(), randFloat() };
return QVariant::fromValue(message);
}
case 1: {
TestMessageB message = { createRandomBytes() };
return QVariant::fromValue(message);
}
case 2:
default: {
TestMessageC message;
message.foo = randomBoolean();
message.bar = rand();
message.baz = randFloat();
message.bong.foo = createRandomBytes();
return QVariant::fromValue(message);
}
}
}
static bool messagesEqual(const QVariant& firstMessage, const QVariant& secondMessage) {
int type = firstMessage.userType();
if (secondMessage.userType() != type) {
return false;
}
if (type == TestMessageA::Type) {
return firstMessage.value<TestMessageA>() == secondMessage.value<TestMessageA>();
} else if (type == TestMessageB::Type) {
return firstMessage.value<TestMessageB>() == secondMessage.value<TestMessageB>();
} else if (type == TestMessageC::Type) {
return firstMessage.value<TestMessageC>() == secondMessage.value<TestMessageC>();
} else {
return firstMessage == secondMessage;
}
}
bool Endpoint::simulate(int iterationNumber) {
// enqueue some number of high priority messages
const float MIN_HIGH_PRIORITY_MESSAGES = 0.0f;
const float MAX_HIGH_PRIORITY_MESSAGES = 2.0f;
_highPriorityMessagesToSend += randFloatInRange(MIN_HIGH_PRIORITY_MESSAGES, MAX_HIGH_PRIORITY_MESSAGES);
while (_highPriorityMessagesToSend >= 1.0f) {
QVariant message = createRandomMessage();
_highPriorityMessagesSent.append(message);
_sequencer->sendHighPriorityMessage(message);
highPriorityMessagesSent++;
_highPriorityMessagesToSend -= 1.0f;
}
// send a packet
try {
Bitstream& out = _sequencer->startPacket();
SequencedTestMessage message = { iterationNumber, createRandomMessage() };
_unreliableMessagesSent.append(message);
unreliableMessagesSent++;
out << message;
_sequencer->endPacket();
} catch (const QString& message) {
qDebug() << message;
return true;
}
return false;
}
void Endpoint::sendDatagram(const QByteArray& datagram) {
// some datagrams are dropped
const float DROP_PROBABILITY = 0.1f;
if (randFloat() < DROP_PROBABILITY) {
return;
}
_other->_sequencer->receivedDatagram(datagram);
}
void Endpoint::handleHighPriorityMessage(const QVariant& message) {
if (_other->_highPriorityMessagesSent.isEmpty()) {
throw QString("Received unsent/already sent high priority message.");
}
QVariant sentMessage = _other->_highPriorityMessagesSent.takeFirst();
if (!messagesEqual(message, sentMessage)) {
throw QString("Sent/received high priority message mismatch.");
}
}
void Endpoint::readMessage(Bitstream& in) {
SequencedTestMessage message;
in >> message;
for (QList<SequencedTestMessage>::iterator it = _other->_unreliableMessagesSent.begin();
it != _other->_unreliableMessagesSent.end(); it++) {
if (it->sequenceNumber == message.sequenceNumber) {
if (!messagesEqual(it->submessage, message.submessage)) {
throw QString("Sent/received unreliable message mismatch.");
}
_other->_unreliableMessagesSent.erase(_other->_unreliableMessagesSent.begin(), it + 1);
unreliableMessagesReceived++;
return;
}
}
throw QString("Received unsent/already sent unreliable message.");
}

View file

@ -0,0 +1,109 @@
//
// MetavoxelTests.h
// metavoxel-tests
//
// Created by Andrzej Kapolka on 2/7/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
//
#ifndef __interface__MetavoxelTests__
#define __interface__MetavoxelTests__
#include <QCoreApplication>
#include <QVariantList>
#include <Bitstream.h>
class DatagramSequencer;
class SequencedTestMessage;
/// Tests various aspects of the metavoxel library.
class MetavoxelTests : public QCoreApplication {
Q_OBJECT
public:
MetavoxelTests(int& argc, char** argv);
/// Performs our various tests.
/// \return true if any of the tests failed.
bool run();
};
/// Represents a simulated endpoint.
class Endpoint : public QObject {
Q_OBJECT
public:
Endpoint(const QByteArray& datagramHeader);
void setOther(Endpoint* other) { _other = other; }
/// Perform a simulation step.
/// \return true if failure was detected
bool simulate(int iterationNumber);
private slots:
void sendDatagram(const QByteArray& datagram);
void handleHighPriorityMessage(const QVariant& message);
void readMessage(Bitstream& in);
private:
DatagramSequencer* _sequencer;
Endpoint* _other;
float _highPriorityMessagesToSend;
QVariantList _highPriorityMessagesSent;
QList<SequencedTestMessage> _unreliableMessagesSent;
};
/// A simple test message.
class TestMessageA {
STREAMABLE
public:
STREAM bool foo;
STREAM int bar;
STREAM float baz;
};
DECLARE_STREAMABLE_METATYPE(TestMessageA)
// Another simple test message.
class TestMessageB {
STREAMABLE
public:
STREAM QByteArray foo;
};
DECLARE_STREAMABLE_METATYPE(TestMessageB)
// A test message that demonstrates inheritance and composition.
class TestMessageC : public TestMessageA {
STREAMABLE
public:
STREAM TestMessageB bong;
};
DECLARE_STREAMABLE_METATYPE(TestMessageC)
/// Combines a sequence number with a submessage; used for testing unreliable transport.
class SequencedTestMessage {
STREAMABLE
public:
STREAM int sequenceNumber;
STREAM QVariant submessage;
};
DECLARE_STREAMABLE_METATYPE(SequencedTestMessage)
#endif /* defined(__interface__MetavoxelTests__) */

View file

@ -0,0 +1,14 @@
//
// main.cpp
// metavoxel-tests
//
// Created by Andrzej Kapolka on 2/7/14.
// Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
#include <QDebug>
#include "MetavoxelTests.h"
int main(int argc, char** argv) {
return MetavoxelTests(argc, argv).run();
}

View file

@ -90,7 +90,7 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
foreach (const Streamable& str, streamables) {
const QString& name = str.clazz.name;
out << "Bitstream& operator<< (Bitstream& out, const " << name << "& obj) {\n";
out << "Bitstream& operator<<(Bitstream& out, const " << name << "& obj) {\n";
foreach (const QString& base, str.clazz.bases) {
out << " out << static_cast<const " << base << "&>(obj);\n";
}
@ -100,7 +100,7 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
out << " return out;\n";
out << "}\n";
out << "Bitstream& operator>> (Bitstream& in, " << name << "& obj) {\n";
out << "Bitstream& operator>>(Bitstream& in, " << name << "& obj) {\n";
foreach (const QString& base, str.clazz.bases) {
out << " in >> static_cast<" << base << "&>(obj);\n";
}
@ -110,6 +110,58 @@ void generateOutput (QTextStream& out, const QList<Streamable>& streamables) {
out << " return in;\n";
out << "}\n";
out << "bool operator==(const " << name << "& first, const " << name << "& second) {\n";
if (str.clazz.bases.isEmpty() && str.fields.isEmpty()) {
out << " return true";
} else {
out << " return ";
bool first = true;
foreach (const QString& base, str.clazz.bases) {
if (!first) {
out << " &&\n";
out << " ";
}
out << "static_cast<" << base << "&>(first) == static_cast<" << base << "&>(second)";
first = false;
}
foreach (const QString& field, str.fields) {
if (!first) {
out << " &&\n";
out << " ";
}
out << "first." << field << " == second." << field;
first = false;
}
}
out << ";\n";
out << "}\n";
out << "bool operator!=(const " << name << "& first, const " << name << "& second) {\n";
if (str.clazz.bases.isEmpty() && str.fields.isEmpty()) {
out << " return false";
} else {
out << " return ";
bool first = true;
foreach (const QString& base, str.clazz.bases) {
if (!first) {
out << " ||\n";
out << " ";
}
out << "static_cast<" << base << "&>(first) != static_cast<" << base << "&>(second)";
first = false;
}
foreach (const QString& field, str.fields) {
if (!first) {
out << " ||\n";
out << " ";
}
out << "first." << field << " != second." << field;
first = false;
}
}
out << ";\n";
out << "}\n";
out << "const int " << name << "::Type = registerStreamableMetaType<" << name << ">();\n\n";
}
}