Skip to content
Snippets Groups Projects
Commit 2fd0bc86 authored by Nat Goodspeed's avatar Nat Goodspeed
Browse files

Change llprocesslauncher_test.cpp eyeballing to program verification.

That is, where before we just flung stuff to stdout with the expectation that
a human user would verify, replace with assertions in the test code itself.
Quiet previous noise on stdout.
Introduce a temp script file that produces output on both stdout and stderr,
with sleep() calls so we predictably have to wait for it. Track and then
verify the history of our interaction with the child process, noting
especially EWOULDBLOCK attempts.
parent 7832d8ec
No related branches found
No related tags found
No related merge requests found
......@@ -17,6 +17,7 @@
// STL headers
#include <vector>
// std headers
#include <errno.h>
// external library headers
#include "llapr.h"
#include "apr_thread_proc.h"
......@@ -71,11 +72,53 @@ namespace tut
typedef llprocesslauncher_group::object object;
llprocesslauncher_group llprocesslaunchergrp("llprocesslauncher");
struct Item
{
Item(): tries(0) {}
unsigned tries;
std::string which;
std::string what;
};
template<> template<>
void object::test<1>()
{
set_test_name("raw APR nonblocking I/O");
// Create a script file in a temporary place.
const char* tempdir = NULL;
aprchk(apr_temp_dir_get(&tempdir, apr.pool));
// Construct a temp filename template in that directory.
char *tempname = NULL;
aprchk(apr_filepath_merge(&tempname, tempdir, "testXXXXXX", 0, apr.pool));
// Create a temp file from that template.
apr_file_t* fp = NULL;
aprchk(apr_file_mktemp(&fp, tempname, APR_CREATE | APR_WRITE | APR_EXCL, apr.pool));
// Write it.
const char script[] =
"import sys\n"
"import time\n"
"\n"
"time.sleep(2)\n"
"print >>sys.stdout, \"stdout after wait\"\n"
"sys.stdout.flush()\n"
"time.sleep(2)\n"
"print >>sys.stderr, \"stderr after wait\"\n"
"sys.stderr.flush()\n"
;
apr_size_t len(sizeof(script)-1);
aprchk(apr_file_write(fp, script, &len));
aprchk(apr_file_close(fp));
// Arrange to track the history of our interaction with child: what we
// fetched, which pipe it came from, how many tries it took before we
// got it.
std::vector<Item> history;
history.push_back(Item());
apr_procattr_t *procattr = NULL;
aprchk(apr_procattr_create(&procattr, apr.pool));
aprchk(apr_procattr_io_set(procattr, APR_CHILD_BLOCK, APR_CHILD_BLOCK, APR_CHILD_BLOCK));
......@@ -84,8 +127,7 @@ namespace tut
std::vector<const char*> argv;
apr_proc_t child;
argv.push_back("python");
argv.push_back("-c");
argv.push_back("raise RuntimeError('Hello from Python!')");
argv.push_back(tempname);
argv.push_back(NULL);
aprchk(apr_proc_create(&child, argv[0],
......@@ -110,24 +152,35 @@ namespace tut
apr_status_t rv = apr_file_gets(buf, sizeof(buf), iterfiles[i].second);
if (APR_STATUS_IS_EOF(rv))
{
std::cout << "(EOF on " << iterfiles[i].first << ")\n";
// std::cout << "(EOF on " << iterfiles[i].first << ")\n";
history.back().which = iterfiles[i].first;
history.back().what = "*eof*";
history.push_back(Item());
outfiles.erase(outfiles.begin() + i);
continue;
}
if (rv != APR_SUCCESS)
if (rv == EWOULDBLOCK)
{
std::cout << "(waiting; apr_file_gets(" << iterfiles[i].first << ") => " << rv << ": " << apr.strerror(rv) << ")\n";
// std::cout << "(waiting; apr_file_gets(" << iterfiles[i].first << ") => " << rv << ": " << apr.strerror(rv) << ")\n";
++history.back().tries;
continue;
}
ensure_equals(rv, APR_SUCCESS);
// Is it even possible to get APR_SUCCESS but read 0 bytes?
// Hope not, but defend against that anyway.
if (buf[0])
{
std::cout << iterfiles[i].first << ": " << buf;
// Just for pretty output... if we only read a partial
// line, terminate it.
if (buf[strlen(buf) - 1] != '\n')
std::cout << "...\n";
// std::cout << iterfiles[i].first << ": " << buf;
history.back().which = iterfiles[i].first;
history.back().what.append(buf);
if (buf[strlen(buf) - 1] == '\n')
history.push_back(Item());
else
{
// Just for pretty output... if we only read a partial
// line, terminate it.
// std::cout << "...\n";
}
}
}
sleep(1);
......@@ -141,9 +194,29 @@ namespace tut
apr_status_t rv;
while (! APR_STATUS_IS_CHILD_DONE(rv = apr_proc_wait(&child, &rc, &why, APR_NOWAIT)))
{
std::cout << "child not done (" << rv << "): " << apr.strerror(rv) << '\n';
// std::cout << "child not done (" << rv << "): " << apr.strerror(rv) << '\n';
sleep(0.5);
}
std::cout << "child done: rv = " << rv << " (" << apr.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n';
// std::cout << "child done: rv = " << rv << " (" << apr.strerror(rv) << "), why = " << why << ", rc = " << rc << '\n';
ensure_equals(rv, APR_CHILD_DONE);
ensure_equals(why, APR_PROC_EXIT);
ensure_equals(rc, 0);
// Remove temp script file
aprchk(apr_file_remove(tempname, apr.pool));
// Beyond merely executing all the above successfully, verify that we
// obtained expected output -- and that we duly got control while
// waiting, proving the non-blocking nature of these pipes.
ensure("blocking I/O on child pipe (0)", history[0].tries);
ensure_equals(history[0].which, "out");
ensure_equals(history[0].what, "stdout after wait\n");
ensure("blocking I/O on child pipe (1)", history[1].tries);
ensure_equals(history[1].which, "out");
ensure_equals(history[1].what, "*eof*");
ensure_equals(history[2].which, "err");
ensure_equals(history[2].what, "stderr after wait\n");
ensure_equals(history[3].which, "err");
ensure_equals(history[3].what, "*eof*");
}
} // namespace tut
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment