#include #include #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); }