From 2ba8beac6e6f3fcaebc2080d63de58058c57e105 Mon Sep 17 00:00:00 2001 From: Davin McCall Date: Fri, 6 Jul 2018 18:19:57 +0100 Subject: [PATCH] Add initial control protocol test. This includes a lot of framework necessary for writing additional tests. --- .gitignore | 13 ++-- src/control.cc | 8 +-- src/includes/baseproc-sys.h | 2 + src/includes/control.h | 11 ++- src/includes/cpbuffer.h | 7 +- src/tests/Makefile | 2 + src/tests/cptests/Makefile | 33 +++++++++ src/tests/cptests/cptests.cc | 41 +++++++++++ src/tests/test-bpsys.cc | 95 ++++++++++++++++++++++++-- src/tests/test-includes/baseproc-sys.h | 16 +++++ src/tests/test-includes/dinit.h | 43 ++++++++++++ 11 files changed, 248 insertions(+), 23 deletions(-) create mode 100644 src/tests/cptests/Makefile create mode 100644 src/tests/cptests/cptests.cc diff --git a/.gitignore b/.gitignore index 9d145ba..725676e 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/src/control.cc b/src/control.cc index dfb9a1d..09a1b6b 100644 --- a/src/control.cc +++ b/src/control.cc @@ -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 &&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 & 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 diff --git a/src/includes/baseproc-sys.h b/src/includes/baseproc-sys.h index 209ad2a..14166be 100644 --- a/src/includes/baseproc-sys.h +++ b/src/includes/baseproc-sys.h @@ -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 diff --git a/src/includes/control.h b/src/includes/control.h index 7e8dfb8..c9d981e 100644 --- a/src/includes/control.h +++ b/src/includes/control.h @@ -46,7 +46,10 @@ class service_record; class control_conn_watcher : public eventloop_t::bidi_fd_watcher_impl { - 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 +#include + +#include "baseproc-sys.h" // control protocol buffer, a circular buffer with fixed capacity. template class cpbuffer @@ -53,7 +56,7 @@ template 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 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; } diff --git a/src/tests/Makefile b/src/tests/Makefile index 00aa81c..2836564 100644 --- a/src/tests/Makefile +++ b/src/tests/Makefile @@ -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 index 0000000..1795a65 --- /dev/null +++ b/src/tests/cptests/Makefile @@ -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 index 0000000..01dc9c6 --- /dev/null +++ b/src/tests/cptests/cptests.cc @@ -0,0 +1,41 @@ +#include +#include + +#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 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; +} diff --git a/src/tests/test-bpsys.cc b/src/tests/test-bpsys.cc index 30df33f..4014ac1 100644 --- a/src/tests/test-bpsys.cc +++ b/src/tests/test-bpsys.cc @@ -1,15 +1,44 @@ #include #include #include +#include #include #include "baseproc-sys.h" -static std::vector usedfds = {true, true, true}; +namespace { + +std::vector usedfds = {true, true, true}; + +struct read_result +{ + read_result(int errcode_p) : errcode(errcode_p) {} + + read_result(std::vector &data_p) : errcode(0), data(data_p) {} + read_result(std::vector &&data_p) : errcode(0), data(std::move(data_p)) {} + + int errcode; // errno return + std::vector data; // data (if errcode == 0) +}; + +// map of fd to read results to supply for reads of that fd +std::map> read_data; + +// map of data written to each fd +std::map> 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 &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 &&data) +{ + read_data[fd].emplace_back(std::move(data)); +} + +// retrieve data written via write() +void extract_written_data(int fd, std::vector &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 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 &wd = written_data[fd]; + wd.insert(wd.end(), (char *)buf, (char *)buf + count); + return count; +} + } diff --git a/src/tests/test-includes/baseproc-sys.h b/src/tests/test-includes/baseproc-sys.h index af1442c..4376ae1 100644 --- a/src/tests/test-includes/baseproc-sys.h +++ b/src/tests/test-includes/baseproc-sys.h @@ -2,6 +2,8 @@ #define BPSYS_INCLUDED #include +#include + #include #include @@ -9,6 +11,17 @@ namespace bp_sys { +// Test helper functions: + +// allocate a file descriptor +int allocfd(); + +void supply_read_data(int fd, std::vector &data); +void supply_read_data(int fd, std::vector &&data); +void extract_written_data(int fd, std::vector &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 diff --git a/src/tests/test-includes/dinit.h b/src/tests/test-includes/dinit.h index 65bf4f1..c41c227 100644 --- a/src/tests/test-includes/dinit.h +++ b/src/tests/test-includes/dinit.h @@ -4,6 +4,7 @@ // dummy dinit.h #include +#include #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 class bidi_fd_watcher_impl : public bidi_fd_watcher + { + + }; + class timer { public: @@ -119,6 +161,7 @@ class eventloop_t }; std::unordered_set active_timers; + std::map regd_bidi_watchers; }; inline void open_control_socket(bool report_ro_failure = true) noexcept -- 2.25.1