Как вы высмеиваете время для ускорителей? - программирование
Подтвердить что ты не робот

Как вы высмеиваете время для ускорителей?

Если возможно, как вы высмеиваете время для запуска импульсов повышения в unit test?

Например, можно ли добиться чего-то вроде следующего:

#include <iostream>
#include <boost/asio.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

void print(const boost::system::error_code& /*e*/)
{
  std::cout << "Hello, world!\n";
}

int main()
{
    boost::asio::io_service io;        // Possibly another class needed here, or a way of setting the clock to be fake

    boost::asio::deadline_timer t(io, boost::posix_time::hours(24));
    t.async_wait(&print);

    io.poll();  // Nothing should happen - no handlers ready

    // PSEUDO-CODE below of what I'd like to happen, jump ahead 24 hours
    io.set_time(io.get_time() + boost::posix_time::hours(24));

    io.poll();  // The timer should go off

    return 0;
}

Обновление. Спасибо всем ответам, они предоставили отличное представление о проблеме. Я предоставил свой собственный ответ (SSCCE), но не мог этого сделать без помощи.

4b9b3361

Ответ 1

A SSCCE, основанный на ссылка, отправленная @free_coffee:

#include <boost/asio.hpp>
#include <boost/optional.hpp>

class mock_time_traits
{       
    typedef boost::asio::deadline_timer::traits_type  source_traits;

public:

    typedef source_traits::time_type time_type;
    typedef source_traits::duration_type duration_type;

    // Note this implemenation requires set_now(...) to be called before now()
    static time_type now() { return *now_; }

    // After modifying the clock, we need to sleep the thread to give the io_service
    // the opportunity to poll and notice the change in clock time
    static void set_now(time_type t) 
    { 
        now_ = t; 
        boost::this_thread::sleep_for(boost::chrono::milliseconds(2)); 
    }

    static time_type add(time_type t, duration_type d) { return source_traits::add(t, d); }
    static duration_type subtract(time_type t1, time_type t2) { return source_traits::subtract(t1, t2); }
    static bool less_than(time_type t1, time_type t2) { return source_traits::less_than(t1, t2); }

    // This function is called by asio to determine how often to check 
    // if the timer is ready to fire. By manipulating this function, we
    // can make sure asio detects changes to now_ in a timely fashion.
    static boost::posix_time::time_duration to_posix_duration(duration_type d) 
    { 
        return d < boost::posix_time::milliseconds(1) ? d : boost::posix_time::milliseconds(1);
    }

private:

    static boost::optional<time_type> now_;
};

boost::optional<mock_time_traits::time_type> mock_time_traits::now_;



typedef boost::asio::basic_deadline_timer<
            boost::posix_time::ptime, mock_time_traits> mock_deadline_timer;

void handler(const boost::system::error_code &ec)
{
    std::cout << "Handler!" << std::endl;
}


int main()
{
    mock_time_traits::set_now(boost::posix_time::time_from_string("2013-01-20 1:44:01.000"));

    boost::asio::io_service io_service;
    mock_deadline_timer timer(io_service, boost::posix_time::seconds(5));
    timer.async_wait(handler);

    std::cout << "Poll 1" << std::endl;
    io_service.poll();

    mock_time_traits::set_now(mock_time_traits::now() + boost::posix_time::seconds(6));


    std::cout << "Poll 2" << std::endl;
    io_service.poll();

    std::cout << "Poll 3" << std::endl;
    io_service.poll();

    return 0;
}

// Output
Poll 1
Poll 2
Handler!
Poll 3

Благодарим @free_coffee за предоставление этой ссылки в запись в блоге от создателя boost asio. Вышеуказанное немного изменено (и я считаю, что оно немного улучшилось). Не используя смещение на системных часах, вы получаете полный контроль над таймерами: они не срабатывают, пока вы явно не установите время перед таймером.

Решение может быть улучшено путем настройки конфигурации this_thread::sleep. Обратите внимание, что to_posix_duration hack, описанный в [1], должен использовать меньшую продолжительность, чем sleep.

Мне этот подход все еще кажется немного волшебным, так как time_traits плохо документированы, и, в частности, взлом to_posix_duration имеет дуновение вуду о нем. Я предполагаю, что это сводится к интимному знанию реализации deadline_timer (которого у меня нет).

Ответ 2

В шаблоне basic_deadline_timer есть параметр traits, который вы можете использовать для предоставления собственных часов. У автора Boost Asio есть сообщение в блоге, показывающее, как это сделать. Вот пример из сообщения:

class offset_time_traits
  : public asio::deadline_timer::traits_type
{
public:
  static time_type now()
  {
    return add(asio::deadline_timer::traits_type::now(), offset_);
  }

  static void set_now(time_type t)
  {
    offset_ =
      subtract(t, asio::deadline_timer::traits_type::now());
  }

private:
  static duration_type offset_;
};

typedef asio::basic_deadline_timer<
    boost::posix_time::ptime, offset_time_traits> offset_timer;

Может быть, вы можете использовать что-то вроде offset_timer во всем приложении, но только вызывать set_now() при выполнении тестов?

Ответ 3

Насколько я знаю, нет способа подражать изменению времени или установить время с помощью Boost. Прежде чем перейти к нескольким методам, которые можно использовать для решения этой проблемы, необходимо рассмотреть несколько моментов:

  • Boost.Asio предоставляет таймеры, которые используют часы, но не предоставляют часы, поскольку они находятся за пределами области Boost.Asio. Таким образом, связанные с часами функции, такие как настройка или эмуляция, не входят в возможности Boost.Asio.
  • Монотонные часы могут использоваться внутри. Таким образом, изменение часов (эмулированных или фактических) может не дать желаемого эффекта. Например, boost:: asio:: stable_timer не будет влиять на изменения в системном времени, а реализация реакторов с использованием epoll может занять до 5 минут до обнаружения изменений в системном времени, так как он защищен от изменений в системных часах.
  • Для таймеров Boost.Asio изменение времени истечения неявно отменяет асинхронные операции ожидания на WaitableTimerService и TimerService. Это отмена приводит к завершению незавершенных операций асинхронного ожидания, как только это возможно, и отмененные операции будут иметь код ошибки boost::asio::error::operation_aborted.

Тем не менее, есть два общих метода для решения этой проблемы на основе того, что тестируется:

  • Время масштабирования.
  • Типы упаковки.

Время масштабирования

Время масштабирования сохраняет одинаковый общий относительный поток между несколькими таймерами. Например, таймер с истечением 1 секунды должен запускаться до таймера с истечением 24 часов. Для дополнительного контроля также можно использовать минимальную и максимальную длительности. Кроме того, длительность масштабирования работает для таймеров, на которые не влияют системные часы, как на steady_timer.

Вот пример, где применяется шкала 1 час = 1 секунда. Таким образом, 24-часовое истечение будет актуальным для истечения 24 секунд. Кроме того,

namespace bpt = boost::posix_time;
const bpt::time_duration max_duration = bpt::seconds(24);
const boost::chrono::seconds max_sleep(max_duration.total_seconds());

bpt::time_duration scale_time(const bpt::time_duration& duration)
{
  // Scale of 1 hour = 1 seconds.
  bpt::time_duration value =
    bpt::seconds(duration.total_seconds() * bpt::seconds(1).total_seconds() /
      bpt::hours(1).total_seconds());
  return value < max_duration ? value : max_duration;
}

int main()
{
  boost::asio::io_service io;
  boost::asio::deadline_timer t(io, scale_time(bpt::hours(24)));
  t.async_wait(&print);
  io.poll();
  boost::this_thread::sleep_for(max_sleep);
  io.poll();
}

Типы упаковки

Существует несколько различных мест, где можно вводить новые типы для получения желаемого поведения.

Во всех этих случаях важно учитывать поведение, изменяющее время истечения, будет неявно отменять операцию асинхронного ожидания.

Оберните deadline_timer.

Приобретение deadline_timer требует управления внутренним обработчиком. Если таймер передает обработчик пользователя службе, связанной с таймером, тогда пользовательский обработчик будет уведомлен о том, что время истечения срока действия изменится.

Пользовательский таймер может:

  • Храните WaitHandler в async_wait() внутри (user_handler_).
  • Когда вызывается cancel(), устанавливается внутренний флаг, указывающий, что произошло отмена (cancelled_).
  • Совокупность таймера. Когда установлено время истечения срока действия, внутренний агрегированный таймер async_wait передается внутренним обработчиком. Каждый раз, когда вызывается внутренний обработчик, он должен обрабатывать следующие четыре случая:
    • Обычный тайм-аут.
    • Явное аннулирование.
    • Неявное отмена с момента истечения срока действия, измененного на время, не будет в будущем.
    • Неявное отмена с момента истечения срока действия изменяется на время, которое в будущем.

Код внутреннего обработчика может выглядеть следующим образом:

void handle_async_wait(const boost::system::error_code& error)
{
  // Handle normal and explicit cancellation.
  if (error != boost::asio::error::operation_aborted || cancelled_)
  {
    user_handler_(error);
  }
  // Otherwise, if the new expiry time is not in the future, then invoke
  // the user handler.
  if (timer_.expires_from_now() <= boost::posix_time::seconds(0))
  {
    user_handler_(make_error_code(boost::system::errc::success));
  }
  // Otherwise, the new expiry time is in the future, so internally wait.
  else
  {
    timer_.async_wait(boost::bind(&custom_timer::handle_async_wait, this,
                      boost::asio::placeholders::error));
  }
}

Хотя это довольно просто реализовать, для этого требуется понимание интерфейса таймера, чтобы имитировать его предварительные/пост-условия, за исключением поведения, для которого вы хотите отклониться. Также может быть фактор риска при тестировании, так как поведение нужно подражать как можно ближе. Кроме того, для этого требуется изменить тип таймера для тестирования.

int main()
{
    boost::asio::io_service io;

    // Internal timer set to expire in 24 hours.
    custom_timer t(io, boost::posix_time::hours(24));

    // Store user handler into user_handler_.
    t.async_wait(&print);

    io.poll(); // Nothing should happen - no handlers ready

    // Modify expiry time.  The internal timer handler will be ready to
    // run with an error of operation_aborted.
    t.expires_from_now(t.expires_from_now() - boost::posix_time::hours(24));

    // The internal handler will be called, and handle the case where the
    // expiry time changed to timeout.  Thus, print will be called with
    // success.
    io.poll();

    return 0;
}

Создайте пользовательский WaitableTimerService

Создание пользовательского WaitableTimerServiceнемного сложнее. Хотя в документации указывается API и условия pre/post, для реализации требуется понимание некоторых внутренних компонентов, таких как реализация io_service и интерфейс планировщика, который часто является реактором. Если служба передает обработчик пользователю планировщик, тогда пользовательский обработчик будет уведомлен, когда время истечения срока действия будет изменено. Таким образом, подобно обертыванию таймера, пользовательский обработчик должен управляться внутренне.

Это имеет те же недостатки, что и обертка таймера: требует изменения типов и наследует риск из-за возможных ошибок при попытке сопоставить условия pre/post.

Например:

deadline_timer timer;

является эквивалентом:

basic_deadline_timer<boost::posix_time::ptime> timer;

и станет:

basic_deadline_timer<boost::posix_time::ptime,
                     boost::asio::time_traits<boost::posix_time::ptime>,
                     CustomTimerService> timer;

Хотя это можно было бы смягчить с помощью typedef:

typedef basic_deadline_timer<
  boost::posix_time::ptime,
  boost::asio::time_traits<boost::posix_time::ptime>,
  CustomTimerService> customer_timer;

Создайте собственный обработчик.

Класс обработчика может использоваться для обертывания фактического обработчика и обеспечивает тот же подход, что и выше, с дополнительной степенью свободы. Хотя для этого требуется изменить тип и изменить то, что предоставляется async_wait, он обеспечивает гибкость в том, что API пользовательского обработчика не имеет ранее существовавших требований. Эта сокращенная сложность обеспечивает минимальное решение для риска.

int main()
{
    boost::asio::io_service io;

    // Internal timer set to expire in 24 hours.
    deadline_timer t(io, boost::posix_time::hours(24));

    // Create the handler.
    expirable_handler handler(t, &print);
    t.async_wait(&handler);

    io.poll();  // Nothing should happen - no handlers ready

    // Cause the handler to be ready to run.
    // - Sets the timer expiry time to negative infinity.
    // - The internal handler will be ready to run with an error of
    //   operation_aborted.
    handler.set_to_expire();

    // The internal handler will be called, and handle the case where the
    // expiry time changed to timeout.  Thus, print will be called with
    // success.
    io.poll();

    return 0;
}

В целом, тестирование асинхронных программ традиционным способом может быть очень сложным. При правильной инкапсуляции это может быть даже почти невозможно для unit test без условных построений. Иногда это помогает сдвинуть перспективы и обработать всю цепочку асинхронных вызовов как единое целое, причем все внешние обработчики являются API. Если асинхронную цепочку слишком сложно проверить, я часто обнаруживаю, что цепочка слишком сложна для понимания и/или поддержки и будет отмечать ее как кандидата на рефакторинг. Кроме того, мне часто приходится писать вспомогательные типы, которые позволяют моей тестовой проводке обрабатывать асинхронные операции синхронно.

Ответ 4

Я не знаю, как подделать что-то вроде прохода времени, и я считаю, что это излишне, чтобы обеспечить ваше собственное время. Но вот мысль:

Инициализируя таймер с жестко закодированным 24h, вы использовали то, что можно было бы считать магической константой (что означает: что вы не должны делать). Вместо этого вы можете попробовать следующее:

boost::asio::deadline_timer t(io, getDeadLineForX());

Теперь, если вы заглушите функцию getDeadLineForX в своем тестовом наборе, вы можете пройти достаточно небольшой срок для проверки таймера, и вам не нужно ждать 24 часа для завершения набора тестов.