notcurses/tests/fds.cpp
Nick Black 415d4b813f
Tight work on unit testing, controlling tty vs output fd (#758)
* Some things go to the FILE* we're provided. Some can only go to a controlling terminal. Check to see if the FILE we're given is a TTY. If not, open up /dev/tty #752.
* term_emit(): fflush() can return EAGAIN. Loop on it to eliminate a rare error on shutdown that particularly affected unit tests (where we start and shut down Notcurses many times in a row).
* sgr poc: check return value of setlocale()
* drone: run all unit tests
* CMake: add some tests using PoCs
* ncneofetch: print even small palettes
2020-07-02 18:03:52 -04:00

164 lines
4.8 KiB
C++

#include "main.h"
#include <cerrno>
#include <mutex>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include "internal.h"
#include <condition_variable>
std::mutex lock;
std::condition_variable cond;
bool inline_cancelled = false;
auto testfdcb(struct ncfdplane* ncfd, const void* buf, size_t s, void* curry) -> int {
struct ncplane* n = ncfdplane_plane(ncfd);
lock.lock();
if(ncplane_putstr(n, static_cast<const char*>(buf)) <= 0){
lock.unlock();
return -1;
}
notcurses_render(ncplane_notcurses(ncfdplane_plane(ncfd)));
lock.unlock();
(void)curry;
(void)s;
return 0;
}
auto testfdeof(struct ncfdplane* n, int fderrno, void* curry) -> int {
bool* outofline_cancelled = static_cast<bool*>(curry);
lock.lock();
*outofline_cancelled = true;
lock.unlock();
cond.notify_one();
(void)n;
(void)fderrno;
return 0;
}
auto testfdeofdestroys(struct ncfdplane* n, int fderrno, void* curry) -> int {
lock.lock();
inline_cancelled = true;
int ret = ncfdplane_destroy(n);
lock.unlock();
cond.notify_one();
(void)curry;
(void)fderrno;
return ret;
}
// test ncfdplanes and ncsubprocs
TEST_CASE("FdsAndSubprocs"
* doctest::description("Fdplanes and subprocedures")) {
auto nc_ = testing_notcurses();
if(!nc_){
return;
}
struct ncplane* n_ = notcurses_stdplane(nc_);
REQUIRE(n_);
REQUIRE(0 == ncplane_cursor_move_yx(n_, 0, 0));
// destroy the ncfdplane outside of its own context
SUBCASE("FdPlaneDestroyOffline") {
bool outofline_cancelled = false;
ncfdplane_options opts{};
opts.curry = &outofline_cancelled;
int fd = open("/dev/null", O_RDONLY|O_CLOEXEC);
REQUIRE(0 <= fd);
auto ncfdp = ncfdplane_create(n_, &opts, fd, testfdcb, testfdeof);
REQUIRE(ncfdp);
std::unique_lock<std::mutex> lck(lock);
CHECK(0 == notcurses_render(nc_));
while(!outofline_cancelled){
cond.wait(lck);
}
CHECK(0 == ncfdplane_destroy(ncfdp));
CHECK(0 == notcurses_render(nc_));
lock.unlock();
}
// destroy the ncfdplane within its own context, i.e. from the eof callback
SUBCASE("FdPlaneDestroyInline") {
inline_cancelled = false;
ncfdplane_options opts{};
opts.curry = n_;
int fd = open("/dev/null", O_RDONLY|O_CLOEXEC);
REQUIRE(0 <= fd);
auto ncfdp = ncfdplane_create(n_, &opts, fd, testfdcb, testfdeofdestroys);
REQUIRE(ncfdp);
std::unique_lock<std::mutex> lck(lock);
CHECK(0 == notcurses_render(nc_));
while(!inline_cancelled){
cond.wait(lck);
}
CHECK(0 == notcurses_render(nc_));
lock.unlock();
}
SUBCASE("SubprocDestroyCmdExecFails") {
char * const argv[] = { strdup("/should-not-exist"), nullptr, };
bool outofline_cancelled = false;
ncsubproc_options opts{};
opts.curry = &outofline_cancelled;
auto ncsubp = ncsubproc_createvp(n_, &opts, argv[0], argv, testfdcb, testfdeof);
REQUIRE(ncsubp);
std::unique_lock<std::mutex> lck(lock);
CHECK(0 == notcurses_render(nc_));
while(!outofline_cancelled){
cond.wait(lck);
}
CHECK(0 != ncsubproc_destroy(ncsubp));
// FIXME we ought get indication of an error here! or via callback...
CHECK(0 == notcurses_render(nc_));
lock.unlock();
}
SUBCASE("SubprocDestroyCmdSucceeds") {
char * const argv[] = { strdup("/bin/cat"), strdup("/dev/null"), nullptr, };
bool outofline_cancelled = false;
ncsubproc_options opts{};
opts.curry = &outofline_cancelled;
auto ncsubp = ncsubproc_createvp(n_, &opts, argv[0], argv, testfdcb, testfdeof);
REQUIRE(ncsubp);
std::unique_lock<std::mutex> lck(lock);
CHECK(0 == notcurses_render(nc_));
while(!outofline_cancelled){
cond.wait(lck);
}
CHECK(0 == ncsubproc_destroy(ncsubp));
CHECK(0 == notcurses_render(nc_));
lock.unlock();
}
SUBCASE("SubprocDestroyCmdFailed") {
char * const argv[] = { strdup("/bin/cat"), strdup("/dev/nope"), nullptr, };
bool outofline_cancelled = false;
ncsubproc_options opts{};
opts.curry = &outofline_cancelled;
auto ncsubp = ncsubproc_createvp(n_, &opts, argv[0], argv, testfdcb, testfdeof);
REQUIRE(ncsubp);
std::unique_lock<std::mutex> lck(lock);
CHECK(0 == notcurses_render(nc_));
while(!outofline_cancelled){
cond.wait(lck);
}
CHECK(0 != ncsubproc_destroy(ncsubp));
CHECK(0 == notcurses_render(nc_));
lock.unlock();
}
SUBCASE("SubprocDestroyCmdHung") {
char * const argv[] = { strdup("/bin/cat"), nullptr, };
bool outofline_cancelled = false;
ncsubproc_options opts{};
opts.curry = &outofline_cancelled;
auto ncsubp = ncsubproc_createvp(n_, &opts, argv[0], argv, testfdcb, testfdeof);
REQUIRE(ncsubp);
// FIXME ought be CHECK, breaking in drone
WARN(0 != ncsubproc_destroy(ncsubp));
CHECK(0 == notcurses_render(nc_));
}
CHECK(0 == notcurses_stop(nc_));
}