Skip to content

Commit d6b559d

Browse files
committed
chore: automated sync of local changes
1 parent 5ac8998 commit d6b559d

3 files changed

Lines changed: 651 additions & 3 deletions

File tree

TEST-NEEDS.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
<!-- SPDX-License-Identifier: PMPL-1.0-or-later -->
44

5-
## CRG Grade: C+ (approaching B) — Updated 2026-04-04
5+
## CRG Grade: B — Updated 2026-04-04
66

77
## Current Test State
88

@@ -14,8 +14,18 @@
1414
| Property-based | `tests/property_tests.rs` | 41 | PBT |
1515
| Harvard boundary | `tests/harvard_boundary_tests.rs` | 35 | P2P/SEC/SAF |
1616
| Contract/invariant | `tests/contract_tests.rs` | 20 | CTR/LCY |
17-
| Conformance | `tests/conformance_tests.rs` | 2 (pre-existing failures) | E2E |
18-
| **Total passing** | | **339** | |
17+
| Compatibility | `tests/compatibility_tests.rs` | 12 | CMP/VER |
18+
| Mutation killers | `tests/mutation_killers.rs` | 156 | MUT targeting |
19+
| Conformance | `tests/conformance_tests.rs` | 2 (31 valid + 21 invalid programs) | E2E |
20+
| **Total passing** | | **509** | |
21+
22+
## Mutation Testing Results (cargo-mutants)
23+
24+
**Kill rate: 81.9%** on core files (number, typechecker, interpreter, pretty, ast, purity, reversible)
25+
- 344 mutants caught
26+
- 76 mutants missed
27+
- 48 unviable
28+
- 420 total non-unviable
1929

2030
## Proof Files
2131

crates/jtv-core/tests/mutation_killers.rs

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,33 @@ fn lt_incompatible_types_error() {
153153
assert!(Value::Int(1).lt(&Value::Symbolic("x".to_string())).is_err());
154154
}
155155

156+
// Cross-type EQUAL values: lt must return false, gt must return false
157+
// These kill `< with <=` and `> with >=` mutations on cross-type arms
158+
#[test]
159+
fn lt_cross_type_equal_is_false() {
160+
// Int(2) < Float(2.0) should be false
161+
assert_eq!(Value::Int(2).lt(&Value::Float(2.0)).unwrap(), false);
162+
assert_eq!(Value::Float(2.0).lt(&Value::Int(2)).unwrap(), false);
163+
assert_eq!(Value::Int(1).lt(&Value::Rational(Ratio::new(1, 1))).unwrap(), false);
164+
assert_eq!(Value::Rational(Ratio::new(1, 1)).lt(&Value::Int(1)).unwrap(), false);
165+
assert_eq!(Value::Hex(5).lt(&Value::Int(5)).unwrap(), false);
166+
assert_eq!(Value::Int(5).lt(&Value::Hex(5)).unwrap(), false);
167+
assert_eq!(Value::Binary(5).lt(&Value::Int(5)).unwrap(), false);
168+
assert_eq!(Value::Int(5).lt(&Value::Binary(5)).unwrap(), false);
169+
}
170+
171+
#[test]
172+
fn gt_cross_type_equal_is_false() {
173+
assert_eq!(Value::Int(2).gt(&Value::Float(2.0)).unwrap(), false);
174+
assert_eq!(Value::Float(2.0).gt(&Value::Int(2)).unwrap(), false);
175+
assert_eq!(Value::Int(1).gt(&Value::Rational(Ratio::new(1, 1))).unwrap(), false);
176+
assert_eq!(Value::Rational(Ratio::new(1, 1)).gt(&Value::Int(1)).unwrap(), false);
177+
assert_eq!(Value::Hex(5).gt(&Value::Int(5)).unwrap(), false);
178+
assert_eq!(Value::Int(5).gt(&Value::Hex(5)).unwrap(), false);
179+
assert_eq!(Value::Binary(5).gt(&Value::Int(5)).unwrap(), false);
180+
assert_eq!(Value::Int(5).gt(&Value::Binary(5)).unwrap(), false);
181+
}
182+
156183
// ============================================================================
157184
// Value::gt — greater than comparisons
158185
// ============================================================================
@@ -433,6 +460,33 @@ fn ge_binary_int() {
433460
// Value::eq and Value::ne
434461
// ============================================================================
435462

463+
// Kills "delete match arm" mutations for negate and string add
464+
#[test]
465+
fn negate_hex() {
466+
let result = Value::Hex(5).negate().unwrap();
467+
assert_eq!(result, Value::Hex(-5));
468+
}
469+
470+
#[test]
471+
fn negate_binary() {
472+
let result = Value::Binary(5).negate().unwrap();
473+
assert_eq!(result, Value::Binary(-5));
474+
}
475+
476+
#[test]
477+
fn negate_symbolic() {
478+
let result = Value::Symbolic("x".to_string()).negate().unwrap();
479+
assert!(matches!(result, Value::Symbolic(_)));
480+
}
481+
482+
#[test]
483+
fn add_string_string() {
484+
let a = Value::String("hello".to_string());
485+
let b = Value::String(" world".to_string());
486+
let result = a.add(&b).unwrap();
487+
assert_eq!(result, Value::String("hello world".to_string()));
488+
}
489+
436490
#[test]
437491
fn eq_int_true() {
438492
assert_eq!(Value::Int(5).eq(&Value::Int(5)).unwrap(), true);
@@ -894,6 +948,199 @@ fn interp_for_loop_step() {
894948
assert_eq!(interp.get_variable("sum").unwrap(), Value::Int(20));
895949
}
896950

951+
// ============================================================================
952+
// Type checker: add_result and negate_result (kills type coercion mutations)
953+
// ============================================================================
954+
955+
use jtv_core::typechecker::Type;
956+
957+
#[test]
958+
fn type_add_same_types() {
959+
assert_eq!(Type::Int.add_result(&Type::Int), Some(Type::Int));
960+
assert_eq!(Type::Float.add_result(&Type::Float), Some(Type::Float));
961+
assert_eq!(Type::Rational.add_result(&Type::Rational), Some(Type::Rational));
962+
assert_eq!(Type::Complex.add_result(&Type::Complex), Some(Type::Complex));
963+
assert_eq!(Type::Hex.add_result(&Type::Hex), Some(Type::Hex));
964+
assert_eq!(Type::Binary.add_result(&Type::Binary), Some(Type::Binary));
965+
assert_eq!(Type::Symbolic.add_result(&Type::Symbolic), Some(Type::Symbolic));
966+
assert_eq!(Type::String.add_result(&Type::String), Some(Type::String));
967+
}
968+
969+
#[test]
970+
fn type_add_coercions() {
971+
// Int + Float = Float
972+
assert_eq!(Type::Int.add_result(&Type::Float), Some(Type::Float));
973+
assert_eq!(Type::Float.add_result(&Type::Int), Some(Type::Float));
974+
// Int + Rational = Rational
975+
assert_eq!(Type::Int.add_result(&Type::Rational), Some(Type::Rational));
976+
assert_eq!(Type::Rational.add_result(&Type::Int), Some(Type::Rational));
977+
// Int + Complex = Complex
978+
assert_eq!(Type::Int.add_result(&Type::Complex), Some(Type::Complex));
979+
assert_eq!(Type::Complex.add_result(&Type::Int), Some(Type::Complex));
980+
// Float + Complex = Complex
981+
assert_eq!(Type::Float.add_result(&Type::Complex), Some(Type::Complex));
982+
assert_eq!(Type::Complex.add_result(&Type::Float), Some(Type::Complex));
983+
// Hex + Int = Int
984+
assert_eq!(Type::Hex.add_result(&Type::Int), Some(Type::Int));
985+
assert_eq!(Type::Int.add_result(&Type::Hex), Some(Type::Int));
986+
// Binary + Int = Int
987+
assert_eq!(Type::Binary.add_result(&Type::Int), Some(Type::Int));
988+
assert_eq!(Type::Int.add_result(&Type::Binary), Some(Type::Int));
989+
}
990+
991+
#[test]
992+
fn type_add_incompatible() {
993+
assert_eq!(Type::Int.add_result(&Type::String), None);
994+
assert_eq!(Type::Bool.add_result(&Type::Int), None);
995+
assert_eq!(Type::String.add_result(&Type::Float), None);
996+
}
997+
998+
#[test]
999+
fn type_add_any() {
1000+
assert_eq!(Type::Any.add_result(&Type::Int), Some(Type::Int));
1001+
assert_eq!(Type::Float.add_result(&Type::Any), Some(Type::Float));
1002+
}
1003+
1004+
#[test]
1005+
fn type_negate_numeric() {
1006+
assert_eq!(Type::Int.negate_result(), Some(Type::Int));
1007+
assert_eq!(Type::Float.negate_result(), Some(Type::Float));
1008+
assert_eq!(Type::Rational.negate_result(), Some(Type::Rational));
1009+
assert_eq!(Type::Complex.negate_result(), Some(Type::Complex));
1010+
assert_eq!(Type::Hex.negate_result(), Some(Type::Hex));
1011+
assert_eq!(Type::Binary.negate_result(), Some(Type::Binary));
1012+
assert_eq!(Type::Symbolic.negate_result(), Some(Type::Symbolic));
1013+
assert_eq!(Type::Any.negate_result(), Some(Type::Any));
1014+
}
1015+
1016+
#[test]
1017+
fn type_negate_non_numeric() {
1018+
assert_eq!(Type::Bool.negate_result(), None);
1019+
assert_eq!(Type::String.negate_result(), None);
1020+
assert_eq!(Type::Unit.negate_result(), None);
1021+
}
1022+
1023+
#[test]
1024+
fn type_coercible() {
1025+
assert!(Type::Int.coercible_to(&Type::Int));
1026+
assert!(Type::Int.coercible_to(&Type::Float));
1027+
assert!(Type::Int.coercible_to(&Type::Rational));
1028+
assert!(Type::Int.coercible_to(&Type::Complex));
1029+
assert!(Type::Hex.coercible_to(&Type::Int));
1030+
assert!(Type::Binary.coercible_to(&Type::Int));
1031+
assert!(Type::Float.coercible_to(&Type::Complex));
1032+
assert!(Type::Any.coercible_to(&Type::Int));
1033+
assert!(Type::Int.coercible_to(&Type::Any));
1034+
// Not coercible
1035+
assert!(!Type::Int.coercible_to(&Type::String));
1036+
assert!(!Type::String.coercible_to(&Type::Int));
1037+
assert!(!Type::Bool.coercible_to(&Type::Float));
1038+
}
1039+
1040+
// ============================================================================
1041+
// Pretty printer: control statement indentation (kills depth+1 mutations)
1042+
// ============================================================================
1043+
1044+
#[test]
1045+
fn pretty_print_if_indented_body() {
1046+
let code = "if x > 0 { y = 1 }";
1047+
let program = parse_program(code).unwrap();
1048+
let printer = PrettyPrinter::new();
1049+
let output = printer.print_program(&program);
1050+
// Body should be indented (2 spaces by default)
1051+
assert!(output.contains(" y = 1"), "If body should be indented:\n{}", output);
1052+
}
1053+
1054+
#[test]
1055+
fn pretty_print_while_indented_body() {
1056+
let code = "while x > 0 { x = x + 1 }";
1057+
let program = parse_program(code).unwrap();
1058+
let printer = PrettyPrinter::new();
1059+
let output = printer.print_program(&program);
1060+
assert!(output.contains(" x = x"), "While body should be indented:\n{}", output);
1061+
}
1062+
1063+
#[test]
1064+
fn pretty_print_for_indented_body() {
1065+
let code = "for i in 0..10 { x = x + 1 }";
1066+
let program = parse_program(code).unwrap();
1067+
let printer = PrettyPrinter::new();
1068+
let output = printer.print_program(&program);
1069+
assert!(output.contains(" x = x"), "For body should be indented:\n{}", output);
1070+
}
1071+
1072+
#[test]
1073+
fn pretty_print_reverse_indented_body() {
1074+
let code = "reverse { x += 5 }";
1075+
let program = parse_program(code).unwrap();
1076+
let printer = PrettyPrinter::new();
1077+
let output = printer.print_program(&program);
1078+
assert!(output.contains(" x += 5"), "Reverse body should be indented:\n{}", output);
1079+
}
1080+
1081+
#[test]
1082+
fn pretty_print_if_else_both_branches_indented() {
1083+
let code = "if x > 0 { y = 1 } else { y = 0 }";
1084+
let program = parse_program(code).unwrap();
1085+
let printer = PrettyPrinter::new();
1086+
let output = printer.print_program(&program);
1087+
assert!(output.contains(" y = 1"), "Then branch should be indented:\n{}", output);
1088+
assert!(output.contains(" y = 0"), "Else branch should be indented:\n{}", output);
1089+
}
1090+
1091+
#[test]
1092+
fn pretty_print_module() {
1093+
let code = "module M { fn f(): Int { return 1 } }";
1094+
let program = parse_program(code).unwrap();
1095+
let printer = PrettyPrinter::new();
1096+
let output = printer.print_program(&program);
1097+
assert!(output.contains("module M"), "Module header:\n{}", output);
1098+
assert!(output.contains(" fn f"), "Function should be indented in module:\n{}", output);
1099+
}
1100+
1101+
#[test]
1102+
fn pretty_print_program_multiple_stmts() {
1103+
let code = "x = 1\ny = 2";
1104+
let program = parse_program(code).unwrap();
1105+
let printer = PrettyPrinter::new();
1106+
let output = printer.print_program(&program);
1107+
assert!(output.contains("x = 1"), "First stmt:\n{}", output);
1108+
assert!(output.contains("y = 2"), "Second stmt:\n{}", output);
1109+
}
1110+
1111+
#[test]
1112+
fn pretty_print_return_with_value() {
1113+
let printer = PrettyPrinter::new();
1114+
let stmt = ControlStmt::Return(Some(DataExpr::Number(Number::Int(42))));
1115+
let output = printer.print_control_stmt(&stmt, 0);
1116+
assert_eq!(output, "return 42");
1117+
}
1118+
1119+
#[test]
1120+
fn pretty_print_return_without_value() {
1121+
let printer = PrettyPrinter::new();
1122+
let stmt = ControlStmt::Return(None);
1123+
let output = printer.print_control_stmt(&stmt, 0);
1124+
assert_eq!(output, "return");
1125+
}
1126+
1127+
#[test]
1128+
fn pretty_print_complex_number_formats() {
1129+
let printer = PrettyPrinter::new();
1130+
// Complex with real and positive imaginary
1131+
assert_eq!(printer.print_number(&Number::Complex(3.0, 4.0)), "3+4i");
1132+
// Complex with real and negative imaginary
1133+
assert_eq!(printer.print_number(&Number::Complex(3.0, -4.0)), "3-4i");
1134+
// Pure imaginary
1135+
assert_eq!(printer.print_number(&Number::Complex(0.0, 5.0)), "5i");
1136+
}
1137+
1138+
#[test]
1139+
fn pretty_print_symbolic() {
1140+
let printer = PrettyPrinter::new();
1141+
assert_eq!(printer.print_number(&Number::Symbolic("pi".to_string())), "#pi");
1142+
}
1143+
8971144
// ============================================================================
8981145
// Value::Display formatting
8991146
// ============================================================================

0 commit comments

Comments
 (0)