rash_core 2.18.2

Declarative shell scripting using Rust native bindings
Documentation
use crate::cli::modules::run_test_with_env;
use std::env;
use std::path::Path;

fn build_test_path() -> String {
    let mocks_dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/mocks");
    let target_dir = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .join("target/debug");
    format!(
        "{}:{}:{}",
        mocks_dir.to_str().unwrap(),
        target_dir.to_str().unwrap(),
        env::var("PATH").unwrap_or_default()
    )
}

#[test]
fn test_trace_file_opens() {
    let mock_path = build_test_path();

    let script_text = r#"
#!/usr/bin/env rash
- name: Trace file opens
  trace:
    probe: file_opens
    duration: "1s"
  register: files

- name: Verify events captured
  assert:
    that:
      - "{{ files.extra.events | length > 0 }}"
    "#
    .to_string();

    let args: &[&str] = &[];
    let (stdout, stderr) = run_test_with_env(&script_text, args, &[("PATH", &mock_path)]);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(stdout.contains("Traced for"), "stdout: {}", stdout);
}

#[test]
fn test_trace_process_exec() {
    let mock_path = build_test_path();

    let script_text = r#"
#!/usr/bin/env rash
- name: Trace process execution
  trace:
    probe: process_exec
    duration: "1s"
  register: procs

- name: Verify events captured
  assert:
    that:
      - "{{ procs.extra.events | length > 0 }}"
    "#
    .to_string();

    let args: &[&str] = &[];
    let (stdout, stderr) = run_test_with_env(&script_text, args, &[("PATH", &mock_path)]);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(stdout.contains("Traced for"), "stdout: {}", stdout);
}

#[test]
fn test_trace_syscalls_with_filter() {
    let mock_path = build_test_path();

    let script_text = r#"
#!/usr/bin/env rash
- name: Trace syscalls with filter
  trace:
    probe: syscalls
    filter: open,read
    duration: "1s"
  register: syscalls

- name: Verify events captured
  assert:
    that:
      - "{{ syscalls.extra.events | length > 0 }}"
    "#
    .to_string();

    let args: &[&str] = &[];
    let (stdout, stderr) = run_test_with_env(&script_text, args, &[("PATH", &mock_path)]);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(stdout.contains("Traced for"), "stdout: {}", stdout);
}

#[test]
fn test_trace_custom_expression() {
    let mock_path = build_test_path();

    let script_text = r#"
#!/usr/bin/env rash
- name: Trace with custom expression
  trace:
    expr: 'tracepoint:syscalls:sys_enter_open { @[comm] = count(); }'
    duration: "1s"
  register: custom

- name: Verify events captured
  assert:
    that:
      - "{{ custom.extra.events | length > 0 }}"
    "#
    .to_string();

    let args: &[&str] = &[];
    let (stdout, stderr) = run_test_with_env(&script_text, args, &[("PATH", &mock_path)]);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(stdout.contains("Traced for"), "stdout: {}", stdout);
}

#[test]
fn test_trace_stats_included() {
    let mock_path = build_test_path();

    let script_text = r#"
#!/usr/bin/env rash
- name: Trace file opens
  trace:
    probe: file_opens
    duration: "1s"
  register: files

- name: Verify stats
  debug:
    msg: "Total: {{ files.extra.stats.total }}, by comm: {{ files.extra.stats.by_comm }}"
    "#
    .to_string();

    let args: &[&str] = &[];
    let (stdout, stderr) = run_test_with_env(&script_text, args, &[("PATH", &mock_path)]);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(stdout.contains("Total:"), "stdout: {}", stdout);
}

#[test]
fn test_trace_network_connect() {
    let mock_path = build_test_path();

    let script_text = r#"
#!/usr/bin/env rash
- name: Trace network connections
  trace:
    probe: network_connect
    duration: "1s"
  register: conns

- name: Verify events captured
  assert:
    that:
      - "{{ conns.extra.events | length > 0 }}"
    "#
    .to_string();

    let args: &[&str] = &[];
    let (stdout, stderr) = run_test_with_env(&script_text, args, &[("PATH", &mock_path)]);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(stdout.contains("Traced for"), "stdout: {}", stdout);
}

#[test]
fn test_trace_default_duration() {
    let mock_path = build_test_path();

    let script_text = r#"
#!/usr/bin/env rash
- name: Trace with default duration
  trace:
    probe: file_opens
  register: files

- debug:
    msg: "Duration: {{ files.extra.duration_ms }}ms"
    "#
    .to_string();

    let args: &[&str] = &[];
    let (stdout, stderr) = run_test_with_env(&script_text, args, &[("PATH", &mock_path)]);

    assert!(stderr.is_empty(), "stderr: {}", stderr);
    assert!(stdout.contains("Duration:"), "stdout: {}", stdout);
}

#[test]
fn test_trace_invalid_probe() {
    let mock_path = build_test_path();

    let script_text = r#"
#!/usr/bin/env rash
- name: Trace with invalid probe
  trace:
    probe: invalid_probe
    duration: "1s"
    "#
    .to_string();

    let args: &[&str] = &[];
    let (_stdout, stderr) = run_test_with_env(&script_text, args, &[("PATH", &mock_path)]);

    assert!(!stderr.is_empty());
    assert!(stderr.contains("Invalid probe"));
}