/*
* tests/atoms/parallel/ParallelUTest.cxxtest
*
* Copyright (C) 2015 Linas Vepstas
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License v3 as
* published by the Free Software Foundation and including the exceptions
* at http://opencog.org/wiki/Licenses
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, write to:
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <time.h>
#include <sys/time.h>
#include <opencog/guile/SchemeEval.h>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/util/Logger.h>
#ifndef __APPLE__
#define ATOM_SLEEP(x,y) clock_nanosleep(CLOCK_REALTIME, 0, x, y)
#else
#define ATOM_SLEEP(x,y) nanosleep(x, y)
#endif
using namespace opencog;
class ParallelUTest: public CxxTest::TestSuite
{
private:
AtomSpacePtr as;
SchemeEval* eval;
int robustSleep(int secs);
public:
ParallelUTest(void)
{
logger().set_level(Logger::DEBUG);
logger().set_print_to_stdout_flag(true);
as = createAtomSpace();
eval = SchemeEval::get_scheme_evaluator(as);
eval->eval("(add-to-load-path \"" PROJECT_SOURCE_DIR "\")");
}
~ParallelUTest()
{
// Erase the log file if no assertions failed.
if (!CxxTest::TestTracker::tracker().suiteFailed())
std::remove(logger().get_filename().c_str());
}
void setUp(void);
void tearDown(void);
void test_parallel(void);
void test_join(void);
void test_throw(void);
};
void ParallelUTest::tearDown(void)
{
as->clear();
}
void ParallelUTest::setUp(void)
{
as->clear();
}
/*
* Use clock_nanosleep() to repeatedly attempt to sleep until the desired
* number of seconds is past, in spite of signal interrupts.
*/
int ParallelUTest::robustSleep(int secs)
{
struct timespec request, remain;
int e;
request.tv_sec = secs;
request.tv_nsec = 0;
struct timeval tv;
gettimeofday(&tv, nullptr);
double start = tv.tv_sec + 1.0e-6 * tv.tv_usec;
while ((e = ATOM_SLEEP(&request, &remain)) != 0) {
gettimeofday(&tv, nullptr);
double now = tv.tv_sec + 1.0e-6 * tv.tv_usec;
printf("Slept for %.4f secs so far\n", now - start);
if (e == EINTR) {
printf("Interrupt. %ld.%09ld secs remain\n", remain.tv_sec, remain.tv_nsec);
request = remain;
}
else {
return e;
}
}
return e;
}
/*
* ParallelLink unit test.
*/
void ParallelUTest::test_parallel(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
eval->eval("(load-from-path \"tests/atoms/parallel/parallel.scm\")");
// The scheme variable nnn should increment by one after 1, 3 and 5
// seconds. If we check at 0, 2, 4, 6 seconds, we should get a clear
// eye for the measurement. Note that, on a busy system, these
// timing windows can fail, if the OS muffs the sleeps. Which does
// seem to happen; see bug #517 on github.
eval->eval_v("(cog-execute! pllel)");
struct timeval tv;
gettimeofday(&tv, nullptr);
double start = tv.tv_sec + 1.0e-6 * tv.tv_usec;
std::string str = eval->eval("nnn");
printf("0-count = %s\n", str.c_str());
int cnt = atoi(str.c_str());
TS_ASSERT_EQUALS(cnt, 0);
int e = robustSleep(2);
gettimeofday(&tv, nullptr);
double now = tv.tv_sec + 1.0e-6 * tv.tv_usec;
printf("After first sleep (exit %d) %.4f secs past\n", e, now - start);
str = eval->eval("nnn");
printf("1-count = %s\n", str.c_str());
cnt = atoi(str.c_str());
TS_ASSERT_EQUALS(cnt, 1);
e = robustSleep(2);
gettimeofday(&tv, nullptr);
now = tv.tv_sec + 1.0e-6 * tv.tv_usec;
printf("After second sleep (exit %d) %.4f secs past\n", e, now - start);
str = eval->eval("nnn");
printf("2-count = %s\n", str.c_str());
cnt = atoi(str.c_str());
TS_ASSERT_EQUALS(cnt, 2);
e = robustSleep(2);
gettimeofday(&tv, nullptr);
now = tv.tv_sec + 1.0e-6 * tv.tv_usec;
printf("After third sleep (exit %d) %.4f secs past\n", e, now - start);
str = eval->eval("nnn");
printf("3-count = %s\n", str.c_str());
cnt = atoi(str.c_str());
TS_ASSERT_EQUALS(cnt, 3);
logger().debug("END TEST: %s", __FUNCTION__);
}
/*
* ExecuteThreadedLink unit test.
*/
void ParallelUTest::test_join(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
eval->eval("(load-from-path \"tests/atoms/parallel/parallel.scm\")");
eval->eval("(set! nnn 0)");
struct timeval tv;
gettimeofday(&tv, nullptr);
double start = tv.tv_sec + 1.0e-6 * tv.tv_usec;
// This should take exactly 5 seconds to run.
// In particular, this thread should be blocked for 5 seconds.
eval->eval("(cog-value->list (cog-execute! wait))");
gettimeofday(&tv, nullptr);
double end = tv.tv_sec + 1.0e-6 * tv.tv_usec;
double elapsed = end - start;
printf("elapsed time = %f seconds\n", elapsed);
// This would rarely fail with a more strict check, so it's been
// relaxed for now.
TS_ASSERT_LESS_THAN(elapsed, 5.5);
TS_ASSERT_LESS_THAN(4.5, elapsed);
// The scheme variable nnn should get incremented twice (not three
// times); the middle SequentialAnd has a False in it, so the middle
// increment should not happen.
std::string str = eval->eval("nnn");
printf("2-count = %s\n", str.c_str());
int cnt = atoi(str.c_str());
TS_ASSERT_EQUALS(cnt, 2);
logger().debug("END TEST: %s", __FUNCTION__);
}
/*
* ParallelLink, ExecuteThreadedLink exceptions
*/
void ParallelUTest::test_throw(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
eval->eval("(load-from-path \"tests/atoms/parallel/parallel.scm\")");
// Well, these are running in distinct threads. There's not
// really any way to capture exceptions thrown in other threads,
// and re-route them to here. So this is .. I dunno, not quite
// a good situation, but ... whatever. Not sure why we are even
// testing this ...
TS_ASSERT_THROWS_NOTHING(eval->eval_v("(cog-execute! pllel-bad)"));
TS_ASSERT_EQUALS(false, eval->eval_error());
TS_ASSERT_THROWS_NOTHING(eval->eval_v("(cog-execute! wait-bad)"));
TS_ASSERT_EQUALS(false, eval->eval_error());
// Let everything settle down, here.
robustSleep(1);
logger().debug("END TEST: %s", __FUNCTION__);
}