Hallo,
ich würde gerne einmal ein kleines Projekt von mir vorstellen. Dazu ein
wenig zur Vorgeschichte.
AVR entwickel ich schon länger. Habe aber immer wieder Probleme mit
automatischen Tests gehabt. Irgendwann bin ich dann auf das google-test
gestoßen und habe dann einfach meine Libs (welche nicht
prozessorspezifisch waren) dagegen automatisch getestet. Jedoch ist mir
dann aufgefallen, dass ein wenig inline Assembler manchmal ziemliche
Performance-Vorteile bringen kann. ^^
Dann war es leider vorbei mit gtest...
Dann habe ich mich wieder ein wenig auf die Suche gemacht und auch was
gefunden... Wie uCUnit. Aber... gtest gefiel mir von dem Overhead
ziemlich gut. Also hab ich mich versucht. ;)
Nun einmal ein kleines Bsp.
Um eine Tests zu definieren ist eine Suite nötig und dann kann schon der
Test kommen.
1
#include<uCTest/uCTest.h>
2
3
TESTSUITE(Simple);
4
5
TEST(Simple,add){
6
EXPECT_EQ((2+2),4);
7
EXPECT_EQ((1+1),2);
8
}
Das kann dann übersetzt und (bisher) in simulavr ausgeführt werden.
[==========] 1 tests from 1 test suites ran. (55 cycles total)
11
[ PASSED ] 1 tests.
12
0
Mehr ist eigentlich nicht nötig.
Ein bisschen mehr zu den Features.
Es kann ein system setup und teardown implementiert werden. In den
Tests. dazu muss uctest_system_setup/teardown implementiert werden.
Diese sind schwach (weak) gebunden.
Es werden Fixture tests zur Verfügung gestellt. Dazu muss TEST_F genutzt
werden. Dort ist es dann erforderlich SETUP(suite) und TEARDOWN(suite)
zu implementieren.
Wenn die lib mit CONFIG_COUNT_CYCLES übersetzt wird, werden die Takte
"gezählt". Dies geschieht über den 16 bit Timer.
Ansonsten...
Die ASSERT und EXPECT sind noch ausbaufähig. Dort habe ich bisher
getestet mit ASSERT_EQ und ASSERT_NEAR. Mehr ist dort noch nicht
implementiert. Das steht aber auch noch ganz groß auf meiner ToDo.
Weiterhin auf der ToDo steht auch noch ein JSON output, welcher dann
nach XML konvertiert werden kann um JUNIT-Kompatible outputs zu
erzeugen.
Guckt euch das gerne einmal an. Ich habe dort viel Freude beim
implementieren gehabt und hoffe es ist vielleicht nützlich für einige!
;)
Viele Grüße
Sebastian
Hallo Testi,
vielen Dank für den guten Link. So in der Art habe ich das auch mit dem
gtest jeweils versucht. Prozessorspezifische Register lassen sich noch
recht gut abstrahieren.
Mir ging es dann aber später um Optimierungen von lib Funktionen.
Als Bsp.
Dieser Filter greift auf eine ShiftRoundAdd Funktion zu. Diese kann man
einmal generisch implementieren. Dann läuft das auch alles und ich kann
das auch wieder auf x86 testen und kann das gegen gtest linken lassen.
Jedoch habe ich mit einer Asm-Implementierung einige hundert Takte
sparen können.
Pro filter_bq_compute... ...
Das ist dann schon ein enormer performance-Vorteil. Dort sollte dann
natürlich auch getestet werden. ;)
Die generische implementierung
1
[----------] 1 tests from FilterBqTest
2
[ RUN ] FilterBqTest.filterBessel
3
[ OK ] FilterBqTest.filterBessel (47240 cycles)
4
[----------] 1 tests from FilterBqTest (47240 cycles)
Per inline Asm optimiertes ShiftRoundAdd
1
[----------] 1 tests from FilterBqTest
2
[ RUN ] FilterBqTest.filterBessel
3
[ OK ] FilterBqTest.filterBessel (19190 cycles)
4
[----------] 1 tests from FilterBqTest (19190 cycles)
Aso. Noch eine Kleinigkeit zu dem Beispiel oben. Das hat den Vorteil,
dass dies mit dem gtest übersetzt werden kann, und auch mit dem uCTest,
sodass es auf dem Controller lauffähig ist ;)
Noch ein Nachtrag:
Ein Grund für die Entwicklung vom uCTest war unter anderem auch, dass
ich nicht jeden Test neu implementieren muss. Sondern das ich auf
bestehende Tests vom gtest zurück greifen kann.
Viele Grüße
Sebastian
Hallo,
ich habe einmal ein kleines Update. Ein ToDo auf meiner Liste war ja
noch die Erweiterung der Assertions. Das war ein wenig Fleißarbeit und
das habe ich einmal getan.
Es stehen nun weiterhin ASSERT_LT, _LE, _GT, GE zur Verfügung. Weiter
habe ich mich auch um ein ASSERT_DEATH gekümmert. Also dass eine
Assertion vom Programm aus Fehl schlägt. Dazu muss dann aber die
verwendete Lib ohne NDEBUG übersetzt werden, sodass die asserts aus der
assert.h auch mit übersetzt werden.
Wichtig bei dem ASSERT_DEATH ist noch zu sagen... Ich prüfe dort bisher
nur, ob ein abort aufgerufen wird. Dann wird die Ausführung abgebrochen
für die Funktion. Es wird aber bisher nicht die Meldung geprüft!
Ich habe einmal ein kleines Bsp. mit einer Sqrt-Implementierung, welche
ich auch hier aus dem Forum habe. Leider finde ich den Thread gerade
nicht mehr.
Aber hier einmal ein wenig Code als Bsp:
Hallo,
ich habe einmal ein wenig weiter gemacht und mich um ein JSON output
gekümmert. Dies klappt nun auch. Man kann das in der Makefile mit
anschalten indem man CONFIG_OUTPUT_JSON als Symbol definiert. Auch ein
CONFIG_OUTPUT_NONE ist mit dazu gekommen. Dort kommt dann halt kein
output.
Um das dann noch ein wenig abzurunden gibt es unter tools ein kleines
Python-Script, welches das JSON in ein JUNIT-kompatibles XML wandelt.
Auch habe ich einen bug beim ASSERT_DEATH behoben. Dort muss natürlich
komplett der Kontext gesichert werden. Denn wenn ein assert aus der libc
das abort aufruft, wird ja an ein Label gesprungen. Dort müssen
natürlich der Stack und auch alle Register wiederhergestellt werden.
Viel Spaß und viele Grüße
Sebastian
Hallo,
ich habe einmal wieder an meinem Test-Framework weiter gearbeitet.
Zuerst einmal gab es ein paar kleinere Bugfixes und Verbesserungen.
* Aus den Headern ist nun die Abhängigkeit zur avr/io.h verschwunden.
* Die Nachrichten werden nun nicht mehr via stdout gesendet sondern
haben ein eigenes FILE bekommen
* Wenn bei einem FAIL kein format-String angegeben ist, dann stürzt der
Controller auch nicht mehr ab.
Weiterhin kam dann noch ein recht gutes Feature dazu.
Ich nutze nun ein Simulator und habe dafür ein Frontend entwickelt. Als
Simulator nutze ich buserror/simavr. Das Frontend sucht nach einer
AVR-ELF-Datei mit dem gleichen Namen und führt diese aus. Weiterhin ist
das Frontend soweit zu GTest kompatibel, dass man es nun in VSCode im
Testexplorer nutzen kann.
Weiterhin können einzelne Tests ausgeführt werden und die Tests gelistet
werden.
[----------] 1 tests from FilterBqTest (2 ms total)
10
11
[==========] 1 tests from 1 test suites ran. (2 ms total)
12
[ PASSED ] 1 tests.
Warum simavr... ...
Ein hauptgrund war, dass das Framework recht einfach ist und er recht
gut erweiterbar ist. Auch habe ich dann in den Beispielen zu dem
Simulator gefunden, dass man dort auch Hardware simulieren kann. Ganz so
schwierig ist das auch nicht. Sobald der Simulator initialisiert ist,
gibt es einen Hook welchen man nutzen kann um die Hardware zu
simulieren.
Dazu überschreibt man den test_init_hook und linkt sein Objekt gegen die
libtestRunner.so.
1
voidtest_init_hook(avr_t*avr);
2
voidtest_start_hook(avr_t*avr,char*name);
Die zweite Funktion ist ein Hook, welcher aufgerufen wird wenn ein Test
startet. Der Name des Tests wird dann in name übergeben.
Ich wünsche viel Freude und ich würde mich über Nachfragen und Beiträge
freuen.
Viele Grüße
Sebastian
Hallo,
ich habe einmal wieder ein Update parat. Mein Ziel war es nun das
Framework auf andere Architekturen zu portieren. Die einfachste war nun
erst einmal die x86-Architektur. Dort ist mir aber ein Problem bei dem
Konzept aufgefallen.
Beim AVR habe ich die Tests via der .init7 auf den Stack gepusht beim
registrieren. Das ist weder bei x86 ordentlich möglich noch bei RISC-V
(zweiter port)... Aus dem Grund bin ich nun einmal an die Linkerfile
ran. Dort habe ich nun für den test eine extra Section eingeführt. Das
funktioniert nun bei x86 sehr gut (dort kann man Sections einfügen) bei
AVR und RISC-V hab ich das noch nicht ganz so schön hinbekommen.. -.-
Sei es drum, dort stelle ich eine gepatchte Linkerfile mit zur
Verfügung.
Lauffähig ist das Framework nun auf
- AVR
- x86
- RISC-V (picolibc)
Beim Simulator-Frontend für den AVR habe ich noch einen
Use-After-Free-Bug behoben wenn FIXTURE-Tests ausgeführt werden.
Weiterhin habe ich nun noch ein wenig mehr dokumentiert und auch in der
README nun zwei kleine Beispiele drin. Für AVR und für RISC-V.
Das war es nun aber ;)
Viele Grüße
Sebastian