Brooks Davis b0d29bc47d Import the kyua test framework.
Having kyua in the base system will simplify automated testing in CI and
eliminates bootstrapping issues on new platforms.

The build of kyua is controlled by WITH(OUT)_TESTS_SUPPORT.

Reviewed by:	emaste
Obtained from:	CheriBSD
Sponsored by:	DARPA
Differential Revision:	https://reviews.freebsd.org/D24103
2020-03-23 19:01:23 +00:00

427 lines
13 KiB
C++

// Copyright 2010 The Kyua Authors.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of Google Inc. nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "utils/signals/timer.hpp"
extern "C" {
#include <signal.h>
#include <unistd.h>
}
#include <cstddef>
#include <iostream>
#include <vector>
#include <atf-c++.hpp>
#include "utils/datetime.hpp"
#include "utils/defs.hpp"
#include "utils/format/containers.ipp"
#include "utils/format/macros.hpp"
#include "utils/signals/interrupts.hpp"
#include "utils/signals/programmer.hpp"
namespace datetime = utils::datetime;
namespace signals = utils::signals;
namespace {
/// A timer that inserts an element into a vector on activation.
class delayed_inserter : public signals::timer {
/// Vector into which to insert the element.
std::vector< int >& _destination;
/// Element to insert into _destination on activation.
const int _item;
/// Timer activation callback.
void
callback(void)
{
signals::interrupts_inhibiter inhibiter;
_destination.push_back(_item);
}
public:
/// Constructor.
///
/// \param delta Time to the timer activation.
/// \param destination Vector into which to insert the element.
/// \param item Element to insert into destination on activation.
delayed_inserter(const datetime::delta& delta,
std::vector< int >& destination, const int item) :
signals::timer(delta), _destination(destination), _item(item)
{
}
};
/// Signal handler that does nothing.
static void
null_handler(const int /* signo */)
{
}
/// Waits for the activation of all given timers.
///
/// \param timers Pointers to all the timers to wait for.
static void
wait_timers(const std::vector< signals::timer* >& timers)
{
std::size_t n_fired, old_n_fired = 0;
do {
n_fired = 0;
for (std::vector< signals::timer* >::const_iterator
iter = timers.begin(); iter != timers.end(); ++iter) {
const signals::timer* timer = *iter;
if (timer->fired())
++n_fired;
}
if (old_n_fired < n_fired) {
std::cout << "Waiting; " << n_fired << " timers fired so far\n";
old_n_fired = n_fired;
}
::usleep(100);
} while (n_fired < timers.size());
}
} // anonymous namespace
ATF_TEST_CASE(program_seconds);
ATF_TEST_CASE_HEAD(program_seconds)
{
set_md_var("timeout", "10");
}
ATF_TEST_CASE_BODY(program_seconds)
{
signals::timer timer(datetime::delta(1, 0));
ATF_REQUIRE(!timer.fired());
while (!timer.fired())
::usleep(1000);
}
ATF_TEST_CASE(program_useconds);
ATF_TEST_CASE_HEAD(program_useconds)
{
set_md_var("timeout", "10");
}
ATF_TEST_CASE_BODY(program_useconds)
{
signals::timer timer(datetime::delta(0, 500000));
ATF_REQUIRE(!timer.fired());
while (!timer.fired())
::usleep(1000);
}
ATF_TEST_CASE(multiprogram_ordered);
ATF_TEST_CASE_HEAD(multiprogram_ordered)
{
set_md_var("timeout", "20");
}
ATF_TEST_CASE_BODY(multiprogram_ordered)
{
static const std::size_t n_timers = 100;
std::vector< signals::timer* > timers;
std::vector< int > items, exp_items;
const int initial_delay_ms = 1000000;
for (std::size_t i = 0; i < n_timers; ++i) {
exp_items.push_back(i);
timers.push_back(new delayed_inserter(
datetime::delta(0, initial_delay_ms + (i + 1) * 10000),
items, i));
ATF_REQUIRE(!timers[i]->fired());
}
wait_timers(timers);
ATF_REQUIRE_EQ(exp_items, items);
}
ATF_TEST_CASE(multiprogram_reorder_next_activations);
ATF_TEST_CASE_HEAD(multiprogram_reorder_next_activations)
{
set_md_var("timeout", "20");
}
ATF_TEST_CASE_BODY(multiprogram_reorder_next_activations)
{
std::vector< signals::timer* > timers;
std::vector< int > items;
// First timer with an activation in the future.
timers.push_back(new delayed_inserter(
datetime::delta(0, 100000), items, 1));
ATF_REQUIRE(!timers[timers.size() - 1]->fired());
// Timer with an activation earlier than the previous one.
timers.push_back(new delayed_inserter(
datetime::delta(0, 50000), items, 2));
ATF_REQUIRE(!timers[timers.size() - 1]->fired());
// Timer with an activation later than all others.
timers.push_back(new delayed_inserter(
datetime::delta(0, 200000), items, 3));
ATF_REQUIRE(!timers[timers.size() - 1]->fired());
// Timer with an activation in between.
timers.push_back(new delayed_inserter(
datetime::delta(0, 150000), items, 4));
ATF_REQUIRE(!timers[timers.size() - 1]->fired());
wait_timers(timers);
std::vector< int > exp_items;
exp_items.push_back(2);
exp_items.push_back(1);
exp_items.push_back(4);
exp_items.push_back(3);
ATF_REQUIRE_EQ(exp_items, items);
}
ATF_TEST_CASE(multiprogram_and_cancel_some);
ATF_TEST_CASE_HEAD(multiprogram_and_cancel_some)
{
set_md_var("timeout", "20");
}
ATF_TEST_CASE_BODY(multiprogram_and_cancel_some)
{
std::vector< signals::timer* > timers;
std::vector< int > items;
// First timer with an activation in the future.
timers.push_back(new delayed_inserter(
datetime::delta(0, 100000), items, 1));
// Timer with an activation earlier than the previous one.
timers.push_back(new delayed_inserter(
datetime::delta(0, 50000), items, 2));
// Timer with an activation later than all others.
timers.push_back(new delayed_inserter(
datetime::delta(0, 200000), items, 3));
// Timer with an activation in between.
timers.push_back(new delayed_inserter(
datetime::delta(0, 150000), items, 4));
// Cancel the first timer to reprogram next activation.
timers[1]->unprogram(); delete timers[1]; timers.erase(timers.begin() + 1);
// Cancel another timer without reprogramming next activation.
timers[2]->unprogram(); delete timers[2]; timers.erase(timers.begin() + 2);
wait_timers(timers);
std::vector< int > exp_items;
exp_items.push_back(1);
exp_items.push_back(3);
ATF_REQUIRE_EQ(exp_items, items);
}
ATF_TEST_CASE(multiprogram_and_expire_before_activations);
ATF_TEST_CASE_HEAD(multiprogram_and_expire_before_activations)
{
set_md_var("timeout", "20");
}
ATF_TEST_CASE_BODY(multiprogram_and_expire_before_activations)
{
std::vector< signals::timer* > timers;
std::vector< int > items;
{
signals::interrupts_inhibiter inhibiter;
// First timer with an activation in the future.
timers.push_back(new delayed_inserter(
datetime::delta(0, 100000), items, 1));
ATF_REQUIRE(!timers[timers.size() - 1]->fired());
// Timer with an activation earlier than the previous one.
timers.push_back(new delayed_inserter(
datetime::delta(0, 50000), items, 2));
ATF_REQUIRE(!timers[timers.size() - 1]->fired());
::sleep(1);
// Timer with an activation later than all others.
timers.push_back(new delayed_inserter(
datetime::delta(0, 200000), items, 3));
::sleep(1);
}
wait_timers(timers);
std::vector< int > exp_items;
exp_items.push_back(2);
exp_items.push_back(1);
exp_items.push_back(3);
ATF_REQUIRE_EQ(exp_items, items);
}
ATF_TEST_CASE(expire_before_firing);
ATF_TEST_CASE_HEAD(expire_before_firing)
{
set_md_var("timeout", "20");
}
ATF_TEST_CASE_BODY(expire_before_firing)
{
std::vector< int > items;
// The code below causes a signal to go pending. Make sure we ignore it
// when we unblock signals.
signals::programmer sigalrm(SIGALRM, null_handler);
{
signals::interrupts_inhibiter inhibiter;
delayed_inserter* timer = new delayed_inserter(
datetime::delta(0, 1000), items, 1234);
::sleep(1);
// Interrupts are inhibited so we never got a chance to execute the
// timer before it was destroyed. However, the handler should run
// regardless at some point, possibly during deletion.
timer->unprogram();
delete timer;
}
std::vector< int > exp_items;
exp_items.push_back(1234);
ATF_REQUIRE_EQ(exp_items, items);
}
ATF_TEST_CASE(reprogram_from_scratch);
ATF_TEST_CASE_HEAD(reprogram_from_scratch)
{
set_md_var("timeout", "20");
}
ATF_TEST_CASE_BODY(reprogram_from_scratch)
{
std::vector< int > items;
delayed_inserter* timer1 = new delayed_inserter(
datetime::delta(0, 100000), items, 1);
timer1->unprogram(); delete timer1;
// All constructed timers are now dead, so the interval timer should have
// been reprogrammed. Let's start over.
delayed_inserter* timer2 = new delayed_inserter(
datetime::delta(0, 200000), items, 2);
while (!timer2->fired())
::usleep(1000);
timer2->unprogram(); delete timer2;
std::vector< int > exp_items;
exp_items.push_back(2);
ATF_REQUIRE_EQ(exp_items, items);
}
ATF_TEST_CASE(unprogram);
ATF_TEST_CASE_HEAD(unprogram)
{
set_md_var("timeout", "10");
}
ATF_TEST_CASE_BODY(unprogram)
{
signals::timer timer(datetime::delta(0, 500000));
timer.unprogram();
usleep(500000);
ATF_REQUIRE(!timer.fired());
}
ATF_TEST_CASE(infinitesimal);
ATF_TEST_CASE_HEAD(infinitesimal)
{
set_md_var("descr", "Ensure that the ordering in which the signal, the "
"timer and the global state are programmed is correct; do so "
"by setting an extremely small delay for the timer hoping that "
"it can trigger such conditions");
set_md_var("timeout", "10");
}
ATF_TEST_CASE_BODY(infinitesimal)
{
const std::size_t rounds = 100;
const std::size_t exp_good = 90;
std::size_t good = 0;
for (std::size_t i = 0; i < rounds; i++) {
signals::timer timer(datetime::delta(0, 1));
// From the setitimer(2) documentation:
//
// Time values smaller than the resolution of the system clock are
// rounded up to this resolution (typically 10 milliseconds).
//
// We don't know what this resolution is but we must wait for longer
// than we programmed; do a rough guess and hope it is good. This may
// be obviously wrong and thus lead to mysterious test failures in some
// systems, hence why we only expect a percentage of successes below.
// Still, we can fail...
::usleep(1000);
if (timer.fired())
++good;
timer.unprogram();
}
std::cout << F("Ran %s tests, %s passed; threshold is %s\n")
% rounds % good % exp_good;
ATF_REQUIRE(good >= exp_good);
}
ATF_INIT_TEST_CASES(tcs)
{
ATF_ADD_TEST_CASE(tcs, program_seconds);
ATF_ADD_TEST_CASE(tcs, program_useconds);
ATF_ADD_TEST_CASE(tcs, multiprogram_ordered);
ATF_ADD_TEST_CASE(tcs, multiprogram_reorder_next_activations);
ATF_ADD_TEST_CASE(tcs, multiprogram_and_cancel_some);
ATF_ADD_TEST_CASE(tcs, multiprogram_and_expire_before_activations);
ATF_ADD_TEST_CASE(tcs, expire_before_firing);
ATF_ADD_TEST_CASE(tcs, reprogram_from_scratch);
ATF_ADD_TEST_CASE(tcs, unprogram);
ATF_ADD_TEST_CASE(tcs, infinitesimal);
}