Heute hab ich einen sehr merkwürdigen Bug gefixt. Ich benutze das Unit Testing Framework (UTF) der Boost-Library. Im Allgemeinen finde ich das Framework auch ganz gut, aber heute (bzw. eigentlich schon gestern) habe ich eine Merkwürdigkeit entdeckt.
In Boost:UTF werden Fixtures über structs realisiert. Der Konstruktor dient als Setup- und der Destruktor als Teardown-Methode. Prinzipiell also eine Umsetzung des RAII-Idioms. Das Ganze sieht dann in etwa folgendermaßen aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | struct FIXTURE { FIXTURE() { foo = new Foo(); } ~FIXTURE() { delete foo; } Foo* foo; } ... BOOST_FIXTURE_TEST_CASE( testname, FIXTURE ) { BOOST_CHECK_EQUAL( foo->bar(), 42 ); } |
Ich hatte gerade ein paar neu Testfälle hinzugefügt und wollte sehen, ob sie durchlaufen. Allerdings brach Vista das Testprogramm einfach ab. Die Vista-Hilfe signalisierte mir, dass das ein tolles Feature ist, das Programme automatisch beendet, wenn sie versuchen fremde Daten zu manipulieren (das sagte IIRC die Hilfe, letztendlich prüft es wohl nur auf eine bestimmte Art AccessViolation). VisualStudio zeigte mir beim durchsteppen eine vollkommen harmlose Stelle (sofern man bei C++ von harmlosen Stellen reden kann). Das war ne einfache Zuweisung an Speicher der garantiert existiert. Prinzipiell die selbe Stelle wurde wo anders auch beschrieben. Der einzige Unterschied war… dass im einen Fall ein Fixture verwendet wurde und im anderen nicht…
Merkwürdigerweise war das selbe Verhalten zu beobachten, wenn man den Test-Case vollkommen leerte. Allein das Einbinden des Fixtures erzeugte die AV. Das nächste, was mich irritiert hat, war, dass mir der Debugger ganz merkwürdiges Zeug anzeigte. Die Daten des Objektes waren nicht die erwarteten und viel merkwürdiger noch: Der Pointer auf die VMT verwies auf eine andere Klasse. Äh…
Ich schien mir also ziemlich den Speicher zerschossen zu haben. Auch, wenn ich mir nicht denken konnte warum. Normalerweise achte ich sehr genau darauf, dass so etwas nicht passiert. Aber bei C++ muss man ja auf alles gefasst sein.
Letztendlich hatte ich mir den Speicher auch gar nicht zerschossen – zumindest nicht direkt. Auf die richtige Fährte hat mich der Zeiger auf die falsche VMT gebracht. In einem anderen cpp-File hatte ich ebenfalls ein Fixture, das jedoch die andere Klasse instanzierte. Blöderweise hatte dieses den gleichen Namen und der Linker hat einfach das falsche Fixture in meinen Test-Case gelinkt. Es handelte sich dabei um eine Schwesterklasse, die ähnlich aufgebaut war. Der Linker hat sich nicht beschwert, sondern einfach das erstbeste passende Fixture gelinkt.
Kaum hatte ich das Fixture umbenannt, gab es keinerlei Probleme mehr. Eine weitere Möglichkeit ist es, das struct innerhalb der TestSuite zu definieren, also nicht:
1 2 3 4 5 6 7 8 | struct FIXTURE { ... } BOOST_AUTO_TEST_SUITE( test_suite ); ... |
sondern
1 2 3 4 5 6 7 8 | BOOST_AUTO_TEST_SUITE( test_suite ); struct FIXTURE { ... } ... |
Vermutlich basteln die ganzen Makros da irgendwie Namespaces drum herum. Keine Ahnung. Jedenfalls hilft das auch. Letztendlich hab ich also beides gemacht: Fixture umbenannt und zusätzlich in die Test-Suite gepackt. Und ab jetzt achte ich darauf, dass sich alle Fixtures innerhalb der Test-Suite befinden.
Ob das nun ein Bug von Boost ist oder einer vom Linker oder was auch immer, weiß ich nicht. Wenn ich mal Zeit dazu finde, guck ich mir das vielleicht mal genauer an und schreib vielleicht mal ein Bug-Report, sollte sich das als solcher erweisen…