Избавляемся от рутины в Engee
Автоматизация верификации кода
Я очень много занимаюсь генерацией кода и проверками того, что он работает одинаково с моделью. Весь этот процесс занимает много ручного труда, и в какой-то момент я понял что мне надоело делать одну и ту же работу руками. Поэтому, я автоматизировал ее, и в этом проекте мы посмотрим на автоматизацию тестирования сгенерированного кода в режиме ПО-в-Контуре (SIL).
О чем вообще речь?
Метод проверки ПО-в-Контуре (SIL-тестирование) заключается в том, что мы сравниваем результаты работа кода на хосте относительно работы модели, используя одинаковые тестовые вектора. Схематично, тестирование выглядит так:
Из модели алгоритма генерируется код, а затем модель алгоритма подменяется вызовами сгенерированного кода. Сам же код компилируется на хосте. Таким образом, SIL-тестирование позволяет проверить поведение кода относительно модели без перехода на целевые вычислители (тестирование кода на целевом вычислителе - это отдельный этап тестирования, называемый Процессор-в-контуре, PIL).
Пример
Чтобы показать автоматизацию SIL-тестирования, я взял пример из этой публикации. Для удобства тестирования я встроил модель фильтра Калмана в большую модель при помощи модели ссылки:
Модель-ссылка дает следующие преимущества:
- Изоляция тестируемого компонента в отдельной модели. При инициализации обвязки интерфейсы модели ссылки инициализируются отдельно.
- Простота замены тестируемого компонента на блок Си-функции для SIL-теста через программное управление.
Автоматизация тестирования
Для написания автоматизации нам потребуется использовать программное управление. При этом, я хочу сделать так, чтобы моя автоматизация была максимально переиспользуемой. Поэтому я написал свой модуль, который автоматизирует большую часть работы. Причем модуль был написан с учетом того, что его функции будут использоваться в тестах, которые я создам в Test.jl.
Давайте подключим все необходимые пакеты и загрузим модуль!
import Pkg; Pkg.add("Test")
using Test
include("SILAutomation.jl");
MIL_Harness = "ABfilter_Harness";
SIL_Harness = "ABfilter_Harness_SIL";
CUT = "alphabetafilter";
Давайте посмотрим на код модуля:
;cat SILAutomation.jl
Генерация проверочной Си функции
Чтобы сгенерировать проверочную Си функцию из тестируемого компонента необходимо включить соответствующую настройку в модели и сгенерировать код. За это в моем модуле отвечает функция buildCUT:
function buildCUT(CUT::String)::Bool
status = false;
try
abfilter = engee.load("$(CUT).engee")
engee.set_param!(engee.gcm(),"CreateCFunction"=>true)
engee.generate_code("$(CUT).engee", "$(CUT)_cg")
include("$(CUT)_cg/$(CUT)_verification.jl")
status = true;
catch ex
return status
end
return status
end
Для того чтобы эта функция была тестируема, будем возвращать статус как булеву переменную.
Автоматизация сборки SIL-обвязки
Я не хочу изменять обвязку для MIL-теста, но мне надо обеспечить эквивалентность тестовых векторов, поэтому я создам новую модель для SIL-теста. Посмотрим на код функции buildSILHarness:
function buildSILHarness(SIL_Harness::String,CUT::String,MIL_Harness::String)
# Create harness (copy contents)
# Guard for old harnesses
model_names = [m.name for m in engee.get_all_models()];
if any(model_names .== SIL_Harness)
engee.close(SIL_Harness,force=true)
end
if isfile("$SIL_Harness.engee")
rm("$SIL_Harness.engee")
end
SIL_mdl = engee.create("$SIL_Harness")
engee.load("$(MIL_Harness).engee")
engee.copy_contents(MIL_Harness,SIL_Harness)
engee.delete_block("$SIL_Harness/$CUT")
try
engee.copy_block("$(CUT)_verification/C Function","$SIL_Harness/C Function")
catch error
show(error)
end
engee.open(SIL_Harness)
#connect SIL Function And prepare simulation
engee.add_line("ProcessNoiseVariance/1","C Function/1")
engee.add_line("MeasurementNoiseVariance/1","C Function/2")
engee.add_line("Add-1/1","C Function/3")
engee.add_line("C Function/1","Add/2")
engee.add_line("C Function/1","SysOut/1")
engee.set_log("C Function/1")
engee.set_param!(SIL_mdl,"FixedStep"=>engee.get_param(MIL_Harness,"FixedStep"))
engee.arrange_model()
engee.save("$SIL_Harness.engee")
end
Сначала с помощью engee.create() я создаю новую пустую модель, а затем копирую содержимое оригинальной модели в новую при помощи engee.copy_contents(). Далее я удаляю модель ссылку и подставляю проверочную Си-функцию с помощью engee.delete_block() и engee.copy_block(). Затем я восстанавливаю сигналы и указываю какой сигнал надо записать. Затем я упорядочиваю модель и сохраняю ее. Итоговая модель будет выглядеть вот так:
.png)
Как правильно сравнивать сигналы?
Мы все привыкли сравнивать два числа через оператор ==. Например:
1.0 == 1.0
Но тут есть две проблемы:
- Числа с плавающей точкой лучше не сравнивать напрямую, а сравнивать модуль их разности с некоторой допустимой погрешностью
- Если говорить о сигналах, то мы опять-таки хотим учесть, что сигналы могут быть неодинаковыми и хотим учитывать небольшую ошибку
Дополнительно, надо проверить, что сигналы синхронизированы.
Посмотрим на код функции сравнения:
function compare_signals(sig_one,sig_two)
Ds = collect(sig_one);
Rs = collect(sig_two);
Cmp = isapprox.(Ds, Rs)
issynched = all(Cmp.time)
issimilar = all(Cmp.value)
return (issynched, issimilar)
end
Мы сравниваем два сигнала, которые представлены как DataFrame с двумя столбцами: time и value. Сравнение будем делать через функцию isapprox(). Возвращать будем две булевы переменные:
- issynched - показывает, что сигналы синхронизированы по времени
- issimilar - показывает, что сигналы одинаковы с определенной точностью
Создание набора тестов и его запуск
Теперь, когда у нас есть все необходимое для автоматизации тестирования, создадим наборы тестов с помощью Test.jl. Причем я разнесу тесты на сборку верификационной модели и эквивалентности работы кода и модели. Запустим тесты и посмотрим на их результат:
@testset verbose = true "SIL" begin
@testset "Code Generation" begin
@test SILAutomotion.buildCUT(CUT)==true
@test isfile(CUT*"_verification.engee")
end
SILAutomotion.buildCUT(CUT)
SILAutomotion.buildSILHarness(SIL_Harness,CUT, MIL_Harness)
@testset "SIL Equality" begin
(MR,SR) = SILAutomotion.runSims(MIL_Harness,SIL_Harness)
(sync,equal) = SILAutomotion.compare_signals(MR["filtered"],SR["C Function.1"])
@test sync==true
@test equal==true
end
end
Видно, что все тесты прошли, а моя автоматизация работает!
Выводы и следующие шаги
Применяя программное управление мне удалось автоматизировать рутину. Можно и дальше развить эту автоматизацию, расширив ее, например, на PIL-тестирование. Написанные с помощью Test.jl тесты также могут быть расширены для более глубокого тестирования. И, наконец, можно организовать тестирование в рамках единой обвязки с помощью блоков Variant Source/Variant Sink. Последнее требует отдельной публикации.