Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 58 additions & 58 deletions laige-api.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/laige-core/include/laige/budget_harness.h
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@

#pragma once

#include <algorithm>
#include <chrono>
#include <cmath>
#include <cstddef>
Expand Down
12 changes: 10 additions & 2 deletions tests/api/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ function(laige_add_api_test name expect_exit root mode file)
" string(APPEND _problems \"output missing '<${_esc}'>; \")\n"
"endif()\n")
endforeach()
set(SCANNER "${CMAKE_BINARY_DIR}/bin/laige-api-scanner")
set(CMD "${_cmd}")
set(EXPECT_EXIT "${expect_exit}")
set(OUT_FILE "")
Expand All @@ -186,6 +185,13 @@ function(laige_add_api_test name expect_exit root mode file)
"${CMAKE_CURRENT_BINARY_DIR}/${name}.cmake" @ONLY)
add_test(NAME ${name} COMMAND ${CMAKE_COMMAND} -P
"${CMAKE_CURRENT_BINARY_DIR}/${name}.cmake")
# The scanner path is carried in the test environment, not in the
# command or baked into the script: $<TARGET_FILE> resolves per
# configuration under multi-config generators (the Windows VS layout
# puts the executable in bin/<Config>/), while a configure-time path
# only matches the single-config layout.
set_tests_properties(${name} PROPERTIES
ENVIRONMENT "LAIGE_API_SCANNER=$<TARGET_FILE:laige-api-scanner>")
endfunction()

# The scan test also compares the written manifest byte-for-byte against
Expand All @@ -194,7 +200,6 @@ set(_scan_out "${CMAKE_CURRENT_BINARY_DIR}/fixture-api.json")
set(_scan_root "${FX}/api")
set(_scan_expected "${CMAKE_CURRENT_BINARY_DIR}/expected-fixture-api.json")
set(_cmd "\"--root\" \"${_scan_root}\" \"--out\" \"${_scan_out}\"")
set(SCANNER "${CMAKE_BINARY_DIR}/bin/laige-api-scanner")
set(CMD "${_cmd}")
set(EXPECT_EXIT "0")
set(OUT_FILE "${_scan_out}")
Expand All @@ -203,6 +208,9 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/expect-api-result.cmake.in"
"${CMAKE_CURRENT_BINARY_DIR}/api-fixture-scan.cmake" @ONLY)
add_test(NAME api-fixture-scan COMMAND ${CMAKE_COMMAND} -P
"${CMAKE_CURRENT_BINARY_DIR}/api-fixture-scan.cmake")
# Same ENVIRONMENT wiring as laige_add_api_test (see its comment).
set_tests_properties(api-fixture-scan PROPERTIES
ENVIRONMENT "LAIGE_API_SCANNER=$<TARGET_FILE:laige-api-scanner>")

laige_add_api_test(api-check-fresh 0 "${FX}/api" check
"${CMAKE_CURRENT_BINARY_DIR}/expected-fixture-api.json"
Expand Down
15 changes: 14 additions & 1 deletion tests/api/expect-api-result.cmake.in
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,22 @@
# The script exits non-zero (failing the CTest test) on any mismatch and
# prints the full scanner output for diagnosis.

# The scanner executable arrives through the test's ENVIRONMENT property
# (LAIGE_API_SCANNER = $<TARGET_FILE:laige-api-scanner>, set in
# tests/api/CMakeLists.txt): CMake resolves it per configuration under
# multi-config generators (bin/<Config>/laige-api-scanner.exe on the
# Windows VS generator), where a path baked in at configure time only
# matches the single-config layout. Read it at test time, never at
# configure time ($ENV{...} in a configure-time string expands early).
set(_scanner "$ENV{LAIGE_API_SCANNER}")
if(_scanner STREQUAL "")
message(FATAL_ERROR
"LAIGE_API_SCANNER is not set: the test's ENVIRONMENT property must "
"carry the scanner path (tests/api/CMakeLists.txt)")
endif()
set(_cmd @CMD@)
execute_process(
COMMAND @SCANNER@ ${_cmd}
COMMAND "${_scanner}" ${_cmd}
RESULT_VARIABLE _rc
OUTPUT_VARIABLE _out
ERROR_VARIABLE _err
Expand Down
22 changes: 19 additions & 3 deletions tests/laige-core/budget_harness_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,26 @@ std::string WriteTempJson(const char* name, const std::string content) {

// The repo-root budgets.json, wired by CTest (ENVIRONMENT
// LAIGE_BUDGETS_PATH); the fallback covers running the binary from the
// source root by hand.
const char* BudgetsFilePath() {
// source root by hand. MSVC deprecates plain getenv (C4996, fatal under
// /WX); getenv_s has the same lookup semantics (CPP-009, pattern:
// logging.cpp).
std::string BudgetsFilePath() {
#if defined(_MSC_VER)
// Largest environment value the suite reads (the budgets file path;
// fits far inside the bound). Named per CORE-005; a value beyond it is
// treated as unset (the documented fallback applies). MSVC-only for the
// same reason as above (CORE-010: no unused symbols under -Werror).
constexpr std::size_t kEnvValueMaxBytes = 4096;
char buf[kEnvValueMaxBytes];
std::size_t len = 0;
if (getenv_s(&len, buf, sizeof(buf), "LAIGE_BUDGETS_PATH") != 0)
return "budgets.json";
return (len > 0) ? std::string(buf, len) : std::string("budgets.json");
#else
const char* env = std::getenv("LAIGE_BUDGETS_PATH");
return (env != nullptr && env[0] != '\0') ? env : "budgets.json";
return (env != nullptr && env[0] != '\0') ? std::string(env)
: std::string("budgets.json");
#endif
}

void ExpectMalformedFile(const std::string& path) {
Expand Down
27 changes: 25 additions & 2 deletions tools/api/laige-api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
// --root REPO_ROOT Repository root (default: ".").
// --out FILE Write the regenerated manifest to FILE.
// --check FILE Regenerate in memory and compare with FILE:
// byte-identical -> exit 0; stale -> exit 1 with a
// identical -> exit 0 (CRLF line endings in FILE are
// normalized to LF first - the canonical form is LF,
// see .gitattributes); stale -> exit 1 with a
// symbol-level diff; unreadable/invalid FILE -> exit 2.
//
// Exit codes: 0 = OK · 1 = stale manifest (--check) · 2 = error (usage,
Expand Down Expand Up @@ -1722,6 +1724,26 @@ int runCheck(const std::string& checkFile, const std::string& manifestText,
std::cout << "laige-api: error: " << err << std::endl;
return 2;
}
// Line-ending normalization: the manifest is canonically LF (the
// serializer emits LF; the repo's .gitattributes pins eol=lf on every
// checkout). CRLF can reach a checked-in copy only as a Windows tooling
// artifact (text-mode CMake file(WRITE) test fixtures, editor saves), so
// normalize CRLF -> LF before the byte compare. A lone \r is NOT
// normalized, so a genuinely corrupted file still fails (CORE-008).
{
std::string norm;
norm.reserve(oldText.size());
for (size_t i = 0; i < oldText.size(); ++i) {
if (oldText[i] == '\r' && i + 1 < oldText.size() &&
oldText[i + 1] == '\n') {
norm += '\n'; // fold the CRLF pair to one LF
++i; // the paired \n belongs to the fold - skip it
} else {
norm += oldText[i];
}
}
oldText = std::move(norm);
}
if (oldText == manifestText) {
std::cout << "laige-api: OK — " << checkFile
<< " is up to date (" << symbols.size() << " symbol(s) from the "
Expand Down Expand Up @@ -1828,7 +1850,8 @@ void printUsage(std::ostream& os) {
<< " --root REPO_ROOT repository root (default: \".\")\n"
<< " --out FILE write the regenerated manifest to FILE\n"
<< " --check FILE compare the regenerated manifest with FILE:\n"
<< " exit 0 if byte-identical, exit 1 if stale\n"
<< " exit 0 if identical (CRLF normalized to LF),\n"
<< " exit 1 if stale\n"
<< "\n"
<< "Exit codes: 0 = OK · 1 = stale manifest · 2 = error\n";
}
Expand Down
50 changes: 42 additions & 8 deletions tools/bench/laige-bench.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,41 @@ constexpr char kCompilerId[] = "unknown";
#define LAIGE_BENCH_BUILD_TYPE "unknown"
#endif

// --- Platform boundary (CPP-009, pattern: logging.cpp) ----------------------
//
// MSVC deprecates plain getenv/fopen (C4996, fatal under the engine /WX
// policy, NFR-8.10); the Windows branch uses the CRT's documented
// replacements with the same lookup/open semantics every other supported
// compiler provides. _fsopen(_SH_DENYNO) keeps plain-fopen sharing
// semantics (no _SH_SECURE re-open denial, see logging.cpp).

#if defined(_MSC_VER)
// Largest environment value this tool reads (a machine description or a
// budgets file path; both fit far inside the bound). Named per CORE-005;
// a value beyond it is treated as unset (the documented fallback applies).
// MSVC-only: getenv_s needs a caller-sized buffer, so the constant has no
// use outside this branch (CORE-010: no unused symbols under -Werror).
constexpr std::size_t kEnvValueMaxBytes = 4096;

std::string envValue(const char* name) {
char buf[kEnvValueMaxBytes];
std::size_t len = 0;
if (getenv_s(&len, buf, sizeof(buf), name) != 0) return {};
return std::string(buf, len);
}
std::FILE* openReportFile(const char* path, const char* mode) {
return ::_fsopen(path, mode, _SH_DENYNO);
}
#else
std::string envValue(const char* name) {
const char* v = std::getenv(name);
return (v != nullptr) ? std::string(v) : std::string();
}
std::FILE* openReportFile(const char* path, const char* mode) {
return std::fopen(path, mode);
}
#endif

} // namespace

int main(int argc, char** argv) {
Expand Down Expand Up @@ -201,7 +236,7 @@ int main(int argc, char** argv) {
// hardware/OS/compiler/build/workload). The operator may set
// LAIGE_BENCH_MACHINE for the machine line; the baseline document
// records the rest (docs/benchmarks/, M0-EXIT-01).
const char* envMachine = std::getenv("LAIGE_BENCH_MACHINE");
const std::string envMachine = envValue("LAIGE_BENCH_MACHINE");
const std::string build =
std::string(kCompilerId) + ", " + LAIGE_BENCH_BUILD_TYPE;

Expand All @@ -225,7 +260,7 @@ int main(int argc, char** argv) {
output += " build=";
output += build;
output += " machine=";
output += (envMachine != nullptr ? envMachine : "");
output += envMachine; // empty when the env var is unset
output += " warmup=";
output += std::to_string(cfg.warmup);
output += "\n";
Expand All @@ -234,10 +269,9 @@ int main(int argc, char** argv) {
// full AGENTS 12 report (pass/fail + before/after + statistics).
std::string budgetsPath = cfg.budgetsPath;
if (budgetsPath.empty()) {
const char* envPath = std::getenv("LAIGE_BUDGETS_PATH");
budgetsPath = (envPath != nullptr && envPath[0] != '\0')
? std::string(envPath)
: std::string("budgets.json");
const std::string envPath = envValue("LAIGE_BUDGETS_PATH");
budgetsPath =
envPath.empty() ? std::string("budgets.json") : envPath;
}
const laige::Result<laige::BudgetTable, laige::ErrorCode> table =
laige::loadBudgets(budgetsPath);
Expand All @@ -257,7 +291,7 @@ int main(int argc, char** argv) {
BudgetReportContext ctx;
ctx.workload = entry->workload.c_str();
ctx.build = build.c_str();
ctx.machine = envMachine != nullptr ? envMachine : "";
ctx.machine = envMachine.c_str(); // outlives the budgetCheck call below
ctx.warmup = static_cast<std::uint32_t>(cfg.warmup);

const laige::BudgetCheckResult check =
Expand All @@ -271,7 +305,7 @@ int main(int argc, char** argv) {
std::fflush(stdout);

if (!cfg.reportPath.empty()) {
std::FILE* f = std::fopen(cfg.reportPath.c_str(), "a");
std::FILE* f = openReportFile(cfg.reportPath.c_str(), "a");
if (f == nullptr) {
std::fprintf(stderr, "laige-bench: cannot open report file '%s'\n",
cfg.reportPath.c_str());
Expand Down
Loading