@@ -157,6 +157,205 @@ fn integer_default_violates_ne() {
157157 assert ! ( is_default_violates_check( & validate_one( table) . unwrap_err( ) ) ) ;
158158}
159159
160+ // ---------------------------------------------------------------------------
161+ // Boundary kills: each comparison op exactly at its threshold so the
162+ // `<`/`>`/`==` and EPSILON-arithmetic mutations are distinguished.
163+ // ---------------------------------------------------------------------------
164+
165+ fn boundary_table ( ty : SimpleColumnType , default : DefaultValue , expr : & str ) -> TableDef {
166+ table_with (
167+ "t" ,
168+ col_with_default ( "v" , ColumnType :: Simple ( ty) , default) ,
169+ vec ! [ check_constraint( "c" , expr) ] ,
170+ )
171+ }
172+
173+ #[ test]
174+ fn integer_default_equal_violates_lt_boundary ( ) {
175+ // 5 < 5 is false (violates). Kills apply_op_i64 `< -> <=` and `< -> ==`.
176+ let t = boundary_table ( SimpleColumnType :: Integer , DefaultValue :: Integer ( 5 ) , "v < 5" ) ;
177+ assert ! ( is_default_violates_check( & validate_one( t) . unwrap_err( ) ) ) ;
178+ }
179+
180+ #[ test]
181+ fn float_default_equal_violates_lt_boundary ( ) {
182+ // 5.0 < 5.0 is false. Kills apply_op_f64 Lt `< -> <=`.
183+ let t = boundary_table ( SimpleColumnType :: Real , DefaultValue :: Float ( 5.0 ) , "v < 5.0" ) ;
184+ assert ! ( is_default_violates_check( & validate_one( t) . unwrap_err( ) ) ) ;
185+ }
186+
187+ #[ test]
188+ fn float_default_equal_violates_gt_boundary ( ) {
189+ // 5.0 > 5.0 is false. Kills apply_op_f64 Gt `> -> >=`.
190+ let t = boundary_table ( SimpleColumnType :: Real , DefaultValue :: Float ( 5.0 ) , "v > 5.0" ) ;
191+ assert ! ( is_default_violates_check( & validate_one( t) . unwrap_err( ) ) ) ;
192+ }
193+
194+ #[ test]
195+ fn float_default_equal_satisfies_eq ( ) {
196+ // (2-2).abs() < EPS is true. Kills apply_op_f64 Eq `- -> +`, `- -> /`,
197+ // and `< -> ==`.
198+ let t = boundary_table ( SimpleColumnType :: Real , DefaultValue :: Float ( 2.0 ) , "v = 2.0" ) ;
199+ assert ! ( validate_one( t) . is_ok( ) ) ;
200+ }
201+
202+ #[ test]
203+ fn float_opposite_sign_satisfies_ne ( ) {
204+ // 2 <> -2: (2-(-2)).abs()=4 >= EPS true. Kills apply_op_f64 Ne `- -> +`
205+ // (where (2+(-2)).abs()=0 would wrongly report "equal").
206+ let t = boundary_table (
207+ SimpleColumnType :: Real ,
208+ DefaultValue :: Float ( 2.0 ) ,
209+ "v <> -2.0" ,
210+ ) ;
211+ assert ! ( validate_one( t) . is_ok( ) ) ;
212+ }
213+
214+ #[ test]
215+ fn float_zero_satisfies_ne_nonzero ( ) {
216+ // 0 <> 5: (0-5).abs()=5 >= EPS true. Kills apply_op_f64 Ne `- -> /`
217+ // (where (0/5).abs()=0 would wrongly report "equal").
218+ let t = boundary_table ( SimpleColumnType :: Real , DefaultValue :: Float ( 0.0 ) , "v <> 5.0" ) ;
219+ assert ! ( validate_one( t) . is_ok( ) ) ;
220+ }
221+
222+ #[ test]
223+ fn string_default_equal_violates_lt_boundary ( ) {
224+ // "'m'" < "'m'" is false. Kills apply_op_str `< -> <=` and `< -> ==`.
225+ let t = boundary_table (
226+ SimpleColumnType :: Text ,
227+ DefaultValue :: String ( "'m'" . into ( ) ) ,
228+ "v < 'm'" ,
229+ ) ;
230+ assert ! ( is_default_violates_check( & validate_one( t) . unwrap_err( ) ) ) ;
231+ }
232+
233+ #[ test]
234+ fn string_default_equal_violates_gt_boundary ( ) {
235+ // "'m'" > "'m'" is false. Kills apply_op_str `> -> >=`.
236+ let t = boundary_table (
237+ SimpleColumnType :: Text ,
238+ DefaultValue :: String ( "'m'" . into ( ) ) ,
239+ "v > 'm'" ,
240+ ) ;
241+ assert ! ( is_default_violates_check( & validate_one( t) . unwrap_err( ) ) ) ;
242+ }
243+
244+ #[ test]
245+ fn bool_default_equal_violates_ne ( ) {
246+ // true <> true is false (violates). Kills apply_op_bool delete of the
247+ // `Op::Ne` arm (which would fall through to `_ => true`).
248+ let t = boundary_table (
249+ SimpleColumnType :: Boolean ,
250+ DefaultValue :: Bool ( true ) ,
251+ "v <> true" ,
252+ ) ;
253+ assert ! ( is_default_violates_check( & validate_one( t) . unwrap_err( ) ) ) ;
254+ }
255+
256+ #[ test]
257+ fn float_default_int_literal_violates_evaluate_op_arm ( ) {
258+ // (Float, Integer) arm: 5.0 < 3 is false. Kills evaluate_op delete of the
259+ // `(Float, Integer)` arm (which would fall through to `_ => true`).
260+ let t = boundary_table ( SimpleColumnType :: Real , DefaultValue :: Float ( 5.0 ) , "v < 3" ) ;
261+ assert ! ( is_default_violates_check( & validate_one( t) . unwrap_err( ) ) ) ;
262+ }
263+
264+ #[ test]
265+ fn integer_in_list_match_exercises_literal_equals_int_arm ( ) {
266+ // 5 matches the IN list -> literal_equals (Int,Int) arm. Kills the
267+ // delete of that arm (-> `_ => false` -> not in list -> would violate).
268+ let t = boundary_table (
269+ SimpleColumnType :: Integer ,
270+ DefaultValue :: Integer ( 5 ) ,
271+ "v IN (1, 5, 9)" ,
272+ ) ;
273+ assert ! ( validate_one( t) . is_ok( ) ) ;
274+ }
275+
276+ #[ test]
277+ fn float_in_list_matches_exercise_literal_equals_float_arms ( ) {
278+ // Float==Float, Int==Float, Float==Int IN-list matches. Each
279+ // `(a-b).abs() < EPS` distinguishes `< -> >` (0 > EPS would miss).
280+ assert ! (
281+ validate_one( boundary_table(
282+ SimpleColumnType :: Real ,
283+ DefaultValue :: Float ( 2.0 ) ,
284+ "v IN (2.0)"
285+ ) )
286+ . is_ok( )
287+ ) ;
288+ assert ! (
289+ validate_one( boundary_table(
290+ SimpleColumnType :: Integer ,
291+ DefaultValue :: Integer ( 2 ) ,
292+ "v IN (2.0)"
293+ ) )
294+ . is_ok( )
295+ ) ;
296+ assert ! (
297+ validate_one( boundary_table(
298+ SimpleColumnType :: Real ,
299+ DefaultValue :: Float ( 2.0 ) ,
300+ "v IN (2)"
301+ ) )
302+ . is_ok( )
303+ ) ;
304+ }
305+
306+ #[ test]
307+ fn bool_in_list_match_exercises_literal_equals_bool_arm ( ) {
308+ // true IN (true) -> literal_equals (Bool,Bool) `==`. Kills `== -> !=`.
309+ let t = boundary_table (
310+ SimpleColumnType :: Boolean ,
311+ DefaultValue :: Bool ( true ) ,
312+ "v IN (true)" ,
313+ ) ;
314+ assert ! ( validate_one( t) . is_ok( ) ) ;
315+ }
316+
317+ // `1.0000000000000002` is exactly `1.0 + f64::EPSILON` (the next representable
318+ // double). A value exactly EPSILON away is treated as DISTINCT by the strict
319+ // `(a-b).abs() < EPSILON` tolerance, so the default violates an `=`/`IN` check.
320+ // These pin `<` against `<=` at the tolerance boundary (the `<=` mutant would
321+ // treat the pair as equal and wrongly pass).
322+ const ONE_PLUS_EPS : & str = "1.0000000000000002" ;
323+
324+ #[ test]
325+ fn float_eq_at_exact_epsilon_distance_violates ( ) {
326+ // apply_op_f64 Eq line: `(a-b).abs() < EPSILON`. Kills `< -> <=`.
327+ let expr = format ! ( "v = {ONE_PLUS_EPS}" ) ;
328+ let t = boundary_table ( SimpleColumnType :: Real , DefaultValue :: Float ( 1.0 ) , & expr) ;
329+ assert ! ( is_default_violates_check( & validate_one( t) . unwrap_err( ) ) ) ;
330+ }
331+
332+ #[ test]
333+ fn float_float_in_list_at_exact_epsilon_distance_misses ( ) {
334+ // literal_equals (Float,Float) EPSILON line. Kills `< -> <=`.
335+ let expr = format ! ( "v IN ({ONE_PLUS_EPS})" ) ;
336+ let t = boundary_table ( SimpleColumnType :: Real , DefaultValue :: Float ( 1.0 ) , & expr) ;
337+ assert ! ( is_default_violates_check( & validate_one( t) . unwrap_err( ) ) ) ;
338+ }
339+
340+ #[ test]
341+ fn int_float_in_list_at_exact_epsilon_distance_misses ( ) {
342+ // literal_equals (Integer,Float) EPSILON line. Kills `< -> <=`.
343+ let expr = format ! ( "v IN ({ONE_PLUS_EPS})" ) ;
344+ let t = boundary_table ( SimpleColumnType :: Integer , DefaultValue :: Integer ( 1 ) , & expr) ;
345+ assert ! ( is_default_violates_check( & validate_one( t) . unwrap_err( ) ) ) ;
346+ }
347+
348+ #[ test]
349+ fn float_int_in_list_at_exact_epsilon_distance_misses ( ) {
350+ // literal_equals (Float,Integer) EPSILON line. Kills `< -> <=`.
351+ let t = boundary_table (
352+ SimpleColumnType :: Real ,
353+ DefaultValue :: Float ( 1.000_000_000_000_000_2 ) ,
354+ "v IN (1)" ,
355+ ) ;
356+ assert ! ( is_default_violates_check( & validate_one( t) . unwrap_err( ) ) ) ;
357+ }
358+
160359// ---------------------------------------------------------------------------
161360// Satisfied: every op passes when the default fits
162361// ---------------------------------------------------------------------------
0 commit comments