fold/tests/unit/test_cost_summary.cpp
2026-06-12 13:25:11 -04:00

112 lines
5.2 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <nlohmann/json.hpp>
#include <string>
#include "fold/symbolic/cost_summary.hpp"
TEST_CASE("cost summary enum names are stable for JSON sidecars", "[cost-summary]") {
using namespace fold::symbolic;
REQUIRE(std::string(costSummaryConfidenceName(CostSummaryConfidence::Proved)) == "proved");
REQUIRE(std::string(costSummaryConfidenceName(CostSummaryConfidence::Latent)) == "latent");
REQUIRE(std::string(costSummaryConfidenceName(CostSummaryConfidence::Heuristic)) ==
"heuristic");
REQUIRE(std::string(costSummaryConfidenceName(CostSummaryConfidence::Syntactic)) ==
"syntactic");
REQUIRE(costSummaryConfidenceFromName("not-a-confidence") == CostSummaryConfidence::Unknown);
REQUIRE(std::string(costBoundKindName(CostBoundKind::Constant)) == "constant");
REQUIRE(std::string(costBoundKindName(CostBoundKind::Linear)) == "linear");
REQUIRE(std::string(costBoundKindName(CostBoundKind::Polynomial)) == "polynomial");
REQUIRE(std::string(costBoundKindName(CostBoundKind::UnboundedCandidate)) ==
"unbounded-candidate");
REQUIRE(costBoundKindFromName("not-a-bound") == CostBoundKind::Unknown);
REQUIRE(std::string(costSecurityRelevanceName(CostSecurityRelevance::None)) == "none");
REQUIRE(std::string(costSecurityRelevanceName(CostSecurityRelevance::InputDependent)) ==
"input-dependent");
REQUIRE(std::string(costSecurityRelevanceName(CostSecurityRelevance::LatentDos)) ==
"latent-dos");
REQUIRE(std::string(costSecurityRelevanceName(CostSecurityRelevance::PotentialDos)) ==
"potential-dos");
REQUIRE(costSecurityRelevanceFromName("not-a-relevance") == CostSecurityRelevance::Unknown);
}
TEST_CASE("cost summary report round-trips source function and loop data", "[cost-summary]") {
using namespace fold::symbolic;
using namespace fold::symbolic::cost_json_keys;
CostInputSymbol n{.name = "n", .subject = "param:0", .symbol_kind = "parameter"};
LoopCostSummary loop;
loop.id = "loop:example.cpp:4:5";
loop.kind = "for";
loop.location = fold::ir::SourceLoc{.file = "example.cpp", .line = 4, .column = 5};
loop.nesting_depth = 1;
loop.guard_expression = "i < n";
loop.induction_variable = "i";
loop.bound_kind = CostBoundKind::Linear;
loop.bound_expression = "max(n, 0)";
loop.confidence = CostSummaryConfidence::Proved;
loop.precision = fold::EncodingPrecision::Exact;
loop.security_relevance = CostSecurityRelevance::InputDependent;
loop.security_reasons = {"loop-bound-depends-on-input-symbol"};
loop.calls = {"work"};
loop.input_symbols = {n};
FunctionCostSummary summary;
summary.backend = "source";
summary.function = "f";
summary.location = fold::ir::SourceLoc{.file = "example.cpp", .line = 2, .column = 1};
summary.cost_expression = "O(n)";
summary.confidence = CostSummaryConfidence::Proved;
summary.precision = fold::EncodingPrecision::Exact;
summary.security_relevance = CostSecurityRelevance::InputDependent;
summary.security_reasons = {"loop-bound-depends-on-input-symbol"};
summary.input_symbols = {n};
summary.loops = {loop};
CostSummaryReport report;
report.summaries = {summary};
const auto j = toJson(report);
REQUIRE(j[kVersion] == 1);
REQUIRE(j[kSummaries].is_array());
REQUIRE(j[kSummaries][0][kBackend] == "source");
REQUIRE(j[kSummaries][0][kFunction] == "f");
REQUIRE(j[kSummaries][0][kLocation][kFile] == "example.cpp");
REQUIRE(j[kSummaries][0][kSecurityRelevance] == "input-dependent");
REQUIRE(j[kSummaries][0][kLoops][0][kBoundKind] == "linear");
REQUIRE(j[kSummaries][0][kLoops][0][kSecurityRelevance] == "input-dependent");
REQUIRE(j[kSummaries][0][kLoops][0][kInputSymbols][0][kSubject] == "param:0");
const auto parsed = costSummaryReportFromJson(j);
REQUIRE(parsed == report);
}
TEST_CASE("cost summary parsing is tolerant of absent optional arrays", "[cost-summary]") {
using namespace fold::symbolic;
using namespace fold::symbolic::cost_json_keys;
const nlohmann::json j = {
{kVersion, 7},
{kSummaries,
nlohmann::json::array({{{kFunction, "g"},
{kLocation, {{kFile, "g.cpp"}, {kLine, 10}, {kColumn, 3}}},
{kCostExpression, "unknown"},
{kConfidence, "heuristic"},
{kPrecision, "unsupported-node"}}})}};
const auto parsed = costSummaryReportFromJson(j);
REQUIRE(parsed.version == 7);
REQUIRE(parsed.summaries.size() == 1);
REQUIRE(parsed.summaries[0].backend == "source");
REQUIRE(parsed.summaries[0].function == "g");
REQUIRE(parsed.summaries[0].location.file == "g.cpp");
REQUIRE(parsed.summaries[0].confidence == CostSummaryConfidence::Heuristic);
REQUIRE(parsed.summaries[0].precision == fold::EncodingPrecision::UnsupportedNode);
REQUIRE(parsed.summaries[0].security_relevance == CostSecurityRelevance::None);
REQUIRE(parsed.summaries[0].security_reasons.empty());
REQUIRE(parsed.summaries[0].input_symbols.empty());
REQUIRE(parsed.summaries[0].loops.empty());
}