From db92e549ed40ecb7f2ceca07f07fe2744b20f861 Mon Sep 17 00:00:00 2001
From: Doralitze <doralitze@chaotikum.org>
Date: Sat, 9 Jan 2021 21:44:03 +0100
Subject: [PATCH] add: Basic connection_line_buffer test

---
 src/net/connection_line_buffer.cpp   | 32 +++++-----
 src/net/connection_line_buffer.hpp   |  4 +-
 test/connection_line_buffer_test.cpp | 89 ++++++++++++++++++++++++++++
 test/loopback_connection_client.hpp  | 20 ++++++-
 4 files changed, 127 insertions(+), 18 deletions(-)
 create mode 100644 test/connection_line_buffer_test.cpp

diff --git a/src/net/connection_line_buffer.cpp b/src/net/connection_line_buffer.cpp
index fd8a0c2..00ae1b8 100644
--- a/src/net/connection_line_buffer.cpp
+++ b/src/net/connection_line_buffer.cpp
@@ -9,7 +9,7 @@
 
 namespace rmrf::net {
 
-static std::string::size_type default_eol_search(
+std::string::size_type default_eol_search(
     const std::string &data,
     std::string::size_type start_position
 ) {
@@ -50,7 +50,10 @@ connection_line_buffer::connection_line_buffer(
     max(max_line_size),
     data("")
 {
-    this->client->set_incomming_data_callback(std::bind(&connection_line_buffer::conn_data_in_cb, this, std::placeholders::_1));
+    this->client->set_incomming_data_callback(
+        [this](const std::string& data_in) {
+            conn_data_in_cb(data_in);
+        });
 }
 
 connection_line_buffer::connection_line_buffer(
@@ -63,26 +66,27 @@ connection_line_buffer::connection_line_buffer(
 
 void connection_line_buffer::conn_data_in_cb(const std::string &data_in) {
 
-    /**
-     * Known limitation: If the last received data ends with '\r' and the next incoming data would start with '\n' there is no way
-     * to detect this as the received message might indeed be a complete one ending with '\r' and one cannot wait for a potential
-     * continuation of said message to arrive.
-     */
+    // Iterate throug the incomming data as long as we find line breaks
+    for (std::string::size_type strpos = 0, nextpos = -1; strpos != std::string::npos; strpos = nextpos++) {
+        // Search for the next line break
+        nextpos = this->search(data_in, strpos);
 
-    std::string::size_type strpos = 0;
-
-    while (strpos != std::string::npos) {
-        std::string::size_type nextpos = this->search(data_in, strpos);
+        // Advance, if the line would be empty
+        if (nextpos == strpos) {
+            nextpos = this->search(data_in, ++strpos);
+        }
 
         if (nextpos == std::string::npos) {
+            // If we didn't find one we store the remaining data in the buffer
             this->data += data_in.substr(strpos, data_in.length() - strpos);
             break;
         } else {
-            this->found_next_line_cb(this->data + data_in.substr(strpos, nextpos - strpos), true);
+            // If we find one we send the buffer plus the incomming data up to the line break to the callback
+            const std::string data_to_send = this->data + data_in.substr(strpos, nextpos - strpos);
+            this->found_next_line_cb(data_to_send, true);
+            // and clear the buffer
             this->data = std::string("");
         }
-
-        strpos = nextpos;
     }
 
     if (this->data.length() > this->max) {
diff --git a/src/net/connection_line_buffer.hpp b/src/net/connection_line_buffer.hpp
index 389a94f..1378141 100644
--- a/src/net/connection_line_buffer.hpp
+++ b/src/net/connection_line_buffer.hpp
@@ -17,7 +17,7 @@ namespace rmrf::net {
 
 typedef std::function<std::string::size_type(const std::string&, std::string::size_type)> eol_search_t;
 
-static std::string::size_type default_eol_search(const std::string& data, std::string::size_type start_position);
+std::string::size_type default_eol_search(const std::string& data, std::string::size_type start_position);
 
 class connection_line_buffer {
 public:
@@ -31,7 +31,7 @@ private:
     std::string data;
 
 public:
-    connection_line_buffer(std::shared_ptr<connection_client> c, found_next_line_cb_t found_next_line_cb_, std::string::size_type max_line_size, eol_search_t search_lb = default_eol_search);
+    connection_line_buffer(std::shared_ptr<connection_client> c, found_next_line_cb_t found_next_line_cb_, std::string::size_type max_line_size, eol_search_t search_lb);
     connection_line_buffer(std::shared_ptr<connection_client> c, found_next_line_cb_t found_next_line_cb_, std::string::size_type max_line_size);
 
 private:
diff --git a/test/connection_line_buffer_test.cpp b/test/connection_line_buffer_test.cpp
new file mode 100644
index 0000000..cb21581
--- /dev/null
+++ b/test/connection_line_buffer_test.cpp
@@ -0,0 +1,89 @@
+#define BOOST_AUTO_TEST_MAIN
+#define BOOST_TEST_MODULE RMRF_TESTS
+#include <boost/test/included/unit_test.hpp>
+
+#include <algorithm>
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include "loopback_connection_client.hpp"
+
+#include "net/connection_line_buffer.hpp"
+
+
+using namespace rmrf::net;
+using namespace rmrf::test;
+
+//BOOST_AUTO_TEST_SUITE( LINE_BUFFER )
+
+int mut_send_stage = 0;
+bool extra_failed = false;
+const bool display_dbg_msg = false;
+
+void mut_send_data_cb(const std::string& data) {
+    std::cout << "This method should never have been called yet we've got:" << std::endl;
+    std::cout << data << std::endl;
+    BOOST_CHECK(false);
+}
+
+void next_line_cb(const std::string& data, bool complete) {
+    if constexpr (display_dbg_msg) std::cout << mut_send_stage << ": " << data << std::endl;
+
+    switch (mut_send_stage++) {
+    case 0:
+        BOOST_CHECK_EQUAL(data, "The first line");
+        BOOST_CHECK(complete);
+        break;
+    case 1:
+        BOOST_CHECK_EQUAL(data, "The second line");
+        BOOST_CHECK(complete);
+        break;
+    case 2:
+        BOOST_CHECK_EQUAL(data, "The third line");
+        BOOST_CHECK(complete);
+        break;
+    case 3:
+        BOOST_CHECK_EQUAL(data.length(), 151);
+        BOOST_CHECK(!complete);
+        BOOST_CHECK(std::find_if(data.cbegin(), data.cend(), [](char c) {
+            return c != 'a';
+        }) == data.cend());
+        break;
+    default:
+        extra_failed = true;
+    }
+}
+
+BOOST_AUTO_TEST_CASE(Default_EoL_Search_Test) {
+    BOOST_CHECK_EQUAL(default_eol_search("This line contains no line break", 0), std::string::npos);
+    std::string data = "This\r line contains line\r\n breaks";
+    BOOST_CHECK_EQUAL(default_eol_search(data, 0), 4);
+    BOOST_CHECK_EQUAL(data.substr(0, 4), "This");
+    BOOST_CHECK_EQUAL(default_eol_search(data, 5), 25);
+    BOOST_CHECK_EQUAL(data.substr(5, 25 - 5), " line contains line\r");
+}
+
+BOOST_AUTO_TEST_CASE(Connection_Line_Buffer_Test) {
+    mut_send_stage = 0;
+    auto ll_client = std::make_shared<loopback_connection_client>(mut_send_data_cb, false);
+    connection_line_buffer clb(ll_client, next_line_cb, 150);
+
+    if constexpr (display_dbg_msg) std::cout << "Testing legit lines" << std::endl;
+
+    ll_client->send_data_to_incomming_data_cb("The first");
+    ll_client->send_data_to_incomming_data_cb(" line\r");
+    ll_client->send_data_to_incomming_data_cb("The second line\r");
+    ll_client->send_data_to_incomming_data_cb("\nThe third line\n");
+
+    if constexpr (display_dbg_msg) std::cout << "Testing line overflow" << std::endl;
+
+    for (int i = 0; i < 151; i++) {
+        ll_client->send_data_to_incomming_data_cb("a");
+    }
+
+    BOOST_CHECK_EQUAL(mut_send_stage, 4);
+    BOOST_CHECK(!extra_failed);
+}
+
+//BOOST_AUTO_TEST_SUITE_END()
diff --git a/test/loopback_connection_client.hpp b/test/loopback_connection_client.hpp
index 8c1ee84..f9b1828 100644
--- a/test/loopback_connection_client.hpp
+++ b/test/loopback_connection_client.hpp
@@ -14,6 +14,7 @@ class loopback_connection_client : public rmrf::net::connection_client {
 private:
     const rmrf::net::connection_client::incomming_data_cb send_data_cb;
     std::vector<std::string> data_archive;
+    const bool echo_data_transfer;
 
 public:
     /**
@@ -21,11 +22,13 @@ public:
      * send data.
      */
     loopback_connection_client(
-        rmrf::net::connection_client::incomming_data_cb send_data_cb_
+        rmrf::net::connection_client::incomming_data_cb send_data_cb_,
+        bool echo_data
     ) :
         rmrf::net::connection_client{},
         send_data_cb(send_data_cb_),
-        data_archive{}
+        data_archive{},
+        echo_data_transfer(echo_data)
     {
         // Does nothing special
     }
@@ -43,7 +46,12 @@ public:
      */
     virtual void write_data(const std::string& data) {
         // TODO fixme
+        if (this->echo_data_transfer) {
+            std::cout << "<-- " << data << std::endl;
+        }
+
         this->data_archive.push_back(data);
+
         if (this->send_data_cb) {
             this->send_data_cb(data);
         }
@@ -56,6 +64,10 @@ public:
      * @param data The data to send.
      */
     void send_data_to_incomming_data_cb(const std::string& data) {
+        if (this->echo_data_transfer) {
+            std::cout << "--> " << data << std::endl;
+        }
+
         if (this->in_data_cb) {
             this->in_data_cb(data);
         }
@@ -67,6 +79,10 @@ public:
     std::vector<std::string> get_send_data() {
         return this->data_archive;
     }
+
+    void clear_sent_data() {
+        this->data_archive.clear();
+    }
 };
 
 }
-- 
GitLab