Scalar generators

Integers

Integers can be generated, and by default they can be positive, or negative. You can force the sign of a number with:

  • Generator\nat() which produces an integer >= 0.
  • Generator\pos() which produces an integer > 0.
  • Generator\neg() which produces an integer < 0.
  • Generator\byte() which produces an integer >= 0 and <= 255.
<?php
use Eris\Generator;
use Eris\TestTrait;

class IntegerTest extends PHPUnit_Framework_TestCase
{
    use TestTrait;

    public function testSumIsCommutative()
    {
        $this->forAll(
            Generator\int(),
            Generator\int()
        )
            ->then(function ($first, $second) {
                $x = $first + $second;
                $y = $second + $first;
                $this->assertEquals(
                    $x,
                    $y,
                    "Sum between {$first} and {$second} should be commutative"
                );
            });
    }

    public function testSumIsAssociative()
    {
        $this->forAll(
            Generator\int(),
            Generator\neg(),
            Generator\pos()
        )
            ->then(function ($first, $second, $third) {
                $x = $first + ($second + $third);
                $y = ($first + $second) + $third;
                $this->assertEquals(
                    $x,
                    $y,
                    "Sum between {$first} and {$second} should be associative"
                );
            });
    }

    public function testByteData()
    {
        $this->forAll(
            Generator\byte()
        )
            ->then(function ($byte) {
                $this->assertTrue(
                    $byte >= 0 && $byte <= 255,
                    "$byte is not a valid value for a byte"
                );
            });
    }
}

For more precise and custom ranges, the Generator\choose() accepts a lower and upper bound for the interval to sample integers from.

<?php
use Eris\Generator;
use Eris\TestTrait;

class ChooseTest extends PHPUnit_Framework_TestCase
{
    use TestTrait;

    public function testSumOfTwoIntegersFromBoundedRangesIsCommutative()
    {
        $this->forAll(
            Generator\choose(-1000, 430),
            Generator\choose(230, -30000)
        )
            ->then(function ($first, $second) {
                $x = $first + $second;
                $y = $second + $first;
                $this->assertEquals(
                    $x,
                    $y,
                    "Sum between {$first} and {$second} should be commutative"
                );
            });
    }
}

Floats

Generator\float() will produce a float value, which can be positive or negative. In this example, testAPropertyHoldingOnlyForPositiveNumbers fails very quickly.

<?php
use Eris\Generator;

class FloatTest extends \PHPUnit_Framework_TestCase
{
    use Eris\TestTrait;

    public function testAPropertyHoldingForAllNumbers()
    {
        $this->forAll(Generator\float())
            ->then(function ($number) {
                $this->assertEquals(
                    0.0,
                    abs($number) - abs($number)
                );
            });
    }

    public function testAPropertyHoldingOnlyForPositiveNumbers()
    {
        $this->forAll(Generator\float())
            ->then(function ($number) {
                $this->assertTrue(
                    $number >= 0,
                    "$number is not a (loosely) positive number"
                );
            });
    }
}

Booleans

Generator\bool() produces a boolean, chosen between true and false. It is mostly useful in conjunction with other Generators.

<?php
use Eris\Generator;
use Eris\TestTrait;

class BooleanTest extends PHPUnit_Framework_TestCase
{
    use TestTrait;

    public function testBooleanValueIsTrueOrFalse()
    {
        $this->forAll(
            Generator\bool()
        )
            ->then(function ($boolValue) {
                $this->assertTrue(
                    ($boolValue === true || $boolValue === false),
                    "$boolValue is not true nor false"
                );
            });
    }
}

Strings

Generator\string() produces a string of arbitrary length. Only printable characters can be included in the string, which is UTF-8. Currently only ASCII characters between 0x33 and 0x126 are used.

<?php
use Eris\Generator;
use Eris\Listener;

function string_concatenation($first, $second)
{
    if (strlen($second) > 5) {
        $second .= 'ERROR';
    }
    return $first . $second;
}

class StringTest extends PHPUnit_Framework_TestCase
{
    use Eris\TestTrait;

    public function testRightIdentityElement()
    {
        $this
            ->forAll(
                Generator\string()
            )
            ->then(function ($string) {
                $this->assertEquals(
                    $string,
                    string_concatenation($string, ''),
                    "Concatenating '$string' to ''"
                );
            });
    }

    public function testLengthPreservation()
    {
        $this
            ->forAll(
                Generator\string(),
                Generator\string()
            )
            ->hook(Listener\log(sys_get_temp_dir().'/eris-string-shrinking.log'))
            ->then(function ($first, $second) {
                $result = string_concatenation($first, $second);
                $this->assertEquals(
                    strlen($first) + strlen($second),
                    strlen($result),
                    "Concatenating '$first' to '$second' gives '$result'" . PHP_EOL
                    . var_export($first, true) . PHP_EOL
                    . "strlen(): " . strlen($first) . PHP_EOL
                    . var_export($second, true) . PHP_EOL
                    . "strlen(): " . strlen($second) . PHP_EOL
                    . var_export($result, true) . PHP_EOL
                    . "strlen(): " . strlen($result) . PHP_EOL
                    . "First hex: " . var_export(bin2hex($first), true) . PHP_EOL
                    . "Second hex: " . var_export(bin2hex($second), true) . PHP_EOL
                    . "Result hex: " . var_export(bin2hex($result), true) . PHP_EOL
                );
            });
    }
}

See also

For more complex use cases, try using a collection generator in conjunction with char().

Characters

Generator\char() generates a character from the chosen charset, by default with a utf-8 encoding. The only supported charset at the time of this writing is basic-latin.

<?php
use Eris\Generator;
use Eris\Antecedent;

class CharacterTest extends PHPUnit_Framework_TestCase
{
    use Eris\TestTrait;

    public function testLengthOfAsciiCharactersInPhp()
    {
        $this->forAll(
            Generator\char(['basic-latin'])
        )
            ->then(function ($char) {
                $this->assertLenghtIs1($char);
            });
    }

    public function testLengthOfPrintableAsciiCharacters()
    {
        $this->forAll(
            Generator\char(['basic-latin'])
        )
            ->when(Antecedent\printableCharacter())
            ->then(function ($char) {
                $this->assertFalse(ord($char) < 32);
            });
    }

    public function testMultiplePrintableCharacters()
    {
        $this
            ->minimumEvaluationRatio(0.1)
            ->forAll(
                Generator\char(['basic-latin']),
                Generator\char(['basic-latin'])
            )
            ->when(Antecedent\printableCharacters())
            ->then(function ($first, $second) {
                $this->assertFalse(ord($first) < 32);
                $this->assertFalse(ord($second) < 32);
            });
    }

    /**
     * @eris-ratio 10
     */
    public function testMultiplePrintableCharactersFromAnnotation()
    {
        $this
            ->forAll(
                Generator\char(['basic-latin']),
                Generator\char(['basic-latin'])
            )
            ->when(Antecedent\printableCharacters())
            ->then(function ($first, $second) {
                $this->assertFalse(ord($first) < 32);
                $this->assertFalse(ord($second) < 32);
            });
    }

    private function assertLenghtIs1($char)
    {
        $length = strlen($char);
        $this->assertEquals(
            1,
            $length,
            "'$char' is too long: $length"
        );
    }
}

Generator\charPrintableAscii() can also be used to limit the range of the character to the set of printable characters, from 0x32 to 0x76.

Constants

Generator\constant() produces always the same value, which is the value used to initialize it. This Generator is useful for debugging and simplifying composite Generators in these occasions.

Often, as shown in testUseConstantGeneratorImplicitly, constant are automatically boxed in this Generator if used where a Generator instance would be required:

<?php

use Eris\Generator;

class ConstantTest extends \PHPUnit_Framework_TestCase
{
    use Eris\TestTrait;

    public function testUseConstantGeneratorExplicitly()
    {
        $this
            ->forAll(
                Generator\nat(),
                Generator\constant(2)
            )
            ->then(function ($number, $alwaysTwo) {
                $this->assertTrue(($number * $alwaysTwo % 2) === 0);
            });
    }

    public function testUseConstantGeneratorImplicitly()
    {
        $this
            ->forAll(
                Generator\nat(),
                2
            )
            ->then(function ($number, $alwaysTwo) {
                $this->assertTrue(($number * $alwaysTwo % 2) === 0);
            });
    }
}

Elements

Generator\elements() produces a value randomly extracted from the specified array. Values can be specified as arguments or with a single, numeric array.

<?php
use Eris\Generator;

class ElementsTest extends \PHPUnit_Framework_TestCase
{
    use Eris\TestTrait;

    public function testElementsOnlyProducesElementsFromTheGivenArguments()
    {
        $this->forAll(
            Generator\elements(1, 2, 3)
        )
            ->then(function ($number) {
                $this->assertContains(
                    $number,
                    [1, 2, 3]
                );
            });
    }

    /**
     * This means you cannot have a Elements Generator with a single element,
     * which is perfectly fine as if you have a single element this generator
     * is useless. Use Constant Generator instead
     */
    public function testElementsOnlyProducesElementsFromTheGivenArrayDomain()
    {
        $this->forAll(
            Generator\elements([1, 2, 3])
        )
            ->then(function ($number) {
                $this->assertContains(
                    $number,
                    [1, 2, 3]
                );
            });
    }


    public function testVectorOfElementsGenerators()
    {
        $this->forAll(
            Generator\vector(
                4,
                Generator\elements([2, 4, 6, 8, 10, 12])
            )
        )
            ->then(function ($vector) {
                $sum = array_sum($vector);
                $isEven = function ($number) {
                    return $number % 2 == 0;
                };
                $this->assertTrue(
                    $isEven($sum),
                    "$sum is not even, but it's the sum of the vector " . var_export($vector, true)
                );
            });
    }
}

testVectorOfElementsGenerators shows how to compose the Elements Generator into a vector() to build a vector of selected, sometimes repeated, elements.

See also

oneOf() does the same with values instead of Generators.