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
//! Smoke test for issue #215 — verifies that
//! every public `mlxrs::Array` constructor in `array::construction`
//! returns normally (rather than process-exiting) in a stripped-ctor
//! environment (eager `#[ctor]` install skipped, e.g. older rustc,
//! linker-stripped consumer binary, sandboxed
//! `__attribute__((constructor))`) on NORMAL inputs.
//!
//! # This is a SMOKE TEST, NOT a regression detector
//!
//! **Removing `ensure_handler_installed()` from any of the seven
//! constructors here does NOT flip this matrix from green to red on
//! normal inputs.** The reason is structural: no reachable first-FFI
//! call in any of these constructors throws an `std::exception` for
//! inputs we can craft from Rust without an allocator-failure shim. The
//! later `default_stream()` (which itself calls
//! `ensure_handler_installed`) then installs the handler before any
//! throw site we CAN reach actually throws. Surfacing the install-time
//! regression on a NORMAL call would require either targeted allocator
//! failure injection for the first constructor FFI call or a
//! source/AST invariant test for the seven `ensure_handler_installed`
//! placements — the former requires platform-specific build tooling
//! that is out of scope for this fixture, and the latter is forbidden
//! by issue #215 (a 7-round syn-based structural-test spiral that
//! ultimately deleted the original `try_item` structural test).
//!
//! **Therefore, code review of `mlxrs/src/array/construction.rs` is
//! the enforcement mechanism for the install-at-call-site requirement
//! on these seven constructors.** The CRITICAL comments on each
//! constructor's `ensure_handler_installed()` call cross-reference this
//! limitation.
//!
//! **What this test DOES verify**: the current normal-input constructor
//! paths return without process exit in a stripped-ctor environment
//! (no `exit(-1)` from mlx-c's default handler on the inputs exercised
//! here).
//!
//! **What this test does NOT verify**: that `ensure_handler_installed()`
//! is the FIRST executable statement in each constructor. The
//! handler-install placement requirement remains enforced ONLY by code
//! review of `mlxrs/src/array/construction.rs`.
//!
//! # How the smoke matrix detects a process-exit
//!
//! Each constructor's worst-case raw FFI is wrapped in mlx-c's standard
//! `try { ... } catch (std::exception& e) { mlx_error(e.what()); ... }`
//! boilerplate (see `mlxrs-sys/vendor/mlx-c/mlx/c/array.cpp` for
//! `mlx_array_new`, `mlx_array_new_float32`, `mlx_array_new_data`).
//! Without an installed handler, `mlx_error` falls through to the
//! default `printf("[FATAL ERROR] ...") + exit(-1)` (see
//! `mlx_default_error_handler` in
//! `mlxrs-sys/vendor/mlx-c/mlx/c/error.cpp`). The smoke matrix spawns
//! one child per public constructor with a NORMAL input that exercises
//! the FFI path without triggering a throw; the child exits 0 if the
//! constructor returned (Ok or Err — both observable as `exit(0)`/`exit(1)`),
//! and any process-exit from mlx-c's default handler would surface as
//! a non-zero parent-observed exit code other than 1.
//!
//! # Why a child process
//!
//! Identical setup to `stripped_ctor_try_item`: in a normal test binary
//! the `#[ctor]` in `mlxrs::error` runs unconditionally at static init,
//! so every test inherits the installed handler in-process and the
//! stripped-ctor exit(-1) path is unreachable. We reproduce a
//! stripped-ctor environment by re-execing the test binary with
//! `MLXRS_DISABLE_CTOR_FOR_TEST=1` (which the ctor reads at start and
//! returns early on).
//!
//! # First-FFI reachability survey (why normal inputs can't throw)
//!
//! | Constructor | First raw FFI | Reliably-craftable throw? |
//! | ------------------------------ | ----------------------------------- | ------------------------- |
//! | `ones` / `zeros` / `eye` / | `mlx_array_new()` | No — body is just |
//! | `arange` / `linspace` | (returns `mlx_array({nullptr})`) | `mlx_array({nullptr})`, |
//! | | | no allocation, no throw. |
//! | `full` | `mlx_array_new_float32(val)` | Only on `std::bad_alloc` |
//! | | (`new mlx::core::array(val)` + a | of a ~16-byte allocation; |
//! | | scalar `malloc(4)` in `init`) | infeasible without an |
//! | | | allocator-shim test build.|
//! | `from_slice` | `mlx_array_new_data(...)` | Only on `std::bad_alloc` |
//! | | (does `malloc(nbytes)` inside) | of the requested buffer; |
//! | | | shape-product overflow |
//! | | | is rejected earlier by |
//! | | | our `checked_mul` guard. |
//!
//! The stripped-ctor exit(-1) bug CLASS is still reproduced as an
//! executable regression by
//! `stripped_ctor_try_item::try_item_survives_stripped_ctor_environment`,
//! which routes through `mlx_array_item_*` — a throw site reachable via
//! a normal-input `try_item::<f32>()` on a non-scalar. That test
//! protects the bug-class itself; this smoke matrix only verifies that
//! today's seven constructor calls return without process exit on the
//! normal inputs exercised here.
use Command;
const STRIPPED_CTOR_CHILD_ENV: &str = "MLXRS_STRIPPED_CTOR_CHILD";
const DISABLE_CTOR_ENV: &str = "MLXRS_DISABLE_CTOR_FOR_TEST";
const CONSTRUCTOR_SELECTOR_ENV: &str = "MLXRS_STRIPPED_CTOR_CONSTRUCTOR";
/// Constructors covered by the smoke matrix. Each variant maps to a
/// distinct child invocation that calls the named constructor with a
/// normal input.
const CONSTRUCTORS: & = &;
/// Run the named constructor in the child role. Returns exit code 0 on
/// `Result::Ok`, 1 on `Err` (still observable, NOT a process-exit), and
/// 42 on unexpected absence. The parent treats both 0 and 1 as PASS
/// (the constructor returned normally); only a non-zero exit OTHER than
/// 1 (e.g. 255 from mlx-c's default `exit(-1)`) indicates a process-exit
/// from mlx-c's default handler.
!
/// SMOKE TEST (not a regression detector — see module docstring).
/// Verifies each public `Array` constructor returns normally on normal
/// inputs in a stripped-ctor environment. Will NOT flip red if
/// `ensure_handler_installed()` is removed from any of these
/// constructors on normal inputs (no reachable throw site fires before
/// `default_stream()` rescues the missing install). Code review of
/// `mlxrs/src/array/construction.rs` is the enforcement mechanism for
/// the install-at-call-site requirement on these seven functions.