exeora-cli 0.8.2

Native Exeora CLI and local tool executor
Documentation
//! The hard suite as data, mirroring `scripts/tool-benchmark/hard-cases.ts`.
//!
//! The corpus these arguments describe is generated by that script for both
//! engines, so neither one is measured against a fixture the other never saw.
//! Timing and verification both read this list, which is what keeps the numbers
//! and the parity check talking about the same calls.

use crate::harness::Weight;
use exeora_cli::protocol::ToolName;
use serde_json::{Value, json};
use std::{fs, path::Path};

pub const PACKAGES: usize = 12;
pub const MODULES: usize = 4;
pub const UNITS: usize = 25;
const HAYSTACK_LINES: usize = 120_000;

pub const MISS_PATTERN: &str = "absent_beacon_[0-9]{4}_symbol";
pub const ALTERNATION_PATTERN: &str = "(?:zeta|omega|kappa|sigma)_[0-9]{3}_beacon";
const LONG_LINE_PATTERN: &str = "runtime_beacon_marker";
const BLOB_PATTERN: &str = "blob_adjacent_marker";

const EDIT_ANCHOR_OLD: &str = "pub const UNIQUE_EDIT_ANCHOR: u64 = 424242;";
pub const EDIT_ANCHOR_NEW: &str = "pub const UNIQUE_EDIT_ANCHOR: u64 = 848484;";

#[cfg(unix)]
const STDOUT_COMMAND: &str = "cat streams/stdout-700k.txt";
#[cfg(windows)]
const STDOUT_COMMAND: &str = "type streams\\stdout-700k.txt";

pub struct HardCase {
    pub name: &'static str,
    pub weight: Weight,
    pub tool: ToolName,
    pub arguments: Value,
    /// edit_file rewrites its target, so the fixture is restored per iteration.
    pub reset: bool,
}

fn case(name: &'static str, weight: Weight, tool: ToolName, arguments: Value) -> HardCase {
    HardCase {
        name,
        weight,
        tool,
        arguments,
        reset: false,
    }
}

pub fn hard_cases() -> Vec<HardCase> {
    vec![
        // 8 MB of text to decode, split and truncate, against a 500 KB answer.
        case(
            "read_file_8mb",
            Weight::Medium,
            ToolName::ReadFile,
            json!({"path":"big/haystack.rs"}),
        ),
        // The same file for a hundred lines at the end: all of the splitting,
        // none of the joining, which is where the two engines stop agreeing.
        case(
            "read_file_tail_slice",
            Weight::Light,
            ToolName::ReadFile,
            json!({"path":"big/haystack.rs", "offset":HAYSTACK_LINES - 1_000, "limit":100}),
        ),
        // A thousand entries, each one a stat.
        case(
            "list_files_corpus_1k",
            Weight::Medium,
            ToolName::ListFiles,
            json!({"path":"corpus", "recursive":true}),
        ),
        case(
            "list_files_glob_corpus",
            Weight::Medium,
            ToolName::ListFiles,
            json!({"path":"corpus", "recursive":true, "glob":"**/unit-0*.rs"}),
        ),
        // Nothing matches, so neither engine may stop early: 6 MB read line by line.
        case(
            "grep_full_scan_miss",
            Weight::XHeavy,
            ToolName::Grep,
            json!({"pattern":MISS_PATTERN, "path":"corpus", "maxResults":200}),
        ),
        // Forty hits behind four alternated prefixes, folded: the cost is a
        // literal set rather than a single needle.
        case(
            "grep_regex_alternation",
            Weight::XHeavy,
            ToolName::Grep,
            json!({"pattern":ALTERNATION_PATTERN, "path":"corpus", "caseInsensitive":true, "maxResults":200}),
        ),
        // 100 KB lines with the match at the end.
        case(
            "grep_long_lines",
            Weight::Heavy,
            ToolName::Grep,
            json!({"pattern":LONG_LINE_PATTERN, "path":"minified", "maxResults":200}),
        ),
        // 4 MB of binary to decline: decoded and rejected, or abandoned at the first NUL.
        case(
            "grep_binary_corpus",
            Weight::Heavy,
            ToolName::Grep,
            json!({"pattern":BLOB_PATTERN, "path":"blobs", "maxResults":200}),
        ),
        // One replacement in 30,000 lines, plus the unified diff that has to prove it.
        HardCase {
            reset: true,
            ..case(
                "edit_file_2mb",
                Weight::Heavy,
                ToolName::EditFile,
                json!({"path":"big/editable.rs", "oldString":EDIT_ANCHOR_OLD, "newString":EDIT_ANCHOR_NEW}),
            )
        },
        case(
            "write_file_8mb",
            Weight::Medium,
            ToolName::WriteFile,
            json!({"path":"big/written.txt", "content":"x".repeat(8 * 1024 * 1024)}),
        ),
        // 700 KB through a pipe, of which the contract returns the last 200 KB.
        case(
            "run_command_700kb_stdout",
            Weight::Heavy,
            ToolName::RunCommand,
            json!({"command":STDOUT_COMMAND}),
        ),
    ]
}

pub fn reset_case(root: &Path) {
    fs::copy(
        root.join("big/editable-pristine.rs"),
        root.join("big/editable.rs"),
    )
    .expect("restore editable fixture");
}

pub fn unit_path(index: usize) -> String {
    format!(
        "corpus/pkg-{:02}/mod-{}/unit-{:02}.rs",
        index % PACKAGES,
        (index >> 2) % MODULES,
        index % UNITS
    )
}

pub fn package_path(index: usize) -> String {
    format!("corpus/pkg-{:02}", index % PACKAGES)
}