A port akkor tekinthető késznek, ha a viselkedése nemcsak kézzel kipróbálva, hanem dokumentált módon újra előállítható és ellenőrizhető. Ez az alfejezet ezért nem újabb nyelvi funkciót tanít, hanem a C++ záróprojekt köré épített build-, teszt- és mérési csomagot szerződésként írja le. A funkcionális magot és a szolgáltatás-életciklust a Task, queue és szolgáltatás-életciklus (39.3) rögzítette; a CTest, a negatív tesztek és a sanitizerek általános mintáit a CTest, negatív tesztek és sanitizer CI (38.4) tárgyalta. Itt ezeket a konkrét projektre alkalmazzuk, és kiegészítjük a C/C++ összehasonlítás mérési tervével.
A fejezet állításai eszközök dokumentált lehetőségeire támaszkodnak, de nem jelentenek futtatási eredményt. A megadott parancsok a tanuló laborjában futtatandók; a compiler-, library- és CMake-verziót a projektprofilban kell rögzíteni, mert a konkrét kapcsolók és viselkedésük verziófüggőek. Ahol a szabvány és az implementáció eltérhet, azt külön jelölöm.
A projektprofil (project profile) azoknak a külső feltételeknek a rögzített halmaza, amelyek mellett a build és a teszt értelmezhető: operációs rendszer és architektúra, C- és C++-fordító pontos kiadása, standard library megvalósítása és verziója, CMake-verzió, generátor, build típus, nyelvi standard, optimalizálási szint, sanitizer-kapcsolók és a forrás commitja. A build provenance ennek géppel is olvasható lenyomata a build mellé írva. Nem azonos a bitre reprodukálható builddel: utóbbi azt jelenti, hogy azonos bemenetből bitre azonos artefaktum keletkezik, ami erősebb és külön infrastruktúrát igényel; ezzel a Release, debug symbol és reprodukálhatóság (38.6) foglalkozik. Itt a gyengébb, de napi gyakorlatban elegendő célt követjük: a build visszajátszható és a környezete utólag azonosítható legyen.
A CMake configure lépésében kiírható a provenance. Két részlet
könnyen elcsúszik. A compiler azonosítója
(CMAKE_CXX_COMPILER_ID) nem azonosítja a standard
könyvtárat: a Clang például libstdc++-szal és libc++-szal is dolgozhat.
Ezért a könyvtárat egy kis próbaprogram azonosítja, amely a ténylegesen
használt fordítási beállításokkal fordul és fut:
// cmake/stdlib_probe.cpp -- a ténylegesen használt C++ standard könyvtár
#include <cstdio>
#include <version>
int main()
{
#if defined(_LIBCPP_VERSION)
std::printf("libc++ %d", _LIBCPP_VERSION);
#elif defined(__GLIBCXX__)
std::printf("libstdc++ %d (release %d)", __GLIBCXX__, _GLIBCXX_RELEASE);
#elif defined(_MSVC_STL_VERSION)
std::printf("msvc-stl %d", _MSVC_STL_VERSION);
#else
std::printf("unknown");
#endif
}A másik csapda a konfigurációspecifikus változók neve. Ezek
nagybetűsek (CMAKE_CXX_FLAGS_RELWITHDEBINFO), ezért a
CMAKE_BUILD_TYPE értékét a lekérdezés előtt nagybetűssé
kell alakítani; egyébként a kiírt flag-lista éppen az optimalizálási és
NDEBUG-kapcsolókat hagyja ki. Többkonfigurációs
generátornál (Ninja Multi-Config, Visual Studio) a
konfiguráció a build idején dől el, ezért minden konfigurációhoz külön
fájl készül:
# cmake/provenance.cmake -- build provenance konfigurációnként
if(CMAKE_CROSSCOMPILING)
set(diag_cxx_stdlib "unknown (cross-compiling, nem futtatható próba)")
else()
try_run(diag_probe_run diag_probe_compiled
SOURCES "${CMAKE_CURRENT_LIST_DIR}/stdlib_probe.cpp"
CXX_STANDARD 20
RUN_OUTPUT_VARIABLE diag_cxx_stdlib)
if(NOT diag_probe_compiled OR NOT diag_probe_run EQUAL 0)
set(diag_cxx_stdlib "unknown (a próba nem futott)")
endif()
endif()
get_property(diag_multi GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
if(diag_multi)
set(diag_configs ${CMAKE_CONFIGURATION_TYPES})
else()
set(diag_configs "${CMAKE_BUILD_TYPE}")
endif()
if(NOT diag_configs)
set(diag_configs None) # nincs konfigurációspecifikus flag
endif()
foreach(cfg IN LISTS diag_configs)
string(TOUPPER "${cfg}" CFG) # a változónév nagybetűs: _RELWITHDEBINFO
file(WRITE "${CMAKE_BINARY_DIR}/build-provenance-${cfg}.txt"
"project=${PROJECT_NAME}
source_dir=${CMAKE_SOURCE_DIR}
cmake=${CMAKE_VERSION}
generator=${CMAKE_GENERATOR}
config=${cfg}
c_compiler=${CMAKE_C_COMPILER_ID} ${CMAKE_C_COMPILER_VERSION}
cxx_compiler=${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}
cxx_stdlib=${diag_cxx_stdlib}
system=${CMAKE_SYSTEM_NAME}
processor=${CMAKE_SYSTEM_PROCESSOR}
c_flags=${CMAKE_C_FLAGS} ${CMAKE_C_FLAGS_${CFG}}
cxx_flags=${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${CFG}}
")
endforeach()A gyökér CMakeLists.txt a project() után
include(cmake/provenance.cmake) sorral veszi fel.
Egykonfigurációs generátornál a fájl neve például
build-provenance-RelWithDebInfo.txt; konfiguráció nélkül
build-provenance-None.txt, üres konfigurációs
flag-listával. Keresztfordításnál a próba nem futtatható; ilyenkor a
mező ezt jelzi, és a könyvtárat a toolchain-fájlból kell rögzíteni.
Buildjegyzőkönyv (lab-gcc profil). A
mintát CMake 4.4.4 és GCC 13.3.0 mellett egy- és többkonfigurációs Ninja
generátorral is lefuttattuk. A RelWithDebInfo provenance
cxx_flags mezője (-O2 -g -DNDEBUG) megegyezett
a compile_commands.json fordítási parancsának kapcsolóival;
a cxx_stdlib mező értéke
libstdc++ 20240904 (release 13) volt. A
Ninja Multi-Config generátor a Debug,
Release és RelWithDebInfo konfigurációhoz
három külön fájlt írt, a megfelelő flagekkel. Clanggal, így a libc++-os
ágat, nem futtattuk.
Ez a fájl configure időben keletkezik, ezért a ténylegesen használt
flag-eket a CMake változóiból veszi. Ha a projekt külső változóból
(például környezeti változóból) módosítja a flag-eket, vagy targetenként
ad hozzá kapcsolókat, azt is rögzíteni kell; a fenti minta csak a
standard CMake-változókat fedi le, a target-szintű
target_compile_options értékeit a
compile_commands.json mellé mentésével lehet megőrizni. A
provenance-t a tesztriport mellé kell menteni, különben egy későbbi
hibaelemzésnél nem tudjuk megmondani, melyik fordítóval és milyen
optimalizálással született az eredmény.
A buildparancsok a projekt gyökeréből:
cmake -S . -B build -G Ninja \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++
cmake --build build --parallel
ctest --test-dir build --output-on-failureA RelWithDebInfo és a Release konfiguráció
NDEBUG-ot definiál, ezért a tesztek ellenőrzései nem
épülhetnek assert-re; a projekt tesztjei ezért saját,
mindig lefutó CHECK makrót használnak (lásd a CMake capstone targetjeit). A
RelWithDebInfo a méréshez és a hibakereséshez is
használható kompromisszum, de a teljesítményméréshez külön, rögzített
Release profilt kell használni, és a sanitizeres futásokat
külön buildkönyvtárban kell tartani. A C és C++ fordítót nem elég a
CC/CXX környezeti változóra bízni: a CMake
cache a első configure után megőrzi a választást, ezért a profilban
explicit módon adjuk meg.
A target-based CMake lényege, hogy a fordítási beállítások ahhoz a targethez tartoznak, amelynek a kódjára vonatkoznak, és nem globális változóként terjednek szét. A warning kapu külön interfész targetbe kerül:
add_library(diag_warnings INTERFACE)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(diag_warnings INTERFACE
-Wall -Wextra -Wpedantic -Wconversion -Wshadow -Werror)
else()
message(WARNING "Ismeretlen compiler: a warning kapu nem aktív")
endif()A -Wconversion hasznos a méret- és előjeles konverziók
megfogására, de zajos lehet generált és harmadik féltől származó
fejlécekben. Ezért a saját targetek linkeljék a
diag_warnings-t, a külső függőségek fejléceit pedig
SYSTEM include-ként kell felvenni, hogy a warningok ne a mi
kapunkat terheljék. A -Werror csak akkor véd, ha a
figyelmeztetéskészlet a rögzített compilerverzióhoz kötött; új compiler
új figyelmeztetéseket hozhat, ezért a warning kaput a projektprofil
részeként kell kezelni, nem örök érvényű szabályként.
A target-gráf a portban három réteget tartalmaz: a C++ magot
(diagcore_core, forrása src/diag_core.cpp), a
stabil C API-t (diagcore_c_api, forrása
src/diag_c_api.cpp, nyilvános fejléce a változatlan
include/diag.h) és a futtatható klienst
(diagcore_cli, a 17.2 C17-ben
fordított src/main.c fájlja). A forrásokat és a
megfigyelhető viselkedést a portterv és a C++ core rögzíti; a CLI felülete a projektszerződés szerinti
diag [FILE|-]. A C API target csak a C++ magra linkel, a C
kliensek csak a C API-ra. Ez a linkhatár az, amit a Stabil shared library határ és verziózás (36.3)
szerződése rögzít. A warning targetet mindhárom saját target linkeli, de
a C API fejlécét C fordítóval is le kell fordítani, különben a
extern "C"-hibák rejtve maradnak.
A tesztkészletet érdemes címkékkel (CTest LABELS)
kategóriákra bontani, mert a gyors és a lassú teszteket más-más
gyakorisággal futtatjuk. Négy kategória elegendő: unit
(egyedi függvények, értéktípusok), golden (rögzített
bemenet–kimenet párok), negative (dokumentált elutasítás)
és abi (C API/FFI smoke). A fuzz és a mérés külön, nem
CTest-vezérelt folyamat.
A golden corpus olyan változatlan bemeneti fájlok és elvárt kimenetek halmaza, amelyekhez a parser és a szolgáltatás viselkedését hasonlítjuk. A golden teszt akkor használható, ha a kimenet determinisztikus, vagy ha a nem determinisztikus részeket (időbélyeg, PID, allokációs cím, sorrend) normalizáljuk. A normalizálás nem csalás, hanem a szerződés része: rögzíteni kell, mely mezők számítanak és melyek nem.
add_executable(diagcore_tests tests/test_core.cpp)
target_link_libraries(diagcore_tests PRIVATE diagcore_core diag_warnings)
add_test(NAME unit_core COMMAND diagcore_tests)
set_tests_properties(unit_core PROPERTIES LABELS "unit")
add_executable(spec_examples tests/spec_examples.c)
target_link_libraries(spec_examples PRIVATE diagcore_c_api)
add_test(NAME spec_examples COMMAND spec_examples)
set_tests_properties(spec_examples PROPERTIES LABELS "golden")
# Golden, csonkolt és használati hibás esetek: kilépési kód,
# teljes stdout és teljes stderr összevetése (lásd 17.2).
add_test(NAME cli_contract
COMMAND sh tests/cli_args.sh $<TARGET_FILE:diagcore_cli>
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
set_tests_properties(cli_contract PROPERTIES LABELS "golden;negative")A tesztforrások a projektszerződés, a 17.2 labor és a C++ core
fájljai. A cli_args.sh a golden bemenetet, a csonkolt
bemenetet, a túl sok argumentumot és a nem létező fájlt is lefuttatja,
és mindegyiknél a pontos kilépési kódot, stdoutot és stderrt ellenőrzi;
a negatív esetek ezért nem WILL_FAIL-re épülnek. A C és C++
CLI differenciális összevetése (portterv,
tests/diff_c_cxx.sh) a C referenciabuild targetjét is
igényli, ezért külön CI-lépés.
A WILL_FAIL önmagában csak azt ellenőrzi, hogy a
folyamat nem nulla visszatérési kóddal áll le. Nem bizonyítja, hogy a
helyes hibakódot adta, és nem zárja ki, hogy összeomlás miatt tért
vissza hibásan. Ezért a negatív teszthez olyan tesztprogram kell, amely
a dokumentált hibakódot is ellenőrzi; a fenti cli_contract
ilyen. A golden teszteknél ugyanez a helyzet: a fájlösszehasonlítás
akkor jó, ha a normalizálás és az elvárt fájl verziója is a repóban
van.
A fuzz célja itt nem a helyes parse bizonyítása, hanem a memóriabiztonság és a terminálás: tetszőleges byte-sorozatra a parser vagy elfogad, vagy dokumentált hibával elutasít, de nem olvas ki a pufferből, nem függeszti fel a szálat, és nem fogyaszt korlátlan memóriát. A libFuzzer target csak Clang alatt és külön buildben kapcsolható be:
option(DIAGCORE_ENABLE_FUZZING "libFuzzer target" OFF)
if(DIAGCORE_ENABLE_FUZZING)
add_executable(diagcore_parser_fuzz fuzz/parser_fuzz.cpp)
target_link_libraries(diagcore_parser_fuzz PRIVATE diagcore_core)
target_compile_options(diagcore_parser_fuzz PRIVATE
-fsanitize=fuzzer,address -fno-omit-frame-pointer)
target_link_options(diagcore_parser_fuzz PRIVATE
-fsanitize=fuzzer,address)
endif()A fuzz target a LLVMFuzzerTestOneInput belépési pontot
használja, és a corpus könyvtárat is át kell adni neki. A futtatás:
cmake -S . -B build-fuzz -G Ninja \
-DCMAKE_CXX_COMPILER=clang++ \
-DDIAGCORE_ENABLE_FUZZING=ON \
-DCMAKE_BUILD_TYPE=Debug
cmake --build build-fuzz --parallel
./build-fuzz/diagcore_parser_fuzz -max_total_time=60 corpus/Az allokációs hiba tesztelése azt ellenőrzi, hogy a kód minden
allokációs ponton képes visszajelzni a hibát, nem hagy maga után
lezáratlan erőforrást, és nem hagy félkész objektumot. A port core-ja
nem allokál; a C API egyetlen allokációs pontja a
dg_create, amelynek hibáját a hívó
dg_allocator callbackjével injektáljuk (lásd az FFI smoke
tesztet lent). A dokumentált eredmény: a dg_create
NULL-t ad, a CLI pedig ERR code=alloc
diagnosztikával, 5-ös kilépési kóddal áll le. Ha a platformadapter
std::pmr tárolót használ, ott egy olyan
std::pmr::memory_resource jól használható, amely adott
számú allokáció után std::bad_alloc-ot dob:
#include <cstddef>
#include <memory_resource>
#include <new>
struct FailingResource final : std::pmr::memory_resource {
std::size_t remaining;
explicit FailingResource(std::size_t n) : remaining(n) {}
void* do_allocate(std::size_t bytes, std::size_t align) override {
if (remaining == 0) throw std::bad_alloc{};
--remaining;
return std::pmr::new_delete_resource()->allocate(bytes, align);
}
void do_deallocate(void* p, std::size_t bytes, std::size_t align) override {
std::pmr::new_delete_resource()->deallocate(p, bytes, align);
}
bool do_is_equal(
const std::pmr::memory_resource& other) const noexcept override {
return this == &other;
}
};A teszt a FailingResource-ot úgy állítja be, hogy az
első néhány allokáció sikerüljön, majd a következő dobjon. Ezután
ellenőrizni kell, hogy a hívó a dokumentált hibát kapja, a destruktorok
lefutottak, és a C API határán nem szivárgott ki C++ kivétel. Új C
hibakód, például egy NOMEM érték bevezetése megfigyelhető
változás lenne, ezért csak külön migrációként jöhetne szóba; a kanonikus
szerződésben az allokációs hiba jelzése a NULL handle.
A sanitizereket nem egyetlen buildben kell futtatni. Az AddressSanitizer és az UndefinedBehaviorSanitizer kombinálható, a ThreadSanitizer viszont külön buildet igényel, mert az ASan és a TSan memóriahozzáférési modellje nem kompatibilis. A mátrix célja, hogy minden hibamódhoz legyen legalább egy futás, és a futások eredménye a provenance-szel együtt maradjon meg.
| Build | Kapcsoló | Mit fed | Mit nem fed |
|---|---|---|---|
build-asan |
-fsanitize=address,undefined |
buffer túlcsordulás, use-after-free, néhány UB | adatverseny, logikai hiba |
build-tsan |
-fsanitize=thread |
adatverseny, szinkronizációs hiba | memóriahiba, UB |
build-debug |
nincs sanitizer | teljes debugger-láthatóság | rejtett memóriahiba |
build-release |
nincs sanitizer | teljesítmény és bináris méret | a legtöbb memóriahiba |
A sanitizeres futás nem helyettesíti sem a determinisztikus tesztet, sem a kódolvasást. Az ASan nem találja meg a nem tűnt hibát, a TSan pedig csak a ténylegesen megtörtént, dinamikusan észlelt versenyhelyzeteket jelzi; egy ritkán előforduló logikai race rejtve maradhat. Ezért a TSan-t a szálas szolgáltatás-életciklus és a queue tesztjein kell futtatni, ismételt és terhelt forgatókönyvvel, nem egyetlen rövid futással.
A C API akkor kompatibilis, ha a fejlécet C fordító is elfogadja, a szimbólumok neve és hívási konvenciója a dokumentált ABI szerint alakul, és a C++ kivételek nem lépnek át a határon. A minimális ellenőrzés egy C nyelven fordított kliens, amely a nyilvános fejlécet include-olja és linkel a könyvtárhoz:
/* tests/c_api_smoke.c -- C fordítóval fordított kliens a C++ könyvtárhoz */
#include "diag.h"
#include <stdio.h>
#include <stdlib.h>
#define CHECK(condition) do { \
if (!(condition)) { \
(void)fprintf(stderr, "CHECK failed at line %d\n", __LINE__); \
exit(EXIT_FAILURE); \
} \
} while (0)
static void *refuse(size_t size, void *context)
{
(void)size;
++*(int *)context;
return NULL; /* allokációs hiba injektálása */
}
static void never(void *pointer, void *context)
{
(void)pointer;
(void)context;
abort(); /* sikertelen create nem szabadít */
}
int main(void)
{
static const unsigned char payload[] = "cpu=42\n";
dg_parser *p = dg_create(NULL);
CHECK(p != NULL);
CHECK(dg_feed(p, (dg_view){ payload, sizeof payload - 1 }));
CHECK(dg_finish(p));
CHECK(dg_get_report(p).records == 1 && dg_get_report(p).sum == 42);
dg_destroy(p);
int calls = 0;
dg_allocator failing = { refuse, never, &calls };
CHECK(dg_create(&failing) == NULL);
CHECK(calls == 1);
return 0;
}add_executable(c_api_smoke tests/c_api_smoke.c)
target_link_libraries(c_api_smoke PRIVATE diagcore_c_api diag_warnings)
set_target_properties(c_api_smoke PROPERTIES C_STANDARD 17 C_EXTENSIONS OFF)A lényeg, hogy ezt a targetet C fordító fordítsa, ne C++ fordító. Ha
C++ fordítóval fordítjuk, a extern "C" hiánya nem derül ki,
és a teszt hamis biztonságot ad. Az FFI smoke teszt nem teljes
ABI-kompatibilitási teszt; a verziózott szimbólumok, a struct-layout és
a régi binárisokkal való kompatibilitás ellenőrzése az ABI- és FFI-labor, kompatibilitási teszt (36.5)
feladata. Itt csak a határ átjárhatóságát igazoljuk. A tesztet GCC
13.3.0-val C17-ben fordítottuk, és a C++ könyvtárhoz (g++
driverrel), valamint ellenőrzésképpen a C referenciakönyvtárhoz is
linkelve lefuttattuk, mindkét esetben sikerrel, a C++ változatot
ASan/LSan/UBSan builddel is; az nm a C++ könyvtárban a
dg_* szimbólumokat név-dekoráció nélkül, C kötéssel
mutatta. A CMake-fragmentumokat ebben a jegyzőkönyvben nem futtattuk,
mert CMake nem állt rendelkezésre.
A mérés DBEN nem az a kérdés, hogy „gyorsabb-e a C++”, hanem hogy a port ugyanazon a workloadon milyen költséget mutat, és ez hogyan viszonyul a rögzített baseline-hoz. A workload a feldolgozandó bemenet és a futtatási mód együttese; a metrika a mért mennyiség és mértékegysége; a mérési terv rögzíti a kettőt, az ismétlésszámot, a statisztikát és az elfogadási feltételt.
| Metrika | Eszköz | Egység | Kontroll |
|---|---|---|---|
| allokációk száma | számláló allocator vagy pmr resource | darab/futás | azonos bemenet, azonos élettartam |
| csúcs memória | /usr/bin/time -v vagy getrusage |
KiB | külön folyamat, hideg indítás |
| latency medián/p95 | saját harness, monotonic óra | µs | warmup után, 30 ismétlés |
| throughput | feldolgozott byte / falóidő | MB/s | azonos corpus, azonos párhuzamosság |
| bináris méret | size, stat |
byte | stripped és unstripped külön |
| build idő | time cmake --build |
s | tiszta buildkönyvtár |
A mérési harnessnek ugyanazt a publikus API-t kell hívnia, mint a valós kliensnek, különben a mérés a benchmarkra optimalizált mellékutat mér. A bemenetet fájlból vagy memóriából kell betölteni, és a betöltés idejét külön kell mérni vagy ki kell zárni. A statisztikát előre rögzíteni kell: például medián és p95, nem pedig a legjobb futás. A warmup szükséges, mert az első futás tartalmazza a page faultokat, a dinamikus loader munkáját és a cache feltöltését.
A mérés eredményét a provenance-szel együtt kell menteni, és a riportban külön kell jelölni, hogy a szám mért érték, becslés vagy célérték. A fejezet nem állít konkrét mért számot; a fenti táblázat a mérési kötelezettséget rögzíti, nem annak eredményét.
Az összehasonlítás csak akkor érvényes, ha a két implementáció ugyanazt a bemenetet ugyanazzal a feldolgozási szerződéssel dolgozza fel. Ellenőrizni kell, hogy a C változat és a C++ változat ugyanazt a corpus-t kapja, ugyanazt a kimenetet állítja elő, és a mérés ugyanazon a ponton történik. Gyakori hiba, hogy a C++ változat több másolást végez a kényelmesebb értékszemantika miatt, vagy a C változat a benchmarkban elkerüli a valós kliensútvonalat. Az összehasonlításnak ezért legalább három szinten kell futnia: allokációszám, latency és bináris méret. A throughput csak akkor értelmezhető, ha a párhuzamosság és a bufferelés azonos.
A mérési terv elfogadási feltételeit a csapat rögzíti. Például: a funkcionális egyenértékűség kötelező; az allokációszám nem nő a baseline-hoz képest; a p95 latency nem nő 10%-nál többet; a stripped bináris méret nem haladja meg a rögzített keretet. Ezek célértékek, nem mért eredmények, és a port akkor tekinthető késznek, ha a mért értékek ezeket teljesítik a dokumentált profilon.
Az egyik leggyakoribb hiba, hogy a warning kapu minden targetre
érvényes, beleértve a harmadik féltől származó fejléceket is; ilyenkor a
build a függőség miatt áll meg. Javítás: SYSTEM include
vagy külön interfész target. A második, hogy az ASan és a TSan
ugyanabban a buildben szerepel; ez nem támogatott, és félrevezető
hibákat ad. Javítás: külön buildkönyvtár és külön CI-futás. A harmadik,
hogy a golden teszt nem determinisztikus kimenetre hasonlít; javítás:
normalizálás vagy a teszt átalakítása determinisztikus részhalmazra.
További tipikus hiba, hogy a fuzz target nem a valós parser belépési pontját hívja, hanem egy egyszerűsített másolatot; így a fuzz nem a szállított kódot vizsgálja. Az allokációs hiba tesztje gyakran csak azt nézi, hogy a folyamat nem omlik össze, de nem ellenőrzi a leaket és a dokumentált hibakódot. Az FFI smoke tesztet C++ fordítóval fordítják, amitől a C-kompatibilitás nem derül ki. A benchmarkot optimalizálatlan buildben futtatják, vagy a mérés a konstrukciót méri a feldolgozás helyett. Végül a provenance hiányzik, ezért a mérési eredmény később nem reprodukálható.
-Werror minden targetre globálisan be van
állítva, és egy harmadik féltől származó header figyelmeztetést okoz.
Javítsd a buildet a warning kapu gyengítése nélkül.a=0\n rekord és egy
további bájt) a CLI-vel elutasíttat, és ellenőrzi a dokumentált kilépési
kódot és diagnosztikát. Magyarázd el, miért nem elég a
WILL_FAIL.dg_allocator callbackjével injektál
hibát, és ellenőrzi, hogy a hiba a határon a dokumentált módon
(NULL handle) jelenik meg, a felszabadító callback nem fut,
és nincs leak.1. megoldás. A harmadik féltől származó fejlécet
SYSTEM include-ként kell felvenni, például
target_include_directories(diagcore_core SYSTEM PUBLIC ...),
vagy a külső függőséget külön interfész targetbe kell tenni, amely nem
linkeli a diag_warnings-t. Így a saját kód továbbra is
-Werror alatt marad, a külső header pedig nem töri meg a
buildet. Ellenőrzési feltétel: a saját forrásokban szándékosan
bevezetett figyelmeztetés továbbra is hibát okoz, a külső header viszont
nem.
2. megoldás. A WILL_FAIL csak azt
vizsgálja, hogy a visszatérési kód nem nulla, ezért egy összeomlás vagy
rossz hibakód is „sikeres” negatív teszt lenne. Helyette a tesztszkript
maga ellenőrzi a kilépési kódot és a pontos diagnosztikát, a határ
mindkét oldalán:
#!/bin/sh
# tests/reject_oversized.sh DIAG -- a limit utáni első bájt elutasítása
set -u
diag=$1
dir=$(mktemp -d) || exit 2
trap 'rm -rf "$dir"' EXIT
awk 'BEGIN { for (i = 0; i < 262144; i++) printf "a=0\n" }' \
> "$dir/limit.in"
cp "$dir/limit.in" "$dir/over.in"
printf 'b' >> "$dir/over.in"
"$diag" "$dir/limit.in" > "$dir/ok.out" || exit 1
test "$(cat "$dir/ok.out")" = 'OK records=262144 sum=0 bytes=1048576' ||
exit 1
rc=0
"$diag" "$dir/over.in" > "$dir/over.out" 2> "$dir/over.err" || rc=$?
test "$rc" -eq 4 || exit 1
test ! -s "$dir/over.out" || exit 1
test "$(cat "$dir/over.err")" = 'ERR code=limit offset=1048576'add_test(NAME reject_oversized_input
COMMAND sh tests/reject_oversized.sh $<TARGET_FILE:diagcore_cli>
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
set_tests_properties(reject_oversized_input PROPERTIES LABELS "negative")A várt érték a projektszerződésből
következik: pontosan 1 048 576 bájt még elfogadható, a következő bájt
limit hibát ad, az offset 1 048 576, a kilépési kód 4, és a
stdout üres. Ellenőrzési feltétel: a limitméretű bemenet sikeres, a
plusz egy bájtos elutasítva, és a folyamat nem omlik össze. A szkriptet
a C és a C++ CLI-vel is lefuttattuk (GCC 13.3.0), mindkettővel
sikeresen; ellenpróbaként a cat paranccsal futtatva hibát
jelzett.
3. megoldás. Az allokátor első hívása
NULL-t ad és megszámolja a hívást; a felszabadító callback
abort()-ot hív, mert sikertelen dg_create után
nincs mit felszabadítani. A teszt ellenőrzi, hogy a
dg_create NULL-t ad, és az allokátort pontosan
egyszer hívta. A C++ wrapperben a placement new csak sikeres foglalás
után fut, így félkész objektum sem keletkezik. A fenti
tests/c_api_smoke.c második fele pontosan ez a teszt.
Ellenőrzési feltétel: nincs kivétel a C határon, a felszabadító nem fut,
és a LeakSanitizer nem jelez.
4. megoldás. A workload legyen ugyanaz a golden corpus és egy szintetikus stream, azonos párhuzamossággal. A metrikák: allokációszám, p95 latency, throughput és stripped bináris méret. Az ismétlésszám legalább 30, warmup után, külön folyamatban. A mérésből nem lehet azt kijelenteni, hogy „a C++ minden esetben gyorsabb” vagy hogy „a C++ memóriabiztonságosabb”; csak azt, hogy az adott profilon, adott workloadon milyen értékek adódtak. A funkcionális egyenértékűséget a golden tesztek igazolják, a teljesítményt a mérési harness; a kettőt nem szabad összekeverni.
A C++ záróprojekt build-, teszt- és mérési csomagja akkor elfogadható, ha a projektprofil és a provenance rögzíti a környezetet, a target-based CMake a saját kódra érvényesíti a warning kaput, a CTest kategóriákra bontja a teszteket, a golden corpus normalizált és determinisztikus, a fuzz és az allokációs hiba teszt a valós belépési pontot vizsgálja, az ASan/UBSan és a TSan külön fut, a C API-t C fordítóval is lefordítjuk, és a mérési terv előre rögzíti a metrikákat, a kontrollokat és az elfogadási feltételeket. A port funkcionális egyenértékűsége és a teljesítménye csak ezekkel a feltételekkel válik ellenőrizhető állítássá.