Как организовать проверку неизменности по времени с D-контрактами?

Например, я должен заверить, что определенная функция для определенной системы реального времени работает в течение 20 мс или меньше. Я могу просто измерить время в начале и в конце функции, а затем утверждать, что разница будет удовлетворительной. И я делаю это в C++.

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

Поэтому мне интересно, могу ли я использовать возможности контракта для проверки времени работы функции?

1 ответ

Решение

Вроде, но не очень хорошо. Причина в том, что переменные, объявленные в блоке in{}, не видны в блоке out{}. (Были некоторые обсуждения об изменении этого, так что он может проверить предварительное состояние после публикации, сделав копию в блоке in, но ничего не было реализовано.)

Итак, это не будет работать:

void foo()
in { auto before = Clock.currTime(); }
out { assert(Clock.currTime - before < dur!"msecs"(20)); }
body { ... }

Переменная from in не будет перенесена в out, что приведет к ошибке неопределенного идентификатора. Но я говорю "вроде", хотя, потому что есть потенциальный обходной путь:

import std.datetime;
struct Foo {
    SysTime test_before;
    void test()
    in {
        test_before = Clock.currTime();
    }
    out {
        assert(Clock.currTime - test_before < dur!"msecs"(20));
    }
    body {

    }
}

Объявление переменной как регулярного члена структуры. Но это будет означать множество бесполезных в противном случае переменных для каждой функции, не будет работать с рекурсией и просто загрязнит пространство имен членов.

Часть меня думает, что вы могли бы сделать свой собственный стек в сторону и потратить {} на время, затем на {} вывести его и проверить.... но быстрый тест показывает, что он может сломаться, как только наследование получит участвует. Если вы повторяете блок in{} каждый раз, это может сработать. Но это кажется мне ужасно хрупким. Правило с наследованием контракта - это ВСЕ блоки out{} дерева наследования, которые необходимо пройти, но только все ОДИН из блоков in{} должны пройти. Так что, если у вас есть другой вход {} в цепочке, он может забыть потянуть время, а затем, когда out попытается вытолкнуть его, ваш стек будет опустошен.

// just for experimenting.....
SysTime[] timeStack; // WARNING: use a real stack here in production, a plain array will waste a *lot* of time reallocating as you push and pop on to it

 class Foo {
    void test()
      in {
        timeStack ~= Clock.currTime();
      }
      out {
         auto start = timeStack[$-1];
         timeStack = timeStack[0 .. $-1];
         assert(Clock.currTime - start < dur!"msecs"(20));
         import std.stdio;
         // making sure the stack length is still sane
         writeln("stack length ", timeStack.length);
       }
    body { }
}

class Bar : Foo {
 override void test()
  in {
     // had to repeat the in block on the child class for this to work at all
    timeStack ~= Clock.currTime();
  }
  body {
    import core.thread;
    Thread.sleep(10.msecs); // bump that up to force a failure, ensuring the test is actually run
  }
}

Кажется, это работает, но я думаю, что это больше проблем, чем оно того стоит. Я ожидаю, что она как-то сломается, когда программа станет больше, и если ваш тест нарушит вашу программу, это как бы побьет цель.

Я бы, вероятно, сделал это как unittest{}, если бы только проверка с помощью явных тестов соответствовала вашим требованиям (однако, обратите внимание, что контракты, как и большинство утверждений в D, удаляются, если вы компилируете с ключом -release, поэтому они не будут на самом деле проверяться и в версиях релиза. Если вам нужно, чтобы он надежно завершился с ошибкой, выведите исключение, а не утверждение, так как это всегда будет работать в режимах отладки и выпуска.).

Или вы можете сделать это с помощью assert в функции или вспомогательной структуры или чего-либо подобного C++. Я бы использовал прицел:

void test() {
    auto before = Clock.currTime();
    scope(exit) assert(Clock.currTime - before < dur!"msecs"(20)); // or import std.exception; and use enforce instead of assert if you want it in release builds too
    /* write the rest of your function */
}

Конечно, здесь вам придется скопировать его и в подклассы, но кажется, что вам все равно придется делать это с блоками in{}, так что, по крайней мере, переменная before является локальной.

В итоге, я бы сказал, что вам, вероятно, лучше делать это более или менее так же, как вы это делали в C++.

Другие вопросы по тегам