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";
     }
 }