cairo_lang_test_utils/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#![cfg(feature = "testing")]

pub mod parse_test_file;
use std::fs;
use std::path::Path;
use std::str::FromStr;
use std::sync::{Mutex, MutexGuard};

use cairo_lang_utils::ordered_hash_map::OrderedHashMap;
use cairo_lang_utils::require;
pub use parse_test_file::parse_test_file;

/// Returns the content of the relevant test file.
fn get_expected_contents(path: &Path) -> String {
    fs::read_to_string(path).unwrap_or_else(|_| panic!("Could not read file: '{path:?}'"))
}

/// Overrides the test file data.
fn set_contents(path: &Path, content: String) {
    fs::write(path, content).unwrap_or_else(|_| panic!("Could not write file: '{path:?}'"));
}

/// Compares content to examples content, or overrides it if the `CAIRO_FIX_TESTS` environment
/// value is set to `1`.
pub fn compare_contents_or_fix_with_path(path: &Path, content: String) {
    let is_fix_mode = std::env::var("CAIRO_FIX_TESTS") == Ok("1".into());
    if is_fix_mode {
        set_contents(path, content);
    } else {
        pretty_assertions::assert_eq!(content, get_expected_contents(path));
    }
}

/// Locks the given mutex, and prints an informative error on failure.
pub fn test_lock<'a, T: ?Sized + 'a>(m: &'a Mutex<T>) -> MutexGuard<'a, T> {
    match m.lock() {
        Ok(guard) => guard,
        // Allow other test to take the lock if it was poisoned by a thread that panicked.
        Err(poisoned) => poisoned.into_inner(),
    }
}

/// Returns an error string according to the extracted `ExpectDiagnostics`.
/// Returns None on success.
pub fn verify_diagnostics_expectation(
    args: &OrderedHashMap<String, String>,
    diagnostics: &str,
) -> Option<String> {
    let expect_diagnostics = args.get("expect_diagnostics")?;
    require(expect_diagnostics != "*")?;

    let expect_diagnostics = expect_diagnostics_input_input(expect_diagnostics);
    let has_diagnostics = !diagnostics.is_empty();
    // TODO(Gil): This is a bit of a hack, try and get the original diagnostics from the test.
    let has_errors = diagnostics.lines().any(|line| line.starts_with("error: "));
    match expect_diagnostics {
        ExpectDiagnostics::Any => {
            if !has_diagnostics {
                return Some(
                    "`expect_diagnostics` is true, but no diagnostics were generated.\n"
                        .to_string(),
                );
            }
        }
        ExpectDiagnostics::Warnings => {
            if !has_diagnostics {
                return Some(
                    "`expect_diagnostics` is 'warnings_only', but no diagnostics were generated.\n"
                        .to_string(),
                );
            } else if has_errors {
                return Some(
                    "`expect_diagnostics` is 'warnings_only', but errors were generated.\n"
                        .to_string(),
                );
            }
        }
        ExpectDiagnostics::None => {
            if has_diagnostics {
                return Some(
                    "`expect_diagnostics` is false, but diagnostics were generated:\n".to_string(),
                );
            }
        }
    };
    None
}

/// The expected diagnostics for a test.
enum ExpectDiagnostics {
    /// Any diagnostics (warnings or errors) are expected.
    Any,
    /// Only warnings are expected.
    Warnings,
    /// No diagnostics are expected.
    None,
}

/// Translates a string test input to bool ("false" -> false, "true" -> true). Panics if invalid.
/// Ignores case.
fn expect_diagnostics_input_input(input: &str) -> ExpectDiagnostics {
    let input = input.to_lowercase();
    match input.as_str() {
        "false" => ExpectDiagnostics::None,
        "true" => ExpectDiagnostics::Any,
        "warnings_only" => ExpectDiagnostics::Warnings,
        _ => panic!("Expected 'true', 'false' or 'warnings', actual: {input}"),
    }
}

/// Translates a string test input to bool ("false" -> false, "true" -> true). Panics if invalid.
/// Ignores case.
pub fn bool_input(input: &str) -> bool {
    let input = input.to_lowercase();
    bool::from_str(&input).unwrap_or_else(|_| panic!("Expected 'true' or 'false', actual: {input}"))
}

/// Parses a test input that may be a file input. If the input starts with ">>> file: " it reads the
/// file and returns the file path and content, otherwise, it returns the input and a default dummy
/// path.
pub fn get_direct_or_file_content(input: &str) -> (String, String) {
    if let Some(path) = input.strip_prefix(">>> file: ") {
        (
            path.to_string(),
            fs::read_to_string(path).unwrap_or_else(|_| panic!("Could not read file: '{path}'")),
        )
    } else {
        ("dummy_file.cairo".to_string(), input.to_string())
    }
}