From 524ceb4ed2f788ce9ebf012f27a835c148bb12fe Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka <drzej.k@gmail.com> Date: Sun, 9 Feb 2014 16:26:53 -0800 Subject: [PATCH] Working on tests for datagram sequencing, added autogenerated ==/!= operators to mtc. --- CMakeLists.txt | 3 +- .../metavoxels/src/AttributeRegistry.cpp | 8 + libraries/metavoxels/src/AttributeRegistry.h | 3 + libraries/metavoxels/src/Bitstream.h | 4 + tests/CMakeLists.txt | 10 + tests/metavoxels/CMakeLists.txt | 32 ++++ tests/metavoxels/src/MetavoxelTests.cpp | 178 ++++++++++++++++++ tests/metavoxels/src/MetavoxelTests.h | 109 +++++++++++ tests/metavoxels/src/main.cpp | 14 ++ tools/mtc/src/main.cpp | 56 +++++- 10 files changed, 414 insertions(+), 3 deletions(-) create mode 100644 tests/CMakeLists.txt create mode 100644 tests/metavoxels/CMakeLists.txt create mode 100644 tests/metavoxels/src/MetavoxelTests.cpp create mode 100644 tests/metavoxels/src/MetavoxelTests.h create mode 100644 tests/metavoxels/src/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a583d7d951..f20142d698 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,4 +39,5 @@ add_subdirectory(assignment-client) add_subdirectory(domain-server) add_subdirectory(interface) add_subdirectory(pairing-server) -add_subdirectory(voxel-edit) \ No newline at end of file +add_subdirectory(tests) +add_subdirectory(voxel-edit) diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 431eaf62c9..9595d96e1f 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -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) { } diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index db5e54cc4a..56cff7eeb4 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -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; diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index 5a79b10766..cc776a742a 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000000..e817ffe506 --- /dev/null +++ b/tests/CMakeLists.txt @@ -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() + diff --git a/tests/metavoxels/CMakeLists.txt b/tests/metavoxels/CMakeLists.txt new file mode 100644 index 0000000000..a21dc2f316 --- /dev/null +++ b/tests/metavoxels/CMakeLists.txt @@ -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}) + + diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp new file mode 100644 index 0000000000..ec8b474f33 --- /dev/null +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -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."); +} diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h new file mode 100644 index 0000000000..fb47d9f463 --- /dev/null +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -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__) */ diff --git a/tests/metavoxels/src/main.cpp b/tests/metavoxels/src/main.cpp new file mode 100644 index 0000000000..10bf786957 --- /dev/null +++ b/tests/metavoxels/src/main.cpp @@ -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(); +} diff --git a/tools/mtc/src/main.cpp b/tools/mtc/src/main.cpp index 77f0a069b5..050fe0e418 100644 --- a/tools/mtc/src/main.cpp +++ b/tools/mtc/src/main.cpp @@ -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"; } }