The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
## no critic (RCS,VERSION,encapsulation,Module)

use strict;
use warnings;
use Test::More;


use Bytes::Random::Secure;

# We'll use a weaker source because we're testing for function, quality
# isn't being contested here.  Weak=>1 should assure we use /dev/urandom where
# it's available.  Low entropy chosen to preserve our source.
my $random = Bytes::Random::Secure->new(
  NonBlocking => 1,
  Weak        => 1,
  Bits        => 64
);


# Tests for _closest_divisor().

my @divisors = (  1,  1,  2,  4,  4,  8,  8,  8,  8,
                 16, 16, 16, 16, 16, 16, 16, 16,
                 32, 32, 32, 32, 32, 32, 32, 32, 32
);

# Nearest factor of 2**32 >= $ix;

for my $ix ( 0 .. $#divisors ) {
  is( $random->_closest_divisor($ix), $divisors[$ix],
      "_closest_divisor($ix) == $divisors[$ix]" );
}

is( $random->_closest_divisor(), 1,
    '_closest_divisor() == 1; No param list defaults to zero.' );

ok( ! eval { $random->_closest_divisor(-1); 1; },
    '_closest_divisor(-1) throws on negative input.' );

ok( ! eval { $random->_closest_divisor(2**33); 1 },
    '_closest_divisor(2**33) throws (out of range input).' );

is( $random->_closest_divisor(2**32), 2**32,
    "_closest_divisor(2**32) == 2**32." );

is( $random->_closest_divisor( 2**32 - 1 ), 2**32,
    '_closest_divisor(2**32-1) == 2**32' );

# Tests for _ranged_randoms().

for my $count ( 0 .. 11 ) {
  is( scalar @{[ $random->_ranged_randoms(16,$count) ]}, $count,
      "Requested $count ranged randoms, and got $count." );
}

is( scalar @{[ $random->_ranged_randoms(16) ]}, 0,
    'Requested undefined quantity of ranged randoms, and got zero (default).' );

my( $min, $max );
$min = $max = $random->_ranged_randoms(200, 1);

my $MAX_TRIES = 1_000_000;
my $tries     = 0;
while( ( $min > 0 || $max < 199 ) && $tries++ < $MAX_TRIES ) {
  my $random = ($random->_ranged_randoms(200,1))[0];
  $min = $random < $min ? $random : $min;
  $max = $random > $max ? $random : $max;
}
is( $min, 0, '_ranged_randoms generates range minimum.' );
is( $max, 199, '_ranged_randoms generates range maximum.' );
note "It took $tries tries to hit both min and max.";

# Testing random_string_from().

is( $random->string_from( 'abc', 0 ), '',
    'string_from() with a quantity of zero returns empty string.' );
    
is( $random->string_from( 'abc' ), '',
    'string_from() with an undefined quantity defaults to zero.' );

is( length( $random->string_from( 'abc', 5 ) ), 5,
    'string_from(): Requested 5, got 5.' );

my %bag;
$tries = 0;
while( scalar( keys %bag ) < 26 && $tries++ < $MAX_TRIES ) {
  $bag{ $random->string_from( 'abcdefghijklmnopqrstuvwxyz', 1 ) }++;
}

is( scalar( keys %bag ), 26,
   'string_from() returned all bytes from bag, and only bytes from bag.'
);

ok( ! scalar( grep{ $_ =~ m/[^abcdefghijklmnopqrstuvwxyz]/ } keys %bag ),
    'string_from(): No out of range characters in output.' );

ok( $tries >= 26,
    'string_from():Test validation: took at least 26 tries to hit all 26.' );

note "It took $tries tries to hit them all at least once.";

ok( ! eval { $random->string_from(); 1; },
    'No bag string passed (or bag of zero length) throws an exception.' );

done_testing();