// Shared correctness checks + benchmark plumbing for every step.
//
// The contract each step signs:
// 1. Provide parse_all_materialized(path) -> vector<vector<string>>
// (fields fully unescaped). It runs against edge.csv and must reproduce
// EXPECTED_EDGE exactly, or the program exits 1 before any benchmark.
// 2. Time its native parse of bench.csv and report through print_bench(),
// which emits one machine-readable line bench.sh can grep.
//
// This header also counts heap allocations by overriding global operator
// new/delete. That is how step 3 PROVES "zero-copy" instead of asserting it:
// parse the same file twice and show the second pass allocates nothing.
#pragma once
#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <new>
#include <string>
#include <vector>
#include <chrono>
// ---- allocation counter -----------------------------------------------------
// Single-TU programs, so defining these here is safe. Every heap allocation in
// the process ticks the counter -- there is nowhere for a hidden copy to hide.
inline std::atomic<unsigned long long> g_allocs{0};
void* operator new(std::size_t n) {
g_allocs.fetch_add(1, std::memory_order_relaxed);
if (void* p = std::malloc(n)) return p;
throw std::bad_alloc{};
}
void* operator new[](std::size_t n) { return operator new(n); }
void operator delete(void* p) noexcept { std::free(p); }
void operator delete[](void* p) noexcept { std::free(p); }
void operator delete(void* p, std::size_t) noexcept { std::free(p); }
void operator delete[](void* p, std::size_t) noexcept { std::free(p); }
inline unsigned long long allocs_now() { return g_allocs.load(std::memory_order_relaxed); }
// ---- edge.csv expectations ---------------------------------------------------
// The exact parse of edge.csv (written by gen_data.py): 7 records, 4 fields
// each, quotes stripped, doubled quotes collapsed, the embedded CRLF kept.
inline std::vector<std::vector<std::string>> expected_edge() {
return {
{"id", "name", "notes", "value"},
{"1", "plain", "simple", "3.14"},
{"2", "quoted, with comma", "line1\r\nline2", "2.71"},
{"3", "she said \"hi\"", "bare", "1"},
{"4", "", "", "0"},
{"5", " leading and trailing ", "x", "42"},
{"6", "end", "no-trailing-newline", "7"},
};
}
template <typename ParseAll>
bool run_edge_checks(const char* edge_path, ParseAll parse_all) {
auto got = parse_all(edge_path);
auto want = expected_edge();
if (got.size() != want.size()) {
std::fprintf(stderr, "EDGE FAIL: %zu records, want %zu\n", got.size(), want.size());
return false;
}
for (size_t r = 0; r < want.size(); ++r) {
if (got[r].size() != want[r].size()) {
std::fprintf(stderr, "EDGE FAIL: record %zu has %zu fields, want %zu\n",
r, got[r].size(), want[r].size());
return false;
}
for (size_t c = 0; c < want[r].size(); ++c) {
if (got[r][c] != want[r][c]) {
std::fprintf(stderr, "EDGE FAIL: [%zu][%zu] = \"%s\", want \"%s\"\n",
r, c, got[r][c].c_str(), want[r][c].c_str());
return false;
}
}
}
std::puts("edge.csv: all 7 records exact -- PASS");
return true;
}
// ---- benchmark plumbing --------------------------------------------------------
struct BenchResult {
double best_secs = 1e30;
unsigned long long rows = 0, fields = 0, byte_sum = 0;
unsigned long long last_pass_allocs = 0;
};
inline double now_secs() {
using namespace std::chrono;
return duration<double>(steady_clock::now().time_since_epoch()).count();
}
inline void print_bench(const char* step, const BenchResult& r, double file_mb) {
// human line
std::printf("%s: %.3f s | %.1f Mrows/s | %.0f MB/s | allocs last pass: %llu\n",
step, r.best_secs, r.rows / r.best_secs / 1e6, file_mb / r.best_secs,
r.last_pass_allocs);
// machine line for bench.sh
std::printf("BENCH step=%s secs=%.4f rows=%llu fields=%llu mbps=%.0f allocs=%llu bytesum=%llu\n",
step, r.best_secs, r.rows, r.fields, file_mb / r.best_secs,
r.last_pass_allocs, r.byte_sum);
}