From c79706c249494f80025e9f43010bcd941c28035a Mon Sep 17 00:00:00 2001
From: Marko Budiselic <mbudiselicbuda@gmail.com>
Date: Mon, 6 Jun 2016 23:57:16 +0200
Subject: [PATCH] concurrent and unit tests, concurrently linked list test (not
 production ready linked list :))

---
 CMakeLists.txt                        | 14 ++++++-
 README.md                             |  3 ++
 src/data_structures/linked_list.hpp   | 46 ++++++++++++++++++++
 src/data_structures/sllist.hpp        | 37 -----------------
 tests/CMakeLists.txt                  | 55 ++++++++++++++++--------
 tests/concurrent/linked_list.cpp      | 60 +++++++++++++++++++++++++++
 tests/{ => concurrent}/timer.cpp      |  0
 tests/{ => unit}/cypher_traversal.cpp |  0
 tests/{ => unit}/db_index.cpp         |  0
 tests/{ => unit}/dynamic_bitset.cpp   |  0
 tests/{ => unit}/lockfree_hashmap.cpp |  0
 tests/{ => unit}/skiplist.cpp         |  0
 12 files changed, 159 insertions(+), 56 deletions(-)
 create mode 100644 src/data_structures/linked_list.hpp
 delete mode 100644 src/data_structures/sllist.hpp
 create mode 100644 tests/concurrent/linked_list.cpp
 rename tests/{ => concurrent}/timer.cpp (100%)
 rename tests/{ => unit}/cypher_traversal.cpp (100%)
 rename tests/{ => unit}/db_index.cpp (100%)
 rename tests/{ => unit}/dynamic_bitset.cpp (100%)
 rename tests/{ => unit}/lockfree_hashmap.cpp (100%)
 rename tests/{ => unit}/skiplist.cpp (100%)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index e859d5111..28acf164b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,13 +13,25 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++1y")
 
 # functions
 
+# prints all included directories
 function(list_includes)
-    get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
+    get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+                      PROPERTY INCLUDE_DIRECTORIES)
     foreach(dir ${dirs})
           message(STATUS "dir='${dir}'")
     endforeach()
 endfunction(list_includes)
 
+# get file names from list of file paths
+function(get_file_names file_paths file_names)
+    set(file_names "")
+    foreach(file_path ${file_paths})
+        get_filename_component (file_name ${file_path} NAME_WE)
+        list(APPEND file_names ${file_name})
+    endforeach()
+    set(file_names "${file_names}" PARENT_SCOPE)
+endfunction()
+
 # custom targets
 
 # move test data data to the build directory
diff --git a/README.md b/README.md
index aa02a42cd..9c4f7b0c5 100644
--- a/README.md
+++ b/README.md
@@ -28,5 +28,8 @@ make
 ctest
 ctest -V
 ctest -R test_name
+ctest -R unit
+ctest -R concurrent
+ctest -R concurrent --parallel 4
 ```
 
diff --git a/src/data_structures/linked_list.hpp b/src/data_structures/linked_list.hpp
new file mode 100644
index 000000000..4d05cc4ef
--- /dev/null
+++ b/src/data_structures/linked_list.hpp
@@ -0,0 +1,46 @@
+#pragma once
+
+#include <list>
+
+#include "threading/sync/lockable.hpp"
+#include "threading/sync/spinlock.hpp"
+
+template <typename value_type, typename lock_type = SpinLock>
+class LinkedList : public Lockable<lock_type>
+{
+public:
+    std::size_t size() const
+    {
+        auto guard = this->acquire_unique();
+        return data.size();
+    }
+
+    void push_front(const value_type &value)
+    {
+        auto guard = this->acquire_unique();
+        data.push_front(value);
+    }
+
+    void push_front(value_type &&value)
+    {
+        auto guard = this->acquire_unique();
+        data.push_front(std::forward<value_type>(value));
+    }
+
+    void pop_front()
+    {
+        auto guard = this->acquire_unique();
+        data.pop_front();
+    }
+
+    // value_type& as return value
+    // would not be concurrent
+    value_type front()
+    {
+        auto guard = this->acquire_unique();
+        return data.front();
+    }
+
+private:
+    std::list<value_type> data;
+};
diff --git a/src/data_structures/sllist.hpp b/src/data_structures/sllist.hpp
deleted file mode 100644
index 1cada4c80..000000000
--- a/src/data_structures/sllist.hpp
+++ /dev/null
@@ -1,37 +0,0 @@
-#pragma once
-
-#include <list>
-
-#include "sync/spinlock.hpp"
-#include "sync/lockable.hpp"
-
-template <class T>
-class SpinLockedList : Lockable<SpinLock>
-{
-public:
-
-    void push_front(T item)
-    {
-        auto guard = this->acquire();
-        head = new Node(item, head);
-    }
-    
-    bool remove(const T&)
-    {
-        // HM!
-    }
-
-private:
-    struct Node : Lockable<SpinLock>
-    {
-        Node(T item, Node* next) : item(item), next(next) {}
-
-        T item;
-        Node* next;
-    };
-
-    Node* head;
-
-    // TODO add removed items to a list for garbage collection
-    // std::list<Node*> removed;
-};
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 5aa4ebb75..34fe7fef9 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -21,25 +21,44 @@ set(catch_source_dir ${source_dir})
 
 include_directories(${catch_source_dir}/include)
 
-# find tests
-file(GLOB_RECURSE test_files ${CMAKE_HOME_DIRECTORY}/tests/*.cpp)
-set(tests "")
-foreach(test_file ${test_files})
-    get_filename_component(test_name ${test_file} NAME_WE)
-    list(APPEND tests ${test_name})
-endforeach()
-MESSAGE(STATUS "Available tests are: ${tests}")
+## UNIT TESTS
 
-# copy test data
-file(COPY ${CMAKE_SOURCE_DIR}/tests/data DESTINATION ${CMAKE_BINARY_DIR}/tests)
+# find unit tests
+file(GLOB_RECURSE unit_test_files ${CMAKE_HOME_DIRECTORY}/tests/unit/*.cpp)
+get_file_names("${unit_test_files}" file_names)
+set(unit_test_names "${file_names}")
+message(STATUS "Available unit tests are: ${unit_test_names}")
 
-# build tests
-foreach(test ${tests})
-    add_executable(${test} ${test}.cpp)
+# copy unit test data
+file(COPY ${CMAKE_SOURCE_DIR}/tests/data
+     DESTINATION ${CMAKE_BINARY_DIR}/tests)
+
+# build unit tests
+foreach(test ${unit_test_names})
+    set(test_name unit_${test})
+    add_executable(${test_name} unit/${test}.cpp)
     # TODO: separate dependencies
-    target_link_libraries(${test} stdc++fs)
-    target_link_libraries(${test} cypher_lib)
-    target_link_libraries(${test} Threads::Threads)
-    add_test(NAME ${test} COMMAND ${test})
-    set_property(TARGET ${test} PROPERTY CXX_STANDARD 14)
+    target_link_libraries(${test_name} stdc++fs)
+    target_link_libraries(${test_name} cypher_lib)
+    target_link_libraries(${test_name} Threads::Threads)
+    add_test(NAME ${test_name} COMMAND ${test_name})
+    set_property(TARGET ${test_name} PROPERTY CXX_STANDARD 14)
+endforeach()
+
+## CONCURRENCY TESTS
+
+# find concurrency tests
+file(GLOB_RECURSE concurrency_test_files
+     ${CMAKE_HOME_DIRECTORY}/tests/concurrent/*.cpp)
+get_file_names("${concurrency_test_files}" file_names)
+set(concurrency_test_names "${file_names}")
+message(STATUS "Available concurrency tests are: ${concurrency_test_names}")
+
+# build concurrency tests
+foreach(test ${concurrency_test_names})
+    set(test_name concurrent_${test})
+    add_executable(${test_name} concurrent/${test}.cpp)
+    target_link_libraries(${test_name} Threads::Threads)
+    add_test(NAME ${test_name} COMMAND ${test_name})
+    set_property(TARGET ${test_name} PROPERTY CXX_STANDARD 14)
 endforeach()
diff --git a/tests/concurrent/linked_list.cpp b/tests/concurrent/linked_list.cpp
new file mode 100644
index 000000000..4ef5de29f
--- /dev/null
+++ b/tests/concurrent/linked_list.cpp
@@ -0,0 +1,60 @@
+#include <cassert>
+#include <iostream>
+#include <thread>
+
+#include "data_structures/linked_list.hpp"
+
+using std::cout;
+using std::endl;
+
+template <typename list_type>
+void test_concurrent_list_access(list_type &list, std::size_t size)
+{
+    // test concurrent access
+    for (int i = 0; i < 1000000; ++i) {
+
+        std::thread t1([&list] {
+            list.push_front(1);
+            list.pop_front();
+        });
+
+        std::thread t2([&list] {
+            list.push_front(2);
+            list.pop_front();
+        });
+
+        t1.join();
+        t2.join();
+
+        assert(list.size() == size);
+    }
+}
+
+int main()
+{
+    LinkedList<int> list;
+
+    // push & pop operations
+    list.push_front(10);
+    list.push_front(20);
+    auto a = list.front();
+    assert(a == 20);
+    list.pop_front();
+    a = list.front();
+    assert(a == 10);
+    list.pop_front();
+    assert(list.size() == 0);
+
+    // concurrent test
+    LinkedList<int> concurrent_list;
+    concurrent_list.push_front(1);
+    concurrent_list.push_front(1);
+    std::list<int> no_concurrent_list;
+    no_concurrent_list.push_front(1);
+    no_concurrent_list.push_front(1);
+    
+    test_concurrent_list_access(concurrent_list, 2);
+    // test_concurrent_list_access(no_concurrent_list, 2);
+
+    return 0;
+}
diff --git a/tests/timer.cpp b/tests/concurrent/timer.cpp
similarity index 100%
rename from tests/timer.cpp
rename to tests/concurrent/timer.cpp
diff --git a/tests/cypher_traversal.cpp b/tests/unit/cypher_traversal.cpp
similarity index 100%
rename from tests/cypher_traversal.cpp
rename to tests/unit/cypher_traversal.cpp
diff --git a/tests/db_index.cpp b/tests/unit/db_index.cpp
similarity index 100%
rename from tests/db_index.cpp
rename to tests/unit/db_index.cpp
diff --git a/tests/dynamic_bitset.cpp b/tests/unit/dynamic_bitset.cpp
similarity index 100%
rename from tests/dynamic_bitset.cpp
rename to tests/unit/dynamic_bitset.cpp
diff --git a/tests/lockfree_hashmap.cpp b/tests/unit/lockfree_hashmap.cpp
similarity index 100%
rename from tests/lockfree_hashmap.cpp
rename to tests/unit/lockfree_hashmap.cpp
diff --git a/tests/skiplist.cpp b/tests/unit/skiplist.cpp
similarity index 100%
rename from tests/skiplist.cpp
rename to tests/unit/skiplist.cpp