Task3 - adamk90/training-project-lab GitHub Wiki

A projekt kódlefedettség vizsgálata

A módszer

A kódlefedettség vizsgálathoz a gcov, lcov párost használtam. Segítséget és leírást az alábbi oldalon találtam: https://asmaloney.com/2017/01/code/code-coverage-of-unit-tests-using-qt-5-on-macos/ Lényegében a fordításkor meg kellett adni kapcsolókat a gcc-nek, ami metainformációkat helyez el fordításkor. Futtatáskor pedig megjelennek külön fájlokban a lefedettségi információk, amiket az lcov feldolgoz és html generátor által értelmezhető tartalmat állít elő (amit aztán meg lehet nézni böngészőben, ennek az első oldalát - az összesítést - mutatja az Eredmény szekcióban található kép).

Eredmény

LCOV Summary Ahogy a képen is látszik, elég jó a lefedettség (a header file-oknál nem annyira, de ott többnyire csak akkor fut le sor, ha helyben van definiálva függvény), viszont, mint ebből kiderült, egy osztályt egy az egyben kihagytak: a QtScript handler-t.

Vélemény

A gcov nem veszi figyelembe a függvények fejléceit, a konstansokat és értelemszerűen a kommenteket sem. Az alábbi képen látható, hogy a fehér sorok azok, amik nem "futottak", a kékek, amik értelmezettek és futottak (előttük, hogy hányszor), a pirosak pedig, amik értelmezettek és nem futottak (előttük nyilván 0): LCOV - Pillow lines 1 A preprocesszornak szánt utasítások sem futnak futás közben (ezért preprocesszor), így a lefedettségvizsgálat ezeket az eseteket sem tudja megvizsgálni. Pedig a tesztelés szempontjából nyilván fontos, hogy egyszerre csak az egyik eset tud érvényesülni (érdemes Qt4-gyel és Qt5-tel is tesztelni).

Saját tesztek a lefedettség javításához

Kitérő - A szerver futtatása

Gondoltam, mielőtt saját tesztet írok, kipróbálom élesben is a projektet (miért nem ezzel kezdtem?), hogy jobban megérthessem a működését. Ez egyébként ahhoz képest, hogy utólag mennyire egyértelműnek és egyszerűnek tűnik, rengeteg időmet elvette, ezért itt dokumentálom is. Először is, meg kellett ismerkednem a Qt signal/slot mechanizmusának alapvető működésével. Ez röviden úgy működik, hogy a signal-lal jelzett metódusokat lehet "emit"-elni és slotok segítségével reagálni rájuk, amikor feldolgozásra kerültek az event loop-ban. A feliratkozáshoz meg kell adni, hogy melyik objektum melyik signal-ját szeretnénk melyik objektum melyik slot-jára rákötni (lesz rá példa).

Tehát készítettem egy teszt szervert, aminek a forráskódja az alábbi (a kód egy része a pillow projekt README.MD-jéből származik):

#include "HttpHandler.h"
#include "HttpServer.h"
#include <QtCore>

using namespace Pillow;

int main(int argc, char *argv[]) {
  QCoreApplication app(argc, argv);
  HttpServer* server = new HttpServer(QHostAddress(QHostAddress::LocalHost), 4567);
  HttpHandlerStack* handler = new HttpHandlerStack(server); 
    new HttpHandlerFile("/mytest/public", handler);
    new HttpHandler404(handler);
  QObject::connect(server, SIGNAL(requestReady(Pillow::HttpConnection*)),
        handler, SLOT(handleRequest(Pillow::HttpConnection*)));
  return app.exec();
}

Lényegében létrehozok egy szervert a localhost 4567-es portjára, valamint egy handler stack-et. A handler stack két handler-ből áll: az egyik a megadott elérési úton lévő mappában lévő fájlokat szolgálja ki, a másik pedig, ha érvénytelen kérés érkezik, akkor 404-et ad vissza.

QObject::connect(server, SIGNAL(requestReady(Pillow::HttpConnection*)),
        handler, SLOT(handleRequest(Pillow::HttpConnection*)));

Ez a sor felelős a signal-slot összekapcsolásért: ha egy kérést sikeresen fogad a szerver, akkor azt lekezelteti a handler-rel.

Az egésznek van egy kerete:

QCoreApplication app(argc, argv);
...
return app.exec();

A QCoreApplication létrehozása szükséges, hogy legyen event loop és ez el is kezd pörögni akkor, amikor meghívjuk az exec függvényét.

A fordításhoz szükséges írni egy .pro fájlt, ami a qmake-nek mondja meg, hogy hogyan készítse el a Makefile-t. Ez így nézett ki nálam:

TARGET = mytestserver
INCLUDEPATH += . ../pillowcore
QT += network
SOURCES += mytestserver.cpp
LIBS += -L../lib -lpillowcore
CONFIG += c++11

Itt ami fontos, hogy az INCLUDEPATH-ban meg kell adni a pillowcore header-jeinek forrását a LIBS-ben pedig, hogy hol találhatóak a pillowcore dynamic library-k és, hogy milyen néven keresse. Ha ez meg van, akkor csak ki kell adni a qmake, majd utána a make parancsot és le is fordul. Indítás után pedig már figyel is a szerver (csináltam a /mytest/public mappába egy index.html-t):

Pillow Server Success

Ha pedig olyan fájlt próbálok elérni, ami nincs, akkor jön is a 404:

Pillow Server 404

A saját tesztek

Bár azt írtam, hogy a tesztek lefedettsége elég jó (azt is figyelembevéve, hogy ahol esetleg nem, ott többnyire konstruktorok nem voltak meghívva, egy-két warningos ág nem futott le), azért próbáltam néhány helyen növelni a lefedettséget. Az első ilyen teszt azt hivatott ellenőrizni, hogy mi történik akkor, ha a HttpHandlerFile-t valaki rossz útvonallal hívja meg:

void HttpHandlerFileTest::testIllegalPath() {
	HttpHandlerFile handler1{testPath + "/notexits"}; //not exists
	QVERIFY(handler1.publicPath().isEmpty());
	QString legalPath(testPath + "/first");
	QVERIFY(!handler1.handleRequest(createGetRequest(legalPath.toLocal8Bit())));
	HttpHandlerFile handler3{testPath}; //exists
	QVERIFY(!handler3.publicPath().isEmpty());
	QVERIFY(!handler3.handleRequest(createGetRequest("/../HttpHandleFileTest/first"))); // illegal ".." manipulation
	HttpHandlerFile handler2{testPath + "/first"}; //exists, but not directory
	QVERIFY(handler2.publicPath().isEmpty());
	QVERIFY(!handler2.handleRequest(createGetRequest("")));
}

Először megpróbálunk egy olyan útvonalat adni neki, ami szimplán nem létezik, majd egy olyat, ami létezik, de a lekérésnél nem megengedett a "." és a ".." használata (még jobb lenne a teszt, ha olyan fájlt hoznánk létre, ami csak egy link egy másikra, ami másik elérési útvonalon van, ilyenkor sem szabadna működnie, de nincs "." vagy ".." használva), végül pedig egy olyat, ami létezik, de nem könyvtár. Elvileg az utóbbit sem szabadna elfogadnia. Az eredmény pedig itt látható: Pillow First Test Tehát mindennek megfelelt, viszont az utolsó előtti QVERIFY-nál elszállt, mert attól függetlenül, hogy hibás a megadott útvonal (nem könyvtár), beteszi. Nekem kétséges, hogy itt ez az elvárt viselkedés.

Következő tesztek szintén a HttpHandleFile-ra vonatkoznak, méghozzá teszteltem különböző buffer méretekkel a küldést:

void HttpHandlerFileTest::testSmallerBufferSize() {
	HttpHandlerFile handler(testPath);
	handler.setBufferSize(256);
	QCOMPARE(handler.bufferSize(), 256);
	QVERIFY(handler.handleRequest(createGetRequest("/large")));
	response.clear();
	while (response.isEmpty())
		QCoreApplication::processEvents();
	QVERIFY(response.size() > 16 * 1024 * 1024);
	QVERIFY(response.startsWith("HTTP/1.0 200 OK"));
	QVERIFY(response.endsWith(QByteArray(16 * 1024 * 1024, '-')));
}

void HttpHandlerFileTest::testBiggerBufferSize() {
	HttpHandlerFile handler(testPath);
	handler.setBufferSize(16*1024*1024*2);
	QVERIFY(handler.handleRequest(createGetRequest("/large")));
	QVERIFY(response.size() > 16 * 1024 * 1024);
	QVERIFY(response.startsWith("HTTP/1.0 200 OK"));
	QVERIFY(response.endsWith(QByteArray(16 * 1024 * 1024, '-')));
}

A testPath/large egy korábban létrehozott adatfájl, ami 16*1024*1024 db kötőjelet tartalmaz. Ezt próbáljuk átküldeni először 256-os buffermérettel, majd 16*1024*1024*2-essel. A 256-osnál, bár sikerült átállítani, de a konkrét kéréskor a HttpHandlerFileTransfer korrigálni fogja 512-re, mert az a legkisebb méret. Kicsit furcsálom, hogy teteje viszont nincs, a 32 megás bufferméretet simán elfogadta. Eredmény: Pillow First Second

Végül ugyanitt a HttpHandleFile-nál tesztelem, hogy működik-e az ETag cache-elés:

void HttpHandlerFileTest::testEtag() {
	QByteArray smallData(256, '-');
	{ QFile f(testPath + "/small"); f.open(QIODevice::WriteOnly);
        f.write(smallData); f.flush(); f.close(); }
	QCryptographicHash md5sum(QCryptographicHash::Md5);
	md5sum.addData(smallData);
	QByteArray etag = md5sum.result().toHex();
	HttpHandlerFile handler(testPath);
	QVERIFY(handler.handleRequest(createEtagRequest("GET",
                                     "/small", QByteArray(), "1.0", etag)));
	QVERIFY(response.startsWith("HTTP/1.0 304 Not Modified"));
}

Ehhez a createRequest mintájára írtam egy createEtagRequest segédmetódust, ami annyiban különbözik az előbbitől, hogy hozzáfűzi az "If-None-Match" nevű header-t és értékként odaadja neki a kapott etag-ot. A /small path-on készül egy 256 db kötőjelet tartalmazo adatfájl és elkészítjük ennek az md5sum-ját is, amit majd etag-ként használunk. A kérés pedig úgy működik, hogy mielőtt elküldi a fájl tartalmát a kliensnek, a szerver is néz a fájlon egy md5sum-ot és, ha megegyezik a header-ben kapottal (nem módosult azóta), akkor nem küldi át újra a fájlt, hanem csak egy 304-et. A teszt kimenetele (sikeres): Pillow First Third

Ezzel sikerült egy kicsit javítani a HttpHandler.cpp fedettségén, egészen pontosan +1 függvény és +9 sor került bejárásra:) LCOV Summary 2

⚠️ **GitHub.com Fallback** ⚠️