• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Chik-Network / klvm_rs / 16770817670

06 Aug 2025 07:46AM UTC coverage: 93.355% (-0.3%) from 93.675%
16770817670

push

github

Chik-Network
update 0.16.0

526 of 581 new or added lines in 26 files covered. (90.53%)

1 existing line in 1 file now uncovered.

6294 of 6742 relevant lines covered (93.36%)

29671867.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.24
/src/test_ops.rs
1
use crate::allocator::{Allocator, NodePtr, SExp};
2
use crate::bls_ops::{
3
    op_bls_g1_multiply, op_bls_g1_negate, op_bls_g1_subtract, op_bls_g2_add, op_bls_g2_multiply,
4
    op_bls_g2_negate, op_bls_g2_subtract, op_bls_map_to_g1, op_bls_map_to_g2,
5
    op_bls_pairing_identity, op_bls_verify,
6
};
7
use crate::core_ops::{op_cons, op_eq, op_first, op_if, op_listp, op_raise, op_rest};
8
use crate::cost::Cost;
9
use crate::keccak256_ops::op_keccak256;
10
use crate::more_ops::{
11
    op_add, op_all, op_any, op_ash, op_coinid, op_concat, op_div, op_divmod, op_gr, op_gr_bytes,
12
    op_logand, op_logior, op_lognot, op_logxor, op_lsh, op_mod, op_modpow, op_multiply, op_not,
13
    op_point_add, op_pubkey_for_exp, op_sha256, op_strlen, op_substr, op_subtract,
14
};
15
use crate::number::Number;
16
use crate::reduction::{Reduction, Response};
17
use crate::secp_ops::{op_secp256k1_verify, op_secp256r1_verify};
18

19
use crate::error::EvalErr;
20
use hex::FromHex;
21
use num_traits::Num;
22
use std::cmp::min;
23
use std::collections::HashMap;
24

25
fn parse_atom(a: &mut Allocator, v: &str) -> NodePtr {
20,824✔
26
    if v == "0" {
20,824✔
27
        return a.nil();
1,091✔
28
    }
19,733✔
29

30
    assert!(!v.is_empty());
19,733✔
31

32
    if v.starts_with("0x") {
19,733✔
33
        let buf = Vec::from_hex(v.strip_prefix("0x").unwrap()).unwrap();
12,796✔
34
        return a.new_atom(&buf).unwrap();
12,796✔
35
    }
6,937✔
36

37
    if v.starts_with('\"') {
6,937✔
38
        assert!(v.ends_with('\"'));
411✔
39
        let buf = v
411✔
40
            .strip_prefix('\"')
411✔
41
            .unwrap()
411✔
42
            .strip_suffix('\"')
411✔
43
            .unwrap()
411✔
44
            .as_bytes();
411✔
45
        return a.new_atom(buf).unwrap();
411✔
46
    }
6,526✔
47

48
    if let Ok(num) = Number::from_str_radix(v, 10) {
6,526✔
49
        a.new_number(num).unwrap()
2,952✔
50
    } else {
51
        let v = v.strip_prefix('#').unwrap_or(v);
3,574✔
52
        match v {
3,574✔
53
            "q" => a.new_atom(&[1]).unwrap(),
3,574✔
54
            "a" => a.new_atom(&[2]).unwrap(),
1,512✔
55
            "i" => a.new_atom(&[3]).unwrap(),
1,243✔
56
            "c" => a.new_atom(&[4]).unwrap(),
981✔
57
            "f" => a.new_atom(&[5]).unwrap(),
965✔
58
            "r" => a.new_atom(&[6]).unwrap(),
951✔
59
            "l" => a.new_atom(&[7]).unwrap(),
937✔
60
            "x" => a.new_atom(&[8]).unwrap(),
937✔
61
            "=" => a.new_atom(&[9]).unwrap(),
666✔
62
            ">s" => a.new_atom(&[10]).unwrap(),
402✔
63
            "sha256" => a.new_atom(&[11]).unwrap(),
402✔
64
            "substr" => a.new_atom(&[12]).unwrap(),
402✔
65
            "strlen" => a.new_atom(&[13]).unwrap(),
402✔
66
            "concat" => a.new_atom(&[14]).unwrap(),
402✔
67

68
            "+" => a.new_atom(&[16]).unwrap(),
402✔
69
            "-" => a.new_atom(&[17]).unwrap(),
397✔
70
            "*" => a.new_atom(&[18]).unwrap(),
395✔
71
            "/" => a.new_atom(&[19]).unwrap(),
394✔
72
            "divmod" => a.new_atom(&[20]).unwrap(),
391✔
73
            ">" => a.new_atom(&[21]).unwrap(),
391✔
74
            "ash" => a.new_atom(&[22]).unwrap(),
390✔
75
            "lsh" => a.new_atom(&[23]).unwrap(),
390✔
76
            "logand" => a.new_atom(&[24]).unwrap(),
390✔
77
            "logior" => a.new_atom(&[25]).unwrap(),
390✔
78
            "logxor" => a.new_atom(&[26]).unwrap(),
390✔
79
            "lognot" => a.new_atom(&[27]).unwrap(),
390✔
80

81
            "point_add" => a.new_atom(&[29]).unwrap(),
390✔
82
            "pubkey_for_exp" => a.new_atom(&[30]).unwrap(),
387✔
83

84
            "not" => a.new_atom(&[32]).unwrap(),
379✔
85
            "any" => a.new_atom(&[33]).unwrap(),
379✔
86
            "all" => a.new_atom(&[34]).unwrap(),
379✔
87

88
            "softfork" => a.new_atom(&[36]).unwrap(),
379✔
89

90
            "coinid" => a.new_atom(&[48]).unwrap(),
264✔
91

92
            "g1_add" => a.new_atom(&[29]).unwrap(),
223✔
93
            "g1_subtract" => a.new_atom(&[49]).unwrap(),
223✔
94
            "g1_multiply" => a.new_atom(&[50]).unwrap(),
223✔
95
            "g1_negate" => a.new_atom(&[51]).unwrap(),
187✔
96
            "g2_add" => a.new_atom(&[52]).unwrap(),
151✔
97
            "g2_subtract" => a.new_atom(&[53]).unwrap(),
115✔
98
            "g2_multiply" => a.new_atom(&[54]).unwrap(),
115✔
99
            "g2_negate" => a.new_atom(&[55]).unwrap(),
115✔
100
            "g1_map" => a.new_atom(&[56]).unwrap(),
115✔
101
            "g2_map" => a.new_atom(&[57]).unwrap(),
115✔
102
            "bls_pairing_identity" => a.new_atom(&[58]).unwrap(),
115✔
103
            "bls_verify" => a.new_atom(&[59]).unwrap(),
115✔
104
            "modpow" => a.new_atom(&[60]).unwrap(),
115✔
105
            "%" => a.new_atom(&[61]).unwrap(),
79✔
106
            "secp256k1_verify" => a.new_atom(&[0x13, 0xd6, 0x1f, 0x00]).unwrap(),
43✔
107
            "secp256r1_verify" => a.new_atom(&[0x1c, 0x3a, 0x8f, 0x00]).unwrap(),
41✔
108
            "keccak256" => a.new_atom(&[62]).unwrap(),
39✔
109
            _ => {
NEW
110
                panic!("atom not supported \"{v}\"");
×
111
            }
112
        }
113
    }
114
}
20,824✔
115

116
fn pop_token(s: &str) -> (&str, &str) {
35,324✔
117
    let s = s.trim();
35,324✔
118
    if let Some(stripped) = s.strip_prefix('\"') {
35,324✔
119
        if let Some(second_quote) = stripped.find('\"') {
411✔
120
            let (first, rest) = s.split_at(second_quote + 2);
411✔
121
            (first.trim(), rest.trim())
411✔
122
        } else {
123
            panic!("mismatching quote")
×
124
        }
125
    } else if s.starts_with('(') || s.starts_with(')') {
34,913✔
126
        let (first, rest) = s.split_at(1);
8,661✔
127
        (first, rest.trim())
8,661✔
128
    } else {
129
        let space = s.find(' ');
26,252✔
130
        let close = s.find(')');
26,252✔
131

132
        let split_pos = if let (Some(space_pos), Some(close_pos)) = (space, close) {
26,252✔
133
            min(space_pos, close_pos)
7,832✔
134
        } else if let Some(pos) = space {
18,420✔
135
            pos
7,796✔
136
        } else if let Some(pos) = close {
10,624✔
137
            pos
342✔
138
        } else {
139
            s.len()
10,282✔
140
        };
141

142
        let (first, rest) = s.split_at(split_pos);
26,252✔
143
        (first.trim(), rest.trim())
26,252✔
144
    }
145
}
35,324✔
146

147
pub fn parse_list<'a>(a: &mut Allocator, v: &'a str) -> (NodePtr, &'a str) {
26,850✔
148
    let v = v.trim();
26,850✔
149
    let (first, rest) = pop_token(v);
26,850✔
150
    if first.is_empty() {
26,850✔
151
        return (a.nil(), rest);
3,681✔
152
    }
23,169✔
153
    if first == ")" {
23,169✔
154
        return (a.nil(), rest);
2,173✔
155
    }
20,996✔
156
    if first == "(" {
20,996✔
157
        let (head, new_rest) = parse_list(a, rest);
3,214✔
158
        let (tail, new_rest) = parse_list(a, new_rest);
3,214✔
159
        (a.new_pair(head, tail).unwrap(), new_rest)
3,214✔
160
    } else if first == "." {
17,782✔
161
        let (node, new_rest) = parse_exp(a, rest);
2,158✔
162
        let (end_list, new_rest) = pop_token(new_rest);
2,158✔
163
        assert_eq!(end_list, ")");
2,158✔
164
        (node, new_rest)
2,158✔
165
    } else {
166
        let head = parse_atom(a, first);
15,624✔
167
        let (tail, new_rest) = parse_list(a, rest);
15,624✔
168
        (a.new_pair(head, tail).unwrap(), new_rest)
15,624✔
169
    }
170
}
26,850✔
171

172
pub fn parse_exp<'a>(a: &mut Allocator, v: &'a str) -> (NodePtr, &'a str) {
6,316✔
173
    let (first, rest) = pop_token(v);
6,316✔
174
    if first == "(" {
6,316✔
175
        parse_list(a, rest)
1,116✔
176
    } else {
177
        (parse_atom(a, first), rest)
5,200✔
178
    }
179
}
6,316✔
180

181
/// compare two KLVM trees. Returns true if they are identical, false otherwise
182
pub fn node_eq(allocator: &Allocator, s1: NodePtr, s2: NodePtr) -> bool {
3,760✔
183
    let mut stack = vec![(s1, s2)];
3,760✔
184

185
    while let Some((l, r)) = stack.pop() {
23,764✔
186
        match (allocator.sexp(l), allocator.sexp(r)) {
20,004✔
187
            (SExp::Pair(ll, lr), SExp::Pair(rl, rr)) => {
8,122✔
188
                stack.push((lr, rr));
8,122✔
189
                stack.push((ll, rl));
8,122✔
190
            }
8,122✔
191
            (SExp::Atom, SExp::Atom) => {
192
                if !allocator.atom_eq(l, r) {
11,882✔
193
                    return false;
×
194
                }
11,882✔
195
            }
196
            _ => {
197
                return false;
×
198
            }
199
        }
200
    }
201
    true
3,760✔
202
}
3,760✔
203

204
#[cfg(test)]
205
mod tests {
206
    use super::*;
207

208
    #[cfg(feature = "pre-eval")]
209
    use crate::chik_dialect::{ChikDialect, NO_UNKNOWN_OPS};
210

211
    #[cfg(feature = "pre-eval")]
212
    use crate::run_program::run_program_with_pre_eval;
213

214
    #[cfg(feature = "pre-eval")]
215
    use std::cell::RefCell;
216

217
    #[cfg(feature = "pre-eval")]
218
    use std::collections::HashSet;
219

220
    // Allows move closures to tear off a reference and move it.
221
    // Allows interior mutability inside Fn traits.
222
    #[cfg(feature = "pre-eval")]
223
    use std::rc::Rc;
224

225
    use rstest::rstest;
226

227
    type Opf = fn(&mut Allocator, NodePtr, Cost) -> Response;
228

229
    // the input is a list of test cases, each item is a tuple of:
230
    // (function pointer to test, list of arguments, optional result)
231
    // if the result is None, the call is expected to fail
232
    fn run_op_test(op: &Opf, args_str: &str, expected: &str, expected_cost: u64) {
3,682✔
233
        let mut a = Allocator::new();
3,682✔
234

235
        let (args, rest) = parse_list(&mut a, args_str);
3,682✔
236
        assert_eq!(rest, "");
3,682✔
237
        let result = op(&mut a, args, 10000000000 as Cost);
3,682✔
238
        match result {
3,682✔
239
            Err(e) => {
354✔
240
                println!("Error: {e}");
354✔
241
                assert_eq!(expected, "FAIL");
354✔
242
            }
243
            Ok(Reduction(cost, ret_value)) => {
3,328✔
244
                assert_eq!(cost, expected_cost);
3,328✔
245
                let (expected, rest) = parse_exp(&mut a, expected);
3,328✔
246
                assert_eq!(rest, "");
3,328✔
247
                assert!(node_eq(&a, ret_value, expected));
3,328✔
248
            }
249
        }
250
    }
3,682✔
251

252
    #[rstest]
253
    #[case("test-core-ops")]
254
    #[case("test-more-ops")]
255
    #[case("test-bls-ops")]
256
    #[case("test-blspy-g1")]
257
    #[case("test-blspy-g2")]
258
    #[case("test-blspy-hash")]
259
    #[case("test-blspy-pairing")]
260
    #[case("test-blspy-verify")]
261
    #[case("test-bls-zk")]
262
    #[case("test-secp-verify")]
263
    #[case("test-secp256k1")]
264
    #[case("test-secp256r1")]
265
    #[case("test-modpow")]
266
    #[case("test-sha256")]
267
    #[case("test-keccak256")]
268
    #[case("test-keccak256-generated")]
269
    fn test_ops(#[case] filename: &str) {
270
        use std::fs::read_to_string;
271

272
        let filename = format!("op-tests/{filename}.txt");
273

274
        let funs = HashMap::from([
275
            ("i", op_if as Opf),
276
            ("c", op_cons as Opf),
277
            ("f", op_first as Opf),
278
            ("r", op_rest as Opf),
279
            ("l", op_listp as Opf),
280
            ("x", op_raise as Opf),
281
            ("=", op_eq as Opf),
282
            ("sha256", op_sha256 as Opf),
283
            ("+", op_add as Opf),
284
            ("-", op_subtract as Opf),
285
            ("*", op_multiply as Opf),
286
            ("/", op_div as Opf),
287
            ("divmod", op_divmod as Opf),
288
            ("%", op_mod as Opf),
289
            ("substr", op_substr as Opf),
290
            ("strlen", op_strlen as Opf),
291
            ("point_add", op_point_add as Opf),
292
            ("pubkey_for_exp", op_pubkey_for_exp as Opf),
293
            ("concat", op_concat as Opf),
294
            (">", op_gr as Opf),
295
            (">s", op_gr_bytes as Opf),
296
            ("logand", op_logand as Opf),
297
            ("logior", op_logior as Opf),
298
            ("logxor", op_logxor as Opf),
299
            ("lognot", op_lognot as Opf),
300
            ("ash", op_ash as Opf),
301
            ("lsh", op_lsh as Opf),
302
            ("not", op_not as Opf),
303
            ("any", op_any as Opf),
304
            ("all", op_all as Opf),
305
            //the BLS extension
306
            ("coinid", op_coinid as Opf),
307
            ("g1_add", op_point_add as Opf),
308
            ("g1_subtract", op_bls_g1_subtract as Opf),
309
            ("g1_multiply", op_bls_g1_multiply as Opf),
310
            ("g1_negate", op_bls_g1_negate as Opf),
311
            ("g2_add", op_bls_g2_add as Opf),
312
            ("g2_subtract", op_bls_g2_subtract as Opf),
313
            ("g2_multiply", op_bls_g2_multiply as Opf),
314
            ("g2_negate", op_bls_g2_negate as Opf),
315
            ("g1_map", op_bls_map_to_g1 as Opf),
316
            ("g2_map", op_bls_map_to_g2 as Opf),
317
            ("bls_pairing_identity", op_bls_pairing_identity as Opf),
318
            ("bls_verify", op_bls_verify as Opf),
319
            ("secp256k1_verify", op_secp256k1_verify as Opf),
320
            ("secp256r1_verify", op_secp256r1_verify as Opf),
321
            ("modpow", op_modpow as Opf),
322
            ("keccak256", op_keccak256 as Opf),
323
        ]);
324

325
        println!("Test cases from: {filename}");
326
        let test_cases = read_to_string(filename).expect("test file not found");
327
        for t in test_cases.split('\n') {
328
            let t = t.trim();
329
            if t.is_empty() {
330
                continue;
331
            }
332
            // ignore comments
333
            if t.starts_with(';') {
334
                continue;
335
            }
336
            let (op_name, t) = t.split_once(' ').unwrap();
337
            let op = funs
338
                .get(op_name)
339
                .unwrap_or_else(|| panic!("couldn't find operator \"{op_name}\""));
×
340
            let (args, out) = t.split_once("=>").unwrap();
341
            let (expected, expected_cost) = if out.contains('|') {
342
                out.split_once('|').unwrap()
343
            } else {
344
                (out, "0")
345
            };
346

347
            println!("({} {}) => {}", op_name, args.trim(), expected.trim());
348
            run_op_test(
349
                op,
350
                args.trim(),
351
                expected.trim(),
352
                expected_cost.trim().parse().unwrap(),
353
            );
354
        }
355
    }
356

357
    #[test]
358
    fn test_single_argument_raise_atom() {
1✔
359
        let mut allocator = Allocator::new();
1✔
360
        let a1 = allocator.new_atom(&[65]).unwrap();
1✔
361
        let args = allocator.new_pair(a1, allocator.nil()).unwrap();
1✔
362
        let result = op_raise(&mut allocator, args, 100000);
1✔
363
        assert_eq!(result.unwrap_err(), EvalErr::Raise(a1));
1✔
364
    }
1✔
365

366
    #[test]
367
    fn test_single_argument_raise_pair() {
1✔
368
        let mut allocator = Allocator::new();
1✔
369
        let a1 = allocator.new_atom(&[65]).unwrap();
1✔
370
        let a2 = allocator.new_atom(&[66]).unwrap();
1✔
371
        // (a2)
372
        let mut args = allocator.new_pair(a2, allocator.nil()).unwrap();
1✔
373
        // (a1 a2)
374
        args = allocator.new_pair(a1, args).unwrap();
1✔
375
        // ((a1 a2))
376
        args = allocator.new_pair(args, allocator.nil()).unwrap();
1✔
377
        let result = op_raise(&mut allocator, args, 100000);
1✔
378
        assert_eq!(result.unwrap_err(), EvalErr::Raise(args));
1✔
379
    }
1✔
380

381
    #[test]
382
    fn test_multi_argument_raise() {
1✔
383
        let mut allocator = Allocator::new();
1✔
384
        let a1 = allocator.new_atom(&[65]).unwrap();
1✔
385
        let a2 = allocator.new_atom(&[66]).unwrap();
1✔
386
        // (a1)
387
        let mut args = allocator.new_pair(a2, allocator.nil()).unwrap();
1✔
388
        // (a1 a2)
389
        args = allocator.new_pair(a1, args).unwrap();
1✔
390
        let result = op_raise(&mut allocator, args, 100000);
1✔
391
        assert_eq!(result.unwrap_err(), EvalErr::Raise(args));
1✔
392
    }
1✔
393

394
    #[cfg(feature = "pre-eval")]
395
    use crate::error::Result;
396

397
    #[cfg(feature = "pre-eval")]
398
    const COST_LIMIT: u64 = 1000000000;
399

400
    #[cfg(feature = "pre-eval")]
401
    struct EvalFTracker {
402
        pub prog: NodePtr,
403
        pub args: NodePtr,
404
        pub outcome: Option<NodePtr>,
405
    }
406

407
    #[cfg(feature = "pre-eval")]
408
    type Callback = Box<dyn Fn(&mut Allocator, Option<NodePtr>)>;
409

410
    #[cfg(feature = "pre-eval")]
411
    type PreEvalF = Box<dyn Fn(&mut Allocator, NodePtr, NodePtr) -> Result<Option<Callback>>>;
412

413
    // Ensure pre_eval_f and post_eval_f are working as expected.
414
    #[cfg(feature = "pre-eval")]
415
    #[test]
416
    fn test_pre_eval_and_post_eval() {
417
        let mut allocator = Allocator::new();
418

419
        let a1 = allocator.new_atom(&[1]).unwrap();
420
        let a2 = allocator.new_atom(&[2]).unwrap();
421
        let a4 = allocator.new_atom(&[4]).unwrap();
422
        let a5 = allocator.new_atom(&[5]).unwrap();
423

424
        let a99 = allocator.new_atom(&[99]).unwrap();
425
        let a101 = allocator.new_atom(&[101]).unwrap();
426

427
        // (a (q . (f (c 2 5))) (q 99 101))
428
        let arg_tail = allocator.new_pair(a101, allocator.nil()).unwrap();
429
        let arg_mid = allocator.new_pair(a99, arg_tail).unwrap();
430
        let args = allocator.new_pair(a1, arg_mid).unwrap();
431

432
        let cons_tail = allocator.new_pair(a5, allocator.nil()).unwrap();
433
        let cons_args = allocator.new_pair(a2, cons_tail).unwrap();
434
        let cons_expr = allocator.new_pair(a4, cons_args).unwrap();
435

436
        let f_tail = allocator.new_pair(cons_expr, allocator.nil()).unwrap();
437
        let f_expr = allocator.new_pair(a5, f_tail).unwrap();
438
        let f_quoted = allocator.new_pair(a1, f_expr).unwrap();
439

440
        let a_tail = allocator.new_pair(args, allocator.nil()).unwrap();
441
        let a_args = allocator.new_pair(f_quoted, a_tail).unwrap();
442
        let program = allocator.new_pair(a2, a_args).unwrap();
443

444
        let tracking = Rc::new(RefCell::new(HashMap::new()));
445
        let pre_eval_tracking = tracking.clone();
446
        let pre_eval_f: PreEvalF = Box::new(move |_allocator, prog, args| {
447
            let tracking_key = pre_eval_tracking.borrow().len();
448
            // Ensure lifetime of mutable borrow is contained.
449
            // It must end before the lifetime of the following closure.
450
            {
451
                let mut tracking_mutable = pre_eval_tracking.borrow_mut();
452
                tracking_mutable.insert(
453
                    tracking_key,
454
                    EvalFTracker {
455
                        prog,
456
                        args,
457
                        outcome: None,
458
                    },
459
                );
460
            }
461
            let post_eval_tracking = pre_eval_tracking.clone();
462
            let post_eval_f: Callback = Box::new(move |_a, outcome| {
463
                let mut tracking_mutable = post_eval_tracking.borrow_mut();
464
                tracking_mutable.insert(
465
                    tracking_key,
466
                    EvalFTracker {
467
                        prog,
468
                        args,
469
                        outcome,
470
                    },
471
                );
472
            });
473
            Ok(Some(post_eval_f))
474
        });
475

476
        let result = run_program_with_pre_eval(
477
            &mut allocator,
478
            &ChikDialect::new(NO_UNKNOWN_OPS),
479
            program,
480
            NodePtr::NIL,
481
            COST_LIMIT,
482
            Some(pre_eval_f),
483
        )
484
        .unwrap();
485

486
        assert!(node_eq(&allocator, result.1, a99));
487

488
        // Should produce these:
489
        // (q 99 101) => (99 101)
490
        // (q . (f (c 2 5))) => (f (c 2 5))
491
        // 2 (99 101) => 99
492
        // 5 (99 101) => 101
493
        // (c 2 5) (99 101) => (99 . 101)
494
        // (f (c 2 5)) (99 101) => 99
495
        // (a (q 5 (c 2 5))) () => 99
496

497
        // args consed
498
        let args_consed = allocator.new_pair(a99, a101).unwrap();
499

500
        let desired_outcomes = [
501
            (args, NodePtr::NIL, arg_mid),
502
            (f_quoted, NodePtr::NIL, f_expr),
503
            (a2, arg_mid, a99),
504
            (a5, arg_mid, a101),
505
            (cons_expr, arg_mid, args_consed),
506
            (f_expr, arg_mid, a99),
507
            (program, NodePtr::NIL, a99),
508
        ];
509

510
        let mut found_outcomes = HashSet::new();
511
        let tracking_examine = tracking.borrow();
512
        for (_, v) in tracking_examine.iter() {
513
            let found = desired_outcomes.iter().position(|(p, a, o)| {
514
                node_eq(&allocator, *p, v.prog)
515
                    && node_eq(&allocator, *a, v.args)
516
                    && node_eq(&allocator, v.outcome.unwrap(), *o)
517
            });
518
            found_outcomes.insert(found);
519
            assert!(found.is_some());
520
        }
521

522
        assert_eq!(tracking_examine.len(), desired_outcomes.len());
523
        assert_eq!(tracking_examine.len(), found_outcomes.len());
524
    }
525
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc