fold/tests/unit/test_scan_router_profile.cpp
trvon fd4a68dfff
test: add coverage for project graph, pcode encoder, and detector behaviors
Unit + integration coverage for the session's work: incremental project
graph/index, pcode encoder, unreachable/dead-branch, equiv/cross-TU,
div-by-zero/sign-conversion/int-truncation, use-after-free, quality-precision,
scan-router profile, source TU runner, detector plan, and the eval fixtures.
2026-08-16 16:46:07 -06:00

256 lines
11 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <nlohmann/json.hpp>
#include "fold/cli/scan_router.hpp"
#include "fold/cli/scan_runner.hpp"
TEST_CASE("function fingerprints are not collected solely for profiling",
"[scan-runner][profile]") {
fold::cli::ScanOptions options;
options.profile = true;
CHECK_FALSE(fold::cli::shouldCollectFunctionFingerprints(options));
options.incremental = true;
CHECK(fold::cli::shouldCollectFunctionFingerprints(options));
options.incremental = false;
options.cache_dir = "/tmp/fold-project-index";
CHECK(fold::cli::shouldCollectFunctionFingerprints(options));
options.is_worker = true;
CHECK_FALSE(fold::cli::shouldCollectFunctionFingerprints(options));
}
TEST_CASE("scan profile serializes bounded solver query telemetry without expressions",
"[scan-router][profile][solver]") {
fold::cli::ScanProfile profile;
fold::cli::DetectorProfile detector;
detector.name = "dead";
detector.query_samples.push_back({
.canonical_hash = 42,
.ast_nodes = 9,
.max_depth = 3,
.symbols = 2,
.assertions = 4,
.distinct_ops = 1,
.build_us = 11,
.translation_us = 13,
.smt_solve_us = 17,
.bitblast_solve_us = 19,
.winner = "smt",
.result = "unsat",
.smt_timeout = false,
.bitblast_timeout = true,
});
detector.query_samples_dropped = 7;
detector.constraint_slice_queries = 5;
detector.constraint_slice_terms_before = 40;
detector.constraint_slice_terms_after = 12;
detector.constraint_slice_nonproof = 4;
profile.detectors.push_back(detector);
profile.production_roots = 4;
profile.test_roots = 2;
profile.conditional_roots = 1;
profile.unresolved_explicit_roots = 3;
profile.root_inventory_closed_world = false;
profile.reachability_production = 5;
profile.reachability_test_only = 2;
profile.reachability_default_inactive = 1;
profile.reachability_configuration_infeasible = 3;
profile.reachability_unreachable_production = 4;
profile.reachability_removal_candidates = 4;
const auto encoded = nlohmann::json::parse(profile.to_json());
const auto& row = encoded["detectors"][0];
REQUIRE(row["query_samples"].size() == 1);
CHECK(row["query_samples"][0]["canonical_hash"] == "42");
CHECK(row["query_samples"][0]["operators"]["distinct"] == 1);
CHECK(row["query_samples"][0]["timing_us"]["translation"] == 13);
CHECK(row["query_samples"][0]["timeouts"]["bitblast"] == true);
CHECK_FALSE(row["query_samples"][0].contains("expression"));
CHECK(row["query_samples_dropped"] == 7);
CHECK(row["constraint_slice_queries"] == 5);
CHECK(row["constraint_slice_terms_before"] == 40);
CHECK(row["constraint_slice_terms_after"] == 12);
CHECK(row["constraint_slice_nonproof"] == 4);
CHECK(encoded["scan"]["production_roots"] == 4);
CHECK(encoded["scan"]["test_roots"] == 2);
CHECK(encoded["scan"]["conditional_roots"] == 1);
CHECK(encoded["scan"]["unresolved_explicit_roots"] == 3);
CHECK(encoded["scan"]["root_inventory_closed_world"] == false);
CHECK(encoded["scan"]["reachability_production"] == 5);
CHECK(encoded["scan"]["reachability_test_only"] == 2);
CHECK(encoded["scan"]["reachability_default_inactive"] == 1);
CHECK(encoded["scan"]["reachability_configuration_infeasible"] == 3);
CHECK(encoded["scan"]["reachability_unreachable_production"] == 4);
CHECK(encoded["scan"]["reachability_removal_candidates"] == 4);
CHECK(encoded.dump().find("missing-entrypoint") == std::string::npos);
}
TEST_CASE("scan-router: hybrid profile merge preserves lane timing and counters",
"[scan-router][profile]") {
fold::cli::ScanProfile source;
source.num_tus = 2;
source.num_functions = 7;
source.source_tus_selected = 3;
source.source_tus_excluded = 1;
source.source_tus_attempted = 2;
source.source_tus_completed = 1;
source.source_tus_timed_out = 1;
source.source_tus_frontend_failed = 0;
source.functions_stale = 3;
source.functions_fresh = 4;
source.functions_prefilter_skipped = 5;
source.compile_db_load_ms = 11;
source.fanout_orchestration_ms = 13;
source.serial_dispatch_ms = 17;
source.function_fingerprint_ms = 19;
source.project_index_load_ms = 20;
source.project_index_update_ms = 21;
source.call_graph_ms = 23;
source.project_graph_collect_ms = 24;
source.project_graph_merge_ms = 25;
source.project_graph_fallback_ms = 26;
source.project_index_contribution_update_ms = 27;
source.project_index_snapshot_update_ms = 28;
source.security_candidate_ms = 29;
source.cross_tu_property_ms = 29;
source.symbolic_summary_ms = 31;
source.call_graph_nodes = 101;
source.call_graph_edges = 202;
source.call_graph_direct_edges = 150;
source.call_graph_conservative_edges = 40;
source.call_graph_unknown_edges = 12;
source.call_graph_unresolved_nodes = 3;
source.project_index_loaded_nodes = 90;
source.project_index_reused_tus = 7;
source.project_index_reparsed_tus = 2;
source.project_index_removed_tus = 1;
source.guarded_callsites = 12;
source.projected_guards = 7;
source.unconditional_guards = 4;
source.unknown_guards = 1;
source.production_roots = 5;
source.test_roots = 2;
source.conditional_roots = 1;
source.root_inventory_closed_world = true;
source.configuration_profile_version = 1;
source.configuration_profile_hash = std::string(64, 'a');
source.project_index_reused_functions = 80;
source.project_index_changed_functions = 10;
source.project_index_removed_functions = 2;
source.project_index_changed_roots = 6;
source.project_index_invalidated_dependents = 7;
source.project_index_stale_functions_merged = 3;
source.project_index_input_mismatches = 1;
source.project_index_fallbacks = 1;
source.summary_scc_layers = 4;
source.summary_target_functions = 5;
source.summary_fixpoint_iterations = 6;
source.security_functions_scanned = 70;
source.security_candidate_functions = 20;
source.security_graph_expanded_functions = 3;
source.security_functions_pruned = 50;
source.clone_cache_hits = 8;
source.clone_cache_misses = 2;
source.clone_candidate_pairs = 90;
source.clone_length_filtered_pairs = 30;
source.clone_edit_distance_comparisons = 60;
source.clone_capped_buckets = 1;
source.detectors.push_back(fold::cli::DetectorProfile{.name = "div-by-zero"});
fold::cli::ScanProfile binary;
binary.num_tus = 1;
binary.num_functions = 9;
binary.compile_db_load_ms = 19;
binary.fanout_orchestration_ms = 23;
binary.serial_dispatch_ms = 29;
binary.function_fingerprint_ms = 37;
binary.project_index_load_ms = 40;
binary.project_index_update_ms = 41;
binary.call_graph_ms = 41;
binary.project_graph_collect_ms = 42;
binary.project_graph_merge_ms = 43;
binary.project_graph_fallback_ms = 44;
binary.project_index_contribution_update_ms = 45;
binary.project_index_snapshot_update_ms = 46;
binary.security_candidate_ms = 47;
binary.cross_tu_property_ms = 43;
binary.symbolic_summary_ms = 47;
binary.detectors.push_back(fold::cli::DetectorProfile{.name = "binary-div-by-zero"});
fold::cli::ScanOptions opts;
opts.incremental = true;
auto merged = fold::cli::mergeScanProfilesForRouter(source, binary, opts);
REQUIRE(merged);
REQUIRE(merged->incremental);
REQUIRE(merged->num_tus == 3);
REQUIRE(merged->num_functions == 16);
REQUIRE(merged->source_tus_selected == 3);
REQUIRE(merged->source_tus_excluded == 1);
REQUIRE(merged->source_tus_attempted == 2);
REQUIRE(merged->source_tus_completed == 1);
REQUIRE(merged->source_tus_timed_out == 1);
REQUIRE(merged->source_tus_frontend_failed == 0);
REQUIRE(merged->functions_stale == 3);
REQUIRE(merged->functions_fresh == 4);
REQUIRE(merged->functions_prefilter_skipped == 5);
REQUIRE(merged->compile_db_load_ms == 30);
REQUIRE(merged->fanout_orchestration_ms == 36);
REQUIRE(merged->serial_dispatch_ms == 46);
REQUIRE(merged->function_fingerprint_ms == 56);
REQUIRE(merged->project_index_load_ms == 60);
REQUIRE(merged->project_index_update_ms == 62);
REQUIRE(merged->call_graph_ms == 64);
REQUIRE(merged->project_graph_collect_ms == 66);
REQUIRE(merged->project_graph_merge_ms == 68);
REQUIRE(merged->project_graph_fallback_ms == 70);
REQUIRE(merged->project_index_contribution_update_ms == 72);
REQUIRE(merged->project_index_snapshot_update_ms == 74);
REQUIRE(merged->security_candidate_ms == 76);
REQUIRE(merged->cross_tu_property_ms == 72);
REQUIRE(merged->symbolic_summary_ms == 78);
REQUIRE(merged->call_graph_nodes == 101);
REQUIRE(merged->call_graph_edges == 202);
REQUIRE(merged->call_graph_direct_edges == 150);
REQUIRE(merged->call_graph_conservative_edges == 40);
REQUIRE(merged->call_graph_unknown_edges == 12);
REQUIRE(merged->call_graph_unresolved_nodes == 3);
REQUIRE(merged->project_index_loaded_nodes == 90);
REQUIRE(merged->project_index_reused_tus == 7);
REQUIRE(merged->project_index_reparsed_tus == 2);
REQUIRE(merged->project_index_removed_tus == 1);
REQUIRE(merged->guarded_callsites == 12);
REQUIRE(merged->projected_guards == 7);
REQUIRE(merged->unconditional_guards == 4);
REQUIRE(merged->unknown_guards == 1);
REQUIRE(merged->production_roots == 5);
REQUIRE(merged->test_roots == 2);
REQUIRE(merged->conditional_roots == 1);
REQUIRE(merged->root_inventory_closed_world);
REQUIRE(merged->configuration_profile_version == 1);
REQUIRE(merged->configuration_profile_hash == std::string(64, 'a'));
REQUIRE(merged->project_index_reused_functions == 80);
REQUIRE(merged->project_index_changed_functions == 10);
REQUIRE(merged->project_index_removed_functions == 2);
REQUIRE(merged->project_index_changed_roots == 6);
REQUIRE(merged->project_index_invalidated_dependents == 7);
REQUIRE(merged->project_index_stale_functions_merged == 3);
REQUIRE(merged->project_index_input_mismatches == 1);
REQUIRE(merged->project_index_fallbacks == 1);
REQUIRE(merged->summary_scc_layers == 4);
REQUIRE(merged->summary_target_functions == 5);
REQUIRE(merged->summary_fixpoint_iterations == 6);
REQUIRE(merged->security_functions_scanned == 70);
REQUIRE(merged->security_candidate_functions == 20);
REQUIRE(merged->security_graph_expanded_functions == 3);
REQUIRE(merged->security_functions_pruned == 50);
REQUIRE(merged->clone_cache_hits == 8);
REQUIRE(merged->clone_cache_misses == 2);
REQUIRE(merged->clone_candidate_pairs == 90);
REQUIRE(merged->clone_length_filtered_pairs == 30);
REQUIRE(merged->clone_edit_distance_comparisons == 60);
REQUIRE(merged->clone_capped_buckets == 1);
REQUIRE(merged->detectors.size() == 2);
}