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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
// RUNTIME-002: Implement Structs (Value Types) - EXTREME TDD
// RED → GREEN → REFACTOR cycle
//
// This test file follows EXTREME TDD methodology:
// 1. RED: Write tests FIRST (all marked #[ignore], they WILL fail)
// 2. GREEN: Implement minimal code to make tests pass
// 3. REFACTOR: Add property tests, mutation tests, optimize
//
// Requirements from roadmap.yaml:
// - Value::Struct(HashMap<String, Value>) runtime representation
#![allow(clippy::ignore_without_reason)] // TDD RED phase - tests intentionally ignored until impl
#![allow(missing_docs)]
// - Struct instantiation: Point { x: 1.0, y: 2.0 }
// - Field access: point.x, point.y
// - Value semantics (copy on assignment)
// - Error handling for missing/invalid fields
use assert_cmd::Command;
use predicates::prelude::*;
/// Helper to get ruchy binary command
fn ruchy_cmd() -> Command {
assert_cmd::cargo::cargo_bin_cmd!("ruchy")
}
// ==================== RED PHASE: Unit Tests (Will Fail Initially) ====================
/// Test 1: Basic struct instantiation
#[test]
fn test_runtime_002_struct_instantiation_basic() {
ruchy_cmd()
.arg("-e")
.arg("struct Point { x: i32, y: i32 }; let p = Point { x: 10, y: 20 }; p.x")
.assert()
.success()
.stdout(predicate::str::contains("10"));
}
/// Test 2: Struct field access (x field)
#[test]
fn test_runtime_002_struct_field_access_x() {
ruchy_cmd()
.arg("-e")
.arg("struct Point { x: i32, y: i32 }; let p = Point { x: 10, y: 20 }; p.x")
.assert()
.success()
.stdout(predicate::str::contains("10"));
}
/// Test 3: Struct field access (y field)
#[test]
fn test_runtime_002_struct_field_access_y() {
ruchy_cmd()
.arg("-e")
.arg("struct Point { x: i32, y: i32 }; let p = Point { x: 10, y: 20 }; p.y")
.assert()
.success()
.stdout(predicate::str::contains("20"));
}
/// Test 4: Struct value semantics (copy on assignment)
#[test]
fn test_runtime_002_struct_value_semantics_copy() {
ruchy_cmd()
.arg("-e")
.arg("struct Point { x: i32, y: i32 }; let p1 = Point { x: 0, y: 0 }; let p2 = p1; p2.x = 10; println!(p1.x)")
.assert()
.success()
.stdout(predicate::str::contains("0")); // p1.x unchanged (value semantics)
}
/// Test 5: Nested struct instantiation
#[test]
fn test_runtime_002_struct_nested_structs() {
ruchy_cmd()
.arg("-e")
.arg("struct Point { x: i32 }; struct Rectangle { top_left: Point }; let rect = Rectangle { top_left: Point { x: 10 } }; rect.top_left.x")
.assert()
.success()
.stdout(predicate::str::contains("10"));
}
/// Test 6: Struct with different field types
#[test]
fn test_runtime_002_struct_mixed_field_types() {
ruchy_cmd()
.arg("-e")
.arg("struct Person { name: String, age: i32 }; let p = Person { name: \"Alice\", age: 30 }; p.name")
.assert()
.success()
.stdout(predicate::str::contains("Alice"));
}
/// Test 7: Error handling - missing field
#[test]
fn test_runtime_002_struct_error_missing_field() {
ruchy_cmd()
.arg("-e")
.arg("struct Point { x: i32, y: i32 }; let p = Point { x: 10 }") // Missing y field
.assert()
.failure() // Should fail with error
.stderr(predicate::str::contains("missing field").or(predicate::str::contains("field")));
}
/// Test 8: Error handling - extra field
#[test]
fn test_runtime_002_struct_error_extra_field() {
ruchy_cmd()
.arg("-e")
.arg("struct Point { x: i32, y: i32 }; let p = Point { x: 10, y: 20, z: 30 }") // Extra z field
.assert()
.failure() // Should fail with error
.stderr(predicate::str::contains("unknown field").or(predicate::str::contains("field")));
}
/// Test 9: Error handling - accessing non-existent field
#[test]
fn test_runtime_002_struct_error_invalid_field_access() {
ruchy_cmd()
.arg("-e")
.arg("struct Point { x: i32, y: i32 }; let p = Point { x: 10, y: 20 }; p.z") // z doesn't exist
.assert()
.failure() // Should fail with error
.stderr(predicate::str::contains("field").or(predicate::str::contains("not found")));
}
/// Test 10: Struct with float fields
#[test]
fn test_runtime_002_struct_float_fields() {
ruchy_cmd()
.arg("-e")
.arg("struct Point { x: f64, y: f64 }; let p = Point { x: 1.5, y: 2.5 }; p.x")
.assert()
.success()
.stdout(predicate::str::contains("1.5"));
}
// ==================== REFACTOR PHASE: Property Tests (10K+ Iterations) ====================
#[cfg(test)]
mod property_tests {
use super::*;
use proptest::prelude::*;
/// Property test: Struct field access always returns the correct value
/// Validates: Field access preserves values across any valid integers
#[test]
fn prop_struct_field_access_preserves_values() {
proptest!(|(x: i32, y: i32)| {
let code = format!(
"struct Point {{ x: i32, y: i32 }}; let p = Point {{ x: {x}, y: {y} }}; p.x"
);
let result = ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success();
let stdout = String::from_utf8_lossy(&result.get_output().stdout);
prop_assert!(stdout.contains(&x.to_string()),
"Expected stdout to contain {}, got: {}", x, stdout);
});
}
/// Property test: Nested structs maintain correct values
/// Validates: Nested struct instantiation and field access work for any integers
#[test]
fn prop_nested_structs_preserve_values() {
proptest!(|(x: i32)| {
let code = format!(
"struct Point {{ x: i32 }}; struct Rectangle {{ top_left: Point }}; \
let rect = Rectangle {{ top_left: Point {{ x: {x} }} }}; rect.top_left.x"
);
let result = ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success();
let stdout = String::from_utf8_lossy(&result.get_output().stdout);
prop_assert!(stdout.contains(&x.to_string()),
"Expected stdout to contain {}, got: {}", x, stdout);
});
}
/// Property test: Missing required field always produces error
/// Validates: Error handling is consistent across all field names
#[test]
fn prop_missing_field_always_errors() {
proptest!(|(x: i32)| {
let code = format!(
"struct Point {{ x: i32, y: i32 }}; let p = Point {{ x: {x} }}"
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.failure()
.stderr(predicate::str::contains("field")
.or(predicate::str::contains("Missing")));
});
}
/// Property test: Invalid field access always produces error
/// Validates: Error handling works for any struct instance
#[test]
fn prop_invalid_field_access_always_errors() {
proptest!(|(x: i32, y: i32)| {
let code = format!(
"struct Point {{ x: i32, y: i32 }}; let p = Point {{ x: {x}, y: {y} }}; p.z"
);
ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.failure()
.stderr(predicate::str::contains("field")
.or(predicate::str::contains("not found")));
});
}
/// Property test: Float fields work for any valid f64 values
/// Validates: Struct field access works with floating point numbers
#[test]
fn prop_float_fields_work() {
proptest!(|(x in -1000.0f64..1000.0f64, y in -1000.0f64..1000.0f64)| {
let code = format!(
"struct Point {{ x: f64, y: f64 }}; let p = Point {{ x: {x}, y: {y} }}; p.x"
);
let result = ruchy_cmd()
.arg("-e")
.arg(&code)
.assert()
.success();
let stdout = String::from_utf8_lossy(&result.get_output().stdout);
// Float comparison is tricky, just verify it's a number
prop_assert!(stdout.trim().parse::<f64>().is_ok(),
"Expected stdout to contain a float, got: {}", stdout);
});
}
}
// ==================== RED PHASE: Mutation Test Targets ====================
// Mutation testing targets (for REFACTOR phase):
// 1. Field access logic (wrong field name handling)
// 2. Type checking in struct instantiation
// 3. Copy semantics (ensure no reference sharing)
// 4. Error handling branches (missing/extra fields)
// ==================== Test Summary ====================
#[test]
fn test_runtime_002_red_phase_summary() {
// This test documents the RED phase test plan
//
// Unit Tests Created: 10
// 1. test_runtime_002_struct_instantiation_basic
// 2. test_runtime_002_struct_field_access_x
// 3. test_runtime_002_struct_field_access_y
// 4. test_runtime_002_struct_value_semantics_copy
// 5. test_runtime_002_struct_nested_structs
// 6. test_runtime_002_struct_mixed_field_types
// 7. test_runtime_002_struct_error_missing_field
// 8. test_runtime_002_struct_error_extra_field
// 9. test_runtime_002_struct_error_invalid_field_access
// 10. test_runtime_002_struct_float_fields
//
// All tests currently #[ignore]d and will FAIL when un-ignored (RED phase)
//
// Next Step (GREEN phase):
// 1. Add Value::Struct variant to src/runtime/interpreter.rs
// 2. Implement struct instantiation in eval
// 3. Implement field access in eval
// 4. Implement value semantics (clone on assignment)
// 5. Un-ignore tests one by one and make them pass
//
// After GREEN (REFACTOR phase):
// 1. Add 10K+ property tests
// 2. Run mutation tests (target ≥75%)
// 3. Optimize if needed while maintaining tests
assert!(
true,
"RED phase: 10 tests created, all will fail when un-ignored"
);
}