@@ -252,3 +252,226 @@ fn main() -> io::Result<()> {
252
252
253
253
Ok ( ( ) )
254
254
}
255
+
256
+ #[ cfg( test) ]
257
+ mod tests {
258
+ use super :: * ;
259
+ use std:: collections:: HashSet ;
260
+ use unicode_segmentation:: UnicodeSegmentation ;
261
+
262
+ #[ test]
263
+ fn test_random_string_length ( ) {
264
+ for _ in 0 ..1000000 {
265
+ let s = random_string ( ) ;
266
+ let grapheme_count = s. graphemes ( true ) . count ( ) ;
267
+ assert ! (
268
+ ( 1 ..=32 ) . contains( & grapheme_count) ,
269
+ "Grapheme count out of bounds: {} (string: {})" ,
270
+ grapheme_count,
271
+ s
272
+ ) ;
273
+ }
274
+ }
275
+
276
+ #[ test]
277
+ fn test_all_chars_appear ( ) {
278
+ let mut appearances = HashSet :: < & ' static str > :: new ( ) ;
279
+ for _ in 0 ..10000 {
280
+ let s = random_string ( ) ;
281
+ appearances. extend ( CHAR_SET . iter ( ) . filter ( |& & c| s. contains ( c) ) ) ;
282
+ }
283
+ assert_eq ! (
284
+ appearances. len( ) ,
285
+ CHAR_SET . len( ) ,
286
+ "Not all characters appeared in 10000 iterations"
287
+ ) ;
288
+ }
289
+
290
+ #[ test]
291
+ fn test_stream_direction_changes ( ) {
292
+ let mut stream = Stream :: new ( 80 , 24 ) ;
293
+ let mut direction_changes = 0 ;
294
+ let mut last_direction = stream. direction . get_offset ( ) ;
295
+
296
+ for _ in 0 ..1000 {
297
+ stream. update ( 80 , 24 ) ;
298
+ let new_direction = stream. direction . get_offset ( ) ;
299
+ if new_direction != last_direction {
300
+ direction_changes += 1 ;
301
+ }
302
+ last_direction = new_direction;
303
+ }
304
+
305
+ assert ! (
306
+ direction_changes > 50 ,
307
+ "Stream should change direction frequently, only changed {} times" ,
308
+ direction_changes
309
+ ) ;
310
+ }
311
+
312
+ #[ test]
313
+ fn test_stream_bounds ( ) {
314
+ let mut stream = Stream :: new ( 80 , 24 ) ;
315
+ for _ in 0 ..10000 {
316
+ stream. update ( 80 , 24 ) ;
317
+ assert ! (
318
+ stream. x >= 1 && stream. x <= 78 ,
319
+ "X out of bounds: {}" ,
320
+ stream. x
321
+ ) ;
322
+ assert ! ( stream. y >= 1 , "Y below minimum: {}" , stream. y) ;
323
+ }
324
+ }
325
+
326
+ #[ test]
327
+ fn test_color_distribution ( ) {
328
+ let mut color_counts = std:: collections:: HashMap :: new ( ) ;
329
+ for _ in 0 ..10000 {
330
+ let color = random_color ( ) ;
331
+ * color_counts. entry ( format ! ( "{:?}" , color) ) . or_insert ( 0 ) += 1 ;
332
+ }
333
+
334
+ // Check that each color appeared at least once
335
+ assert ! (
336
+ color_counts. len( ) >= COLORS . len( ) ,
337
+ "Not all colors appeared: {:?}" ,
338
+ color_counts
339
+ ) ;
340
+
341
+ // Verify primary colors appear more often than accents
342
+ for weight in COLORS {
343
+ match weight {
344
+ Weight :: Primary ( c, _) => {
345
+ let count = color_counts. get ( & format ! ( "{:?}" , c) ) . unwrap_or ( & 0 ) ;
346
+ assert ! (
347
+ count > & 500 ,
348
+ "Primary color {:?} appeared only {} times" ,
349
+ c,
350
+ count
351
+ ) ;
352
+ }
353
+ Weight :: Accent ( c, _) => {
354
+ let count = color_counts. get ( & format ! ( "{:?}" , c) ) . unwrap_or ( & 0 ) ;
355
+ assert ! (
356
+ count > & 100 ,
357
+ "Accent color {:?} appeared only {} times" ,
358
+ c,
359
+ count
360
+ ) ;
361
+ }
362
+ }
363
+ }
364
+ }
365
+
366
+ #[ test]
367
+ fn test_chaos_probability ( ) {
368
+ let mut new_streams = 0 ;
369
+ let trials = 10000 ;
370
+
371
+ for _ in 0 ..trials {
372
+ if rand:: thread_rng ( ) . gen_bool ( CHAOS ) {
373
+ new_streams += 1 ;
374
+ }
375
+ }
376
+
377
+ let actual_probability = new_streams as f64 / trials as f64 ;
378
+ assert ! (
379
+ ( actual_probability - CHAOS ) . abs( ) < 0.02 ,
380
+ "Chaos probability {} significantly deviated from expected {}" ,
381
+ actual_probability,
382
+ CHAOS
383
+ ) ;
384
+ }
385
+
386
+ #[ test]
387
+ fn test_random_string_content ( ) {
388
+ let s = random_string ( ) ;
389
+ assert ! (
390
+ s. chars( )
391
+ . all( |c| CHAR_SET . iter( ) . any( |& set| set. contains( c) ) ) ,
392
+ "Invalid characters in string: {}" ,
393
+ s
394
+ ) ;
395
+ }
396
+
397
+ #[ test]
398
+ fn test_color_weights ( ) {
399
+ let total: u8 = COLORS
400
+ . iter ( )
401
+ . map ( |c| match c {
402
+ Weight :: Primary ( _, w) | Weight :: Accent ( _, w) => w,
403
+ } )
404
+ . sum ( ) ;
405
+ assert ! ( total > 0 , "Total color weights must be positive" ) ;
406
+
407
+ let mut counts = std:: collections:: HashMap :: new ( ) ;
408
+ for _ in 0 ..1000 {
409
+ let color = random_color ( ) ;
410
+ * counts. entry ( color) . or_insert ( 0 ) += 1 ;
411
+ }
412
+
413
+ // Verify primary colors appear more frequently than accents
414
+ for color_weight in COLORS {
415
+ match color_weight {
416
+ Weight :: Primary ( c, _) => {
417
+ let count = counts. get ( c) . unwrap_or ( & 0 ) ;
418
+ assert ! ( * count > 100 , "Primary color {:?} appeared too rarely" , c) ;
419
+ }
420
+ Weight :: Accent ( _, _) => { }
421
+ }
422
+ }
423
+ }
424
+
425
+ #[ test]
426
+ fn test_stream_boundaries ( ) {
427
+ let mut stream = Stream :: new ( 80 , 24 ) ;
428
+
429
+ // Test multiple updates to ensure boundaries are respected
430
+ for _ in 0 ..1000 {
431
+ stream. update ( 80 , 24 ) ;
432
+ assert ! (
433
+ stream. x > 0 && stream. x < 79 ,
434
+ "X position out of bounds: {}" ,
435
+ stream. x
436
+ ) ;
437
+ assert ! ( stream. y > 0 , "Y position below zero: {}" , stream. y) ;
438
+ }
439
+ }
440
+
441
+ #[ test]
442
+ fn test_direction_distribution ( ) {
443
+ let mut counts = std:: collections:: HashMap :: new ( ) ;
444
+ for _ in 0 ..1000 {
445
+ let dir = Direction :: random ( ) ;
446
+ let offset = dir. get_offset ( ) ;
447
+ * counts. entry ( offset) . or_insert ( 0 ) += 1 ;
448
+ }
449
+
450
+ // Check that all directions are used
451
+ assert_eq ! ( counts. len( ) , 8 , "Not all directions were generated" ) ;
452
+
453
+ // Check for roughly even distribution
454
+ for ( _offset, count) in counts {
455
+ assert ! ( count > 50 , "Direction appeared too rarely: {} times" , count) ;
456
+ }
457
+ }
458
+
459
+ #[ test]
460
+ fn test_stream_movement ( ) {
461
+ let mut stream = Stream :: new ( 80 , 24 ) ;
462
+ let initial_pos = ( stream. x , stream. y ) ;
463
+
464
+ // Store a few positions to verify movement
465
+ let mut positions = vec ! [ initial_pos] ;
466
+ for _ in 0 ..10 {
467
+ stream. update ( 80 , 24 ) ;
468
+ positions. push ( ( stream. x , stream. y ) ) ;
469
+ }
470
+
471
+ // Verify that the stream actually moved
472
+ assert ! (
473
+ positions. windows( 2 ) . any( |w| w[ 0 ] != w[ 1 ] ) ,
474
+ "Stream didn't move from initial position"
475
+ ) ;
476
+ }
477
+ }
0 commit comments