Add initial control protocol test.
authorDavin McCall <davmac@davmac.org>
Fri, 6 Jul 2018 17:19:57 +0000 (18:19 +0100)
committerDavin McCall <davmac@davmac.org>
Sat, 7 Jul 2018 16:51:46 +0000 (17:51 +0100)
This includes a lot of framework necessary for writing additional tests.

.gitignore
src/control.cc
src/includes/baseproc-sys.h
src/includes/control.h
src/includes/cpbuffer.h
src/tests/Makefile
src/tests/cptests/Makefile [new file with mode: 0644]
src/tests/cptests/cptests.cc [new file with mode: 0644]
src/tests/test-bpsys.cc
src/tests/test-includes/baseproc-sys.h
src/tests/test-includes/dinit.h

index 9d145ba8304daa65a7b081e191cdb133322e005c..725676e212a328f2562355cc912466c1777530f3 100644 (file)
@@ -1,11 +1,14 @@
 *.o
 *.d
 .*
-dinit
-dinitctl
-test
-shutdown
-dinit-reboot
+src/dinit
+src/dinitctl
+src/shutdown
+src/mconfig-gen
+src/includes/mconfig.h
 src/tests/tests
 src/tests/proctests
+src/tests/loadtests
 src/tests/includes
+src/tests/cptests/includes
+src/tests/cptests/cptests
index dfb9a1d15c8edd72220093beb2f536ed9f09ad62..09a1b6b74198e9e24370cb06fd96bf70bc83f2e4 100644 (file)
@@ -582,7 +582,7 @@ bool control_conn_t::queue_packet(const char *pkt, unsigned size) noexcept
     // If the queue is empty, we can try to write the packet out now rather than queueing it.
     // If the write is unsuccessful or partial, we queue the remainder.
     if (was_empty) {
-        int wr = write(iob.get_watched_fd(), pkt, size);
+        int wr = bp_sys::write(iob.get_watched_fd(), pkt, size);
         if (wr == -1) {
             if (errno == EPIPE) {
                 return false;
@@ -637,7 +637,7 @@ bool control_conn_t::queue_packet(std::vector<char> &&pkt) noexcept
     if (was_empty) {
         outpkt_index = 0;
         // We can try sending the packet immediately:
-        int wr = write(iob.get_watched_fd(), pkt.data(), pkt.size());
+        int wr = bp_sys::write(iob.get_watched_fd(), pkt.data(), pkt.size());
         if (wr == -1) {
             if (errno == EPIPE) {
                 return false;
@@ -735,14 +735,14 @@ bool control_conn_t::send_data() noexcept
         if (oom_close) {
             // Send oom response
             char oomBuf[] = { DINIT_RP_OOM };
-            write(iob.get_watched_fd(), oomBuf, 1);
+            bp_sys::write(iob.get_watched_fd(), oomBuf, 1);
         }
         return true;
     }
     
     vector<char> & pkt = outbuf.front();
     char *data = pkt.data();
-    int written = write(iob.get_watched_fd(), data + outpkt_index, pkt.size() - outpkt_index);
+    int written = bp_sys::write(iob.get_watched_fd(), data + outpkt_index, pkt.size() - outpkt_index);
     if (written == -1) {
         if (errno == EPIPE) {
             // read end closed
index 209ad2aeac7a893b8b4478ae6daff9dfc905385c..14166bee275b2994dc8470655cb776cfdbb1d814 100644 (file)
@@ -20,6 +20,8 @@ using ::kill;
 using ::getpgid;
 using ::tcsetpgrp;
 using ::getpgrp;
+using ::read;
+using ::write;
 
 // Wrapper around a POSIX exit status
 class exit_status
index 7e8dfb883bb68815feb0b2966f2139cd85f9facb..c9d981e38fa3f7e0ad20d785f06dd1f06f107f2b 100644 (file)
@@ -46,7 +46,10 @@ class service_record;
 
 class control_conn_watcher : public eventloop_t::bidi_fd_watcher_impl<control_conn_watcher>
 {
-    inline rearm receive_event(eventloop_t &loop, int fd, int flags) noexcept;
+    inline rearm receive_event(eventloop_t &loop, int fd, int flags) noexcept
+    {
+        return control_conn_cb(&loop, this, flags);
+    }
 
     eventloop_t * event_loop;
 
@@ -72,12 +75,6 @@ class control_conn_watcher : public eventloop_t::bidi_fd_watcher_impl<control_co
     }
 };
 
-inline dasynq::rearm control_conn_watcher::receive_event(eventloop_t &loop, int fd, int flags) noexcept
-{
-    return control_conn_cb(&loop, this, flags);
-}
-
-
 class control_conn_t : private service_listener
 {
     friend rearm control_conn_cb(eventloop_t *loop, control_conn_watcher *watcher, int revents);
index 449b4bab107c34ba8b7388013ebe894ecb1a444a..ac9a22c7f5caeb5f3ff901caf334211864767b29 100644 (file)
@@ -2,6 +2,9 @@
 #define CPBUFFER_H
 
 #include <cstring>
+#include <algorithm>
+
+#include "baseproc-sys.h"
 
 // control protocol buffer, a circular buffer with fixed capacity.
 template <int SIZE> class cpbuffer
@@ -53,7 +56,7 @@ template <int SIZE> class cpbuffer
         int pos = cur_idx + length;
         if (pos >= SIZE) pos -= SIZE;
         int max_count = std::min(SIZE - pos, SIZE - length);
-        ssize_t r = read(fd, buf + pos, max_count);
+        ssize_t r = bp_sys::read(fd, buf + pos, max_count);
         if (r >= 0) {
             length += r;
         }
@@ -68,7 +71,7 @@ template <int SIZE> class cpbuffer
         if (pos >= SIZE) pos -= SIZE;
         int max_count = std::min(SIZE - pos, SIZE - length);
         max_count = std::min(max_count, limit);
-        ssize_t r = read(fd, buf + pos, max_count);
+        ssize_t r = bp_sys::read(fd, buf + pos, max_count);
         if (r >= 0) {
             length += r;
         }
index 00aa81c5805cdd85276357e308ba0dc8117844ee..2836564aa18d0773c0105e4858aa483ee39d1e17 100644 (file)
@@ -7,6 +7,7 @@ check: build-tests
        ./tests
        ./proctests
        ./loadtests
+       $(MAKE) -C cptests check
 
 build-tests: prepare-incdir tests proctests loadtests
 
@@ -33,6 +34,7 @@ $(parent_objs): %.o: ../%.cc
        $(CXX) $(CXXOPTS) $(SANITIZEOPTS) -MMD -MP -Iincludes -I../dasynq -c $< -o $@
 
 clean:
+       $(MAKE) -C cptests clean
        rm -f *.o *.d tests proctests loadtests
 
 -include $(objects:.o=.d)
diff --git a/src/tests/cptests/Makefile b/src/tests/cptests/Makefile
new file mode 100644 (file)
index 0000000..1795a65
--- /dev/null
@@ -0,0 +1,33 @@
+-include ../../../mconfig
+
+objects = cptests.o
+parent_test_objects = ../test-bpsys.o ../test-dinit.o
+parent_objs = control.o dinit-log.o service.o
+
+check: build-tests
+       ./cptests
+
+build-tests: prepare-incdir cptests
+
+# Create an "includes" directory populated with a combination of real and mock headers:
+prepare-incdir:
+       mkdir -p includes
+       rm -rf includes/*.h
+       cd includes; ln -f ../../../includes/*.h .
+       cd includes; ln -f ../../test-includes/dinit.h .
+       cd includes; ln -f ../../test-includes/baseproc-sys.h .
+
+cptests: cptests.o $(parent_objs)
+       $(CXX) $(SANITIZEOPTS) -o cptests cptests.o $(parent_test_objects) $(parent_objs) $(LDFLAGS)
+
+$(objects): %.o: %.cc
+       $(CXX) $(CXXOPTS) $(SANITIZEOPTS) -MMD -MP -Iincludes -I../../dasynq -c $< -o $@
+
+$(parent_objs): %.o: ../../%.cc
+       $(CXX) $(CXXOPTS) $(SANITIZEOPTS) -MMD -MP -Iincludes -I../../dasynq -c $< -o $@
+
+clean:
+       rm -f *.o *.d cptests
+
+-include $(objects:.o=.d)
+-include $(parent_objects:.o=.d)
diff --git a/src/tests/cptests/cptests.cc b/src/tests/cptests/cptests.cc
new file mode 100644 (file)
index 0000000..01dc9c6
--- /dev/null
@@ -0,0 +1,41 @@
+#include <cassert>
+#include <iostream>
+
+#include "dinit.h"
+#include "service.h"
+#include "baseproc-sys.h"
+#include "control.h"
+
+#define RUN_TEST(name) \
+    std::cout << #name "... "; \
+    name(); \
+    std::cout << "PASSED" << std::endl;
+
+void cptest1()
+{
+       service_set sset;
+       int fd = bp_sys::allocfd();
+       auto *cc = new control_conn_t(event_loop, &sset, fd);
+
+       bp_sys::supply_read_data(fd, { DINIT_CP_QUERYVERSION });
+
+       event_loop.regd_bidi_watchers[fd]->read_ready(event_loop, fd);
+
+       // Write will process immediately, so there's no need for this:
+       //event_loop.regd_bidi_watchers[fd]->write_ready(event_loop, fd);
+
+       // We expect a version number back:
+       std::vector<char> wdata;
+       bp_sys::extract_written_data(fd, wdata);
+
+       assert(wdata.size() == 5);
+       assert(wdata[0] == DINIT_RP_CPVERSION);
+
+       delete cc;
+}
+
+int main(int argc, char **argv)
+{
+    RUN_TEST(cptest1);
+    return 0;
+}
index 30df33fac5f797b3246f50a474b230e279440f98..4014ac1de37a8c2922dc1c5c24254676750316bc 100644 (file)
@@ -1,15 +1,44 @@
 #include <vector>
 #include <utility>
 #include <algorithm>
+#include <map>
 
 #include <cstdlib>
 
 #include "baseproc-sys.h"
 
-static std::vector<bool> usedfds = {true, true, true};
+namespace {
+
+std::vector<bool> usedfds = {true, true, true};
+
+struct read_result
+{
+       read_result(int errcode_p) : errcode(errcode_p) {}
+
+       read_result(std::vector<char> &data_p) : errcode(0), data(data_p) {}
+       read_result(std::vector<char> &&data_p) : errcode(0), data(std::move(data_p)) {}
+
+       int errcode; // errno return
+       std::vector<char> data;  // data (if errcode == 0)
+};
+
+// map of fd to read results to supply for reads of that fd
+std::map<int,std::vector<read_result>> read_data;
+
+// map of data written to each fd
+std::map<int,std::vector<char>> written_data;
+
+} // anon namespace
+
+namespace bp_sys {
+
+int last_sig_sent = -1; // last signal number sent, accessible for tests.
+pid_t last_forked_pid = 1;  // last forked process id (incremented each 'fork')
+
+// Test helper methods:
 
 // Allocate a file descriptor
-static int allocfd()
+int allocfd()
 {
     auto f = std::find(usedfds.begin(), usedfds.end(), false);
     if (f == usedfds.end()) {
@@ -22,10 +51,25 @@ static int allocfd()
     return f - usedfds.begin();
 }
 
-namespace bp_sys {
+// Supply data to be returned by read()
+void supply_read_data(int fd, std::vector<char> &data)
+{
+       read_data[fd].emplace_back(data);
+}
 
-int last_sig_sent = -1; // last signal number sent, accessible for tests.
-pid_t last_forked_pid = 1;  // last forked process id (incremented each 'fork')
+void supply_read_data(int fd, std::vector<char> &&data)
+{
+       read_data[fd].emplace_back(std::move(data));
+}
+
+// retrieve data written via write()
+void extract_written_data(int fd, std::vector<char> &data)
+{
+       data = std::move(written_data[fd]);
+}
+
+
+// Mock implementations of system calls:
 
 int pipe2(int fds[2], int flags)
 {
@@ -48,4 +92,45 @@ int kill(pid_t pid, int sig)
     return 0;
 }
 
+ssize_t read(int fd, void *buf, size_t count)
+{
+       std::vector<read_result> rrs = read_data[fd];
+       if (rrs.empty()) {
+               return 0;
+       }
+
+       read_result &rr = rrs.front();
+       if (rr.errcode != 0) {
+               errno = rr.errcode;
+               // Remove the result record:
+               auto i = rrs.begin();
+               i++;
+               rrs.erase(rrs.begin(), i);
+               return -1;
+       }
+
+       auto dsize = rr.data.size();
+       if (dsize <= count) {
+               // Consume entire result:
+               std::copy_n(rr.data.begin(), dsize, (char *)buf);
+               // Remove the result record:
+               auto i = rrs.begin();
+               i++;
+               rrs.erase(rrs.begin(), i);
+               return dsize;
+       }
+
+       // Consume partial result:
+       std::copy_n(rr.data.begin(), count, (char *)buf);
+       rr.data.erase(rr.data.begin(), rr.data.begin() + count);
+       return count;
+}
+
+ssize_t write(int fd, const void *buf, size_t count)
+{
+       std::vector<char> &wd = written_data[fd];
+       wd.insert(wd.end(), (char *)buf, (char *)buf + count);
+       return count;
+}
+
 }
index af1442c3b7bffec6009fe37bd26ea3d973c57ebb..4376ae19914b033dd9876e605396eaa7e0f14a07 100644 (file)
@@ -2,6 +2,8 @@
 #define BPSYS_INCLUDED
 
 #include <string>
+#include <vector>
+
 #include <sys/types.h>
 #include <unistd.h>
 
@@ -9,6 +11,17 @@
 
 namespace bp_sys {
 
+// Test helper functions:
+
+// allocate a file descriptor
+int allocfd();
+
+void supply_read_data(int fd, std::vector<char> &data);
+void supply_read_data(int fd, std::vector<char> &&data);
+void extract_written_data(int fd, std::vector<char> &data);
+
+// Mock system calls:
+
 // implementations elsewhere:
 int pipe2(int pipefd[2], int flags);
 int close(int fd);
@@ -92,6 +105,9 @@ inline pid_t waitpid(pid_t p, exit_status *statusp, int flags)
     throw std::string("not implemented");
 }
 
+ssize_t read(int fd, void *buf, size_t count);
+ssize_t write(int fd, const void *buf, size_t count);
+
 }
 
 #endif
index 65bf4f1e7003cfcd93d60521860f01f623d79bb5..c41c227388dfc6ac1d36697d9c38349de6cfcf0e 100644 (file)
@@ -4,6 +4,7 @@
 // dummy dinit.h
 
 #include <unordered_set>
+#include <map>
 
 #include "dasynq.h"
 
@@ -89,6 +90,47 @@ class eventloop_t
 
     };
 
+    class bidi_fd_watcher
+    {
+       int watched_fd = -1;
+
+       public:
+       void set_watches(eventloop_t &eloop, int newFlags) noexcept
+       {
+
+       }
+
+       void add_watch(eventloop_t &eloop, int fd, int flags, int inprio = dasynq::DEFAULT_PRIORITY,
+                       int outprio = dasynq::DEFAULT_PRIORITY)
+       {
+               if (eloop.regd_bidi_watchers.find(fd) != eloop.regd_bidi_watchers.end()) {
+                       throw std::string("must not add_watch when already active");
+               }
+               eloop.regd_bidi_watchers[fd] = this;
+               watched_fd = fd;
+       }
+
+       int get_watched_fd() noexcept
+       {
+               return watched_fd;
+       }
+
+       void deregister(eventloop_t &eloop) noexcept
+       {
+               eloop.regd_bidi_watchers.erase(watched_fd);
+               watched_fd = -1;
+       }
+
+       // In the real implementation these are not virtual, but it is easier for testing if they are:
+       virtual rearm read_ready(eventloop_t &loop, int fd) noexcept = 0;
+       virtual rearm write_ready(eventloop_t &loop, int fd) noexcept = 0;
+    };
+
+    template <typename Derived> class bidi_fd_watcher_impl : public bidi_fd_watcher
+       {
+
+       };
+
     class timer
     {
         public:
@@ -119,6 +161,7 @@ class eventloop_t
     };
 
     std::unordered_set<timer *> active_timers;
+       std::map<int, bidi_fd_watcher *> regd_bidi_watchers;
 };
 
 inline void open_control_socket(bool report_ro_failure = true) noexcept