This includes a lot of framework necessary for writing additional tests.
*.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
// 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;
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;
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
using ::getpgid;
using ::tcsetpgrp;
using ::getpgrp;
+using ::read;
+using ::write;
// Wrapper around a POSIX exit status
class exit_status
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;
}
};
-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);
#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
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;
}
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;
}
./tests
./proctests
./loadtests
+ $(MAKE) -C cptests check
build-tests: prepare-incdir tests proctests loadtests
$(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)
--- /dev/null
+-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)
--- /dev/null
+#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;
+}
#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()) {
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)
{
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;
+}
+
}
#define BPSYS_INCLUDED
#include <string>
+#include <vector>
+
#include <sys/types.h>
#include <unistd.h>
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);
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
// dummy dinit.h
#include <unordered_set>
+#include <map>
#include "dasynq.h"
};
+ 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:
};
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