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
427 lines
13 KiB
C++
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);
|
|
}
|