Files
playground/libpda/tests/basics/driver.cpp
T
paulhorn db41258d16 feat: PDA als libpda + pda nach P1204R0
Ersetzt das Geruest (counter/notebook) durch ein echtes Beispiel und teilt
es in zwei eigenstaendige Projekte, wie P1204R0 es fuer Bibliothek plus
Programm verlangt.

libpda: textfile (C, Datei-I/O und Feld-Escaping), calculator
(Recursive-Descent-Parser mit std::expected), contact, editor, explorer.
Exportiert pda::pda ueber install(EXPORT) und ist per find_package(pda)
benutzbar.

pda: Shell ohne Ein-/Ausgabe (execute() gibt Text zurueck, deshalb ohne
Terminal testbar), REPL und Stapelbetrieb in main.cpp.

examples/consumer und tools/check-install.sh pruefen die Export-Kette
Ende-zu-Ende: installieren, dann ein fremdes Projekt dagegen bauen.

docs/CMAKE.md erklaert das Target-Modell, PUBLIC/PRIVATE/INTERFACE,
Generator-Ausdruecke, install/export und Symbolsichtbarkeit an diesem
Projekt.

71 Tests gruen unter Homebrew-clang 22, Apple clang und GCC 16, statisch
und shared, mit und ohne ASan/UBSan.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 16:39:35 +02:00

157 lines
4.8 KiB
C++

/* tests/basics/driver.cpp
* Integrationstest der Bibliothek (P1204R0 Regel 7.2).
*
* Kein Test-Framework: ein Integrationstest soll ein eigenstaendiges Programm
* sein, das ohne Argumente laeuft und ueber seinen Exit-Code Auskunft gibt.
* Er benutzt nur <libpda/...> -- genau das, was ein Benutzer bekommt.
*
* Der Test bildet einen kleinen echten Ablauf ab: Kontakte anlegen, sichern,
* in einem zweiten Buch laden, Notizen schreiben und ueber den Explorer
* nachsehen, dass die Dateien wirklich da sind.
*/
#include <cstdio>
#include <exception>
#include <filesystem>
#include <string>
#include <libpda/calculator.hpp>
#include <libpda/contact.hpp>
#include <libpda/editor.hpp>
#include <libpda/explorer.hpp>
#define CHECK(cond) \
do \
{ \
if (!(cond)) \
{ \
std::fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); \
return 1; \
} \
} while (0)
namespace fs = std::filesystem;
namespace
{
int
contacts_survive_a_save_and_load_cycle(const fs::path& dir)
{
const fs::path file = dir / "kontakte.tsv";
pda::ContactBook book;
CHECK(book.add(pda::Contact{"Anna Schmidt", "0151", "anna@example.org", "Schwester"}));
CHECK(book.add(pda::Contact{"Bert Meier", "0170", "bert@firma.de", "Kollege\tAbt. 4"}));
CHECK(book.size() == 2U);
CHECK(book.save(file).has_value());
pda::ContactBook reloaded;
CHECK(reloaded.load(file).has_value());
CHECK(reloaded.size() == 2U);
const pda::Contact* bert = reloaded.find("Bert Meier");
CHECK(bert != nullptr);
/* Das TAB in der Notiz ist der Grund fuer das Escaping in textfile.c --
* ohne das haette der Datensatz beim Laden fuenf Felder statt vier. */
CHECK(bert->note == "Kollege\tAbt. 4");
return 0;
}
int
notes_round_trip_through_a_file(const fs::path& dir)
{
const fs::path file = dir / "notizen.txt";
pda::TextBuffer buffer;
buffer.append("Einkaufen");
buffer.append("Zahnarzt anrufen");
CHECK(buffer.modified());
CHECK(buffer.save(file).has_value());
CHECK(!buffer.modified());
pda::TextBuffer reloaded;
CHECK(reloaded.load(file).has_value());
CHECK(reloaded.line_count() == 2U);
CHECK(*reloaded.line(2U) == "Zahnarzt anrufen");
return 0;
}
int
the_explorer_sees_what_we_wrote(const fs::path& dir)
{
const pda::Explorer explorer{dir};
const auto entries = explorer.list();
CHECK(entries.has_value());
CHECK(entries->size() == 2U);
/* Alphabetisch: kontakte.tsv vor notizen.txt. */
CHECK((*entries)[0].name == "kontakte.tsv");
CHECK((*entries)[1].name == "notizen.txt");
CHECK((*entries)[0].size > 0U);
return 0;
}
int
the_calculator_agrees_with_arithmetic()
{
const auto value = pda::evaluate("(2 + 3) * 4 - 10 / 2");
CHECK(value.has_value());
CHECK(*value == 15.0);
const auto broken = pda::evaluate("2 +");
CHECK(!broken.has_value());
CHECK(!pda::format_error(broken.error()).empty());
return 0;
}
} /* namespace */
int
main()
{
/* main darf nichts nach aussen werfen: eine entwichene Exception waere
* ein std::terminate, und CTest zeigte nur "Subprocess aborted" ohne
* jeden Hinweis, welcher Fall gescheitert ist. */
try
{
/* Eigenes Arbeitsverzeichnis, damit der Test nichts findet, was er nicht
* selbst angelegt hat -- und nichts hinterlaesst. */
const fs::path dir = fs::current_path() / "integration.basics.tmp";
std::error_code ec;
fs::remove_all(dir, ec);
fs::create_directories(dir, ec);
if (ec)
{
std::fprintf(stderr, "FAIL: Arbeitsverzeichnis nicht anzulegen\n");
return 1;
}
int failures = 0;
failures += contacts_survive_a_save_and_load_cycle(dir);
failures += notes_round_trip_through_a_file(dir);
failures += the_explorer_sees_what_we_wrote(dir);
failures += the_calculator_agrees_with_arithmetic();
fs::remove_all(dir, ec);
if (failures != 0) return 1;
std::puts("integration/basics: ok");
return 0;
}
catch (const std::exception& error)
{
std::fprintf(stderr, "FAIL: unerwartete Exception: %s\n", error.what());
return 1;
}
}