scratch – Rev 87

Subversion Repositories:
Rev:
<?php
/** Copyright (C) Fusonic GmbH (http://www.fusonic.net) 2013, All rights reserved. */

use Fusonic\Linq\Linq;

/**
 * Interface for test purposes only.
 */
interface StubInterface
{
}

/**
 * Class for test purposes only.
 */
final class Stub
        implements
        StubInterface
{
}

/**
 * Class for test purposes only.
 */
final class StubWithoutInterface
{
}

class LinqTest extends PHPUnit_Framework_TestCase
{
    const ExceptionName_UnexpectedValue = "UnexpectedValueException";
    const ExceptionName_InvalidArgument = "InvalidArgumentException";
    const ExceptionName_OutOfRange = "OutOfRangeException";
    const ExceptionName_Runtime = "RuntimeException";

    /** Returns the only element of a sequence that satisfies a specified condition, and throws an exception if more than one such element exists.
     */
    public function testSingle_TestBehaviour()
    {
        // more than one
        $items = array(1, 2);
        $this->assertException(function() use ($items) {
            Linq::from($items)->single();
        });

        // no matching elements
        $items = array();
        $this->assertException(function() use ($items) {
            Linq::from($items)->single();
        });

        // OK
        $items = array(77);
        $this->assertSame(77, Linq::from($items)->single());

        // With closure

        // more than one
        $items = array(1, 2);
        $this->assertException(function() use ($items) {
            Linq::from($items)->single(function($x) { return true; });
        });

        // no matching elements
        // because of false closure
        $this->assertException(function() use ($items) {
            Linq::from($items)->single(function($x) { return false; });
        });

        // because of empty array
        $items = array();
        $this->assertException(function() use ($items) {
            Linq::from($items)->single(function($x) { return true; });
        });

        // OK
        $items = array(77);
        $this->assertSame(77, Linq::from($items)->single(function($x) { return true; }));
    }

    public function testCount_ReturnsCorrectAmounts()
    {
        $items = array(1, 2);
        $this->assertEquals(2, Linq::from($items)->count());

        $items = array(1, 2);
        $this->assertEquals(1, Linq::from($items)->where(function($x) {
            return $x == 2;
        })->count());

        $items = array(1, 2);
        $this->assertEquals(0, Linq::from($items)->where(function($x) {
            return false;
        })->count());

        $items = array();
        $this->assertEquals(0, Linq::from($items)->count());
    }

    /** Returns the only element of a sequence that satisfies a specified condition or a default value if no such element exists;
     * this method throws an exception if more than one element satisfies the condition.
     */
    public function testSingleOrNull_TestBehaviour()
    {
        // more than one
        $items = array(1, 2);
        $this->assertException(function() use ($items) {
            Linq::from($items)->singleOrNull();
        });

        // no matching elements
        $items = array();
        $this->assertNull(Linq::from($items)->singleOrNull());

        // OK
        $items = array(77);
        $this->assertSame(77, Linq::from($items)->singleOrNull());

        // With closure

        // more than one
        $items = array(1, 2);
        $this->assertException(function() use ($items) {
            Linq::from($items)->singleOrNull(function($x) { return true; });
        });

        // no matching elements
        // because of false closure
        $this->assertNull(Linq::from($items)->singleOrNull(function($x) { return false; }));

        // because of empty array
        $items = array();
        $this->assertNull(Linq::from($items)->singleOrNull());

        // OK
        $items = array(77);
        $this->assertSame(77, Linq::from($items)->singleOrNull(function($x) { return true; }));
    }

    /** Returns the first element in a sequence that satisfies a specified condition.
     *
     *  Exceptions:
     *  No element satisfies the condition in predicate.
        -or-
        The source sequence is empty.
     */
    public function testFirst_TestBehaviour()
    {
        $a = new stdClass();
        $a->value = "a";

        $b1 = new stdClass();
        $b1->value = "b";

        $b2 = new stdClass();
        $b2->value = "b";

        $c = new stdClass();
        $c->value = "c";

        // more than one
        $items = array($a, $b1, $b2, $c);
        $this->assertSame($a, Linq::from($items)->first());

        // no matching elements
        $items = array();
        $this->assertException(function() use ($items) {
            Linq::from($items)->first();
        });

        $items = array($a);
        $this->assertSame($a, Linq::from($items)->first());

        // #### With closures ###

        // more than one
        $items = array($a, $b1, $b2, $c);
        $this->assertSame($b1, Linq::from($items)->first(function($x) { return $x->value == "b"; }));

        // no matching elements
        // because of false closure
        $this->assertException(function() use ($items) {
            Linq::from($items)->first(function($x) { return false; });
        });

        // because of empty array
        $items = array();
        $this->assertException(function() use ($items) {
            Linq::from($items)->first(function($x) { return true; });
        });

        // OK
        $items = array($a);
        $this->assertSame($a, Linq::from($items)->first(function($x) { return true; }));
    }

    /**
     * Returns the first element of a sequence, or a default value if the sequence contains no elements.
     */
    public function testFirstOrNull_DoesReturnTheFirstElement_OrNull_DoesNotThrowExceptions()
    {
        $a = new stdClass();
        $a->value = "a";

        $b1 = new stdClass();
        $b1->value = "b";

        $b2 = new stdClass();
        $b2->value = "b";

        $c = new stdClass();
        $c->value = "c";

        $items = array($a, $b1, $b2, $c);
        $this->assertSame($a, Linq::from($items)->firstOrNull());

        $items = array();
        $this->assertNull(Linq::from($items)->firstOrNull());

        // #### With closures ###

        $items = array($a, $b1, $b2, $c);
        $this->assertSame($b1, Linq::from($items)->firstOrNull(function($x) { return $x->value == "b"; }));

        $items = array($a, $b1, $b2, $c);
        $this->assertSame($c, Linq::from($items)->firstOrNull(function($x) { return $x->value == "c"; }));

        $items = array();
        $this->assertNull(Linq::from($items)->firstOrNull(function($x) { return true; }));
    }


    public function testLast_DoesReturnTheLastElement_OrThrowsExceptions()
    {
        $a = new stdClass();
        $a->value = "a";

        $b1 = new stdClass();
        $b1->value = "b";

        $b2 = new stdClass();
        $b2->value = "b";

        $c = new stdClass();
        $c->value = "c";

        // more than one
        $items = array($a, $b1, $b2, $c);
        $last = Linq::from($items)->last();
        $this->assertSame($c, $last);

        // no matching elements
        $items = array();
        $this->assertException(function() use ($items) {
            Linq::from($items)->last();
        });

        $items = array($a);
        $this->assertSame($a, Linq::from($items)->last());

        // #### With closures ###

        // more than one
        $items = array($a, $b1, $b2, $c);
        $this->assertSame($b2, Linq::from($items)->last(function($x) { return $x->value == "b"; }));

        // no matching elements
        // because of false closure
        $this->assertException(function() use ($items) {
            Linq::from($items)->last(function($x) { return false; });
        });

        // because of empty array
        $items = array();
        $this->assertException(function() use ($items) {
            Linq::from($items)->last(function($x) { return true; });
        });

        // OK
        $items = array($a);
        $this->assertSame($a, Linq::from($items)->last(function($x) { return true; }));
    }

    public function testLastOrDefault_DoesReturnTheLastElement_OrNull_DoesNotThrowExceptions()
    {
        $a = new stdClass();
        $a->value = "a";

        $b1 = new stdClass();
        $b1->value = "b";

        $b2 = new stdClass();
        $b2->value = "b";

        $c = new stdClass();
        $c->value = "c";

        $items = array($a, $b1, $b2, $c);
        $this->assertSame($c, Linq::from($items)->lastOrNull());

        $items = array();
        $this->assertNull(Linq::from($items)->lastOrNull());

        // #### With closures ###

        $items = array($a, $b1, $b2, $c);
        $this->assertSame($c, Linq::from($items)->lastOrNull(function($x) { return true; }));

        $items = array($a, $b1, $b2, $c);
        $this->assertSame($b2, Linq::from($items)->lastOrNull(function($x) { return $x->value == "b"; }));

        $items = array();
        $this->assertNull(Linq::from($items)->lastOrNull(function($x) { return true; }));
    }

    public function testWhere_ReturnsOnlyValuesMatching()
    {
        $a = new stdClass();
        $a->value = "a";

        $b1 = new stdClass();
        $b1->value = "b";

        $b2 = new stdClass();
        $b2->value = "b";

        $c = new stdClass();
        $c->value = "c";

        $items = array($a, $b1, $b2, $c);
        $matching = Linq::from($items)->where(function ($v) { return false; });
        $this->assertTrue($matching instanceof Linq);

        $matching = $matching->toArray();

        $this->assertEquals(0, count($matching));

        $matching = Linq::from($items)->where(function ($v) { return true; })->toArray();
        $this->assertEquals(4, count($matching));
        $this->assertTrue(in_array($a, (array)$matching));
        $this->assertTrue(in_array($b1, (array)$matching));
        $this->assertTrue(in_array($b2, (array)$matching));
        $this->assertTrue(in_array($c, (array)$matching));

        $matching = Linq::from($items)->where(function ($v) { return $v->value == "b"; })->toArray();
        $this->assertEquals(2, count($matching));
        $this->assertFalse(in_array($a, (array)$matching));
        $this->assertTrue(in_array($b1, (array)$matching));
        $this->assertTrue(in_array($b2, (array)$matching));
        $this->assertFalse(in_array($c, (array)$matching));
    }

    public function testWhere_ThrowsExceptionIfPredicateDoesNotReturnABoolean()
    {
        $this->assertException(function()
        {
            $items = array("1", "2", "3");
            $matching = Linq::from($items)->where(function ($v) { return "NOT A BOOLEAN"; });
            $matching->toArray();
        }, self::ExceptionName_UnexpectedValue);
    }

    public function testWhere_DoesLazyEvaluation()
    {
        $eval = false;
        $items = array("1", "2", "3");
        $matching = Linq::from($items)->where(function ($v) use(&$eval)
        {
            $eval = true;
            return true;
        });

        $this->assertFalse($eval, "SelectMany did execute before iterating!");
        $matching->toArray();
        $this->assertTrue($eval);
    }

    public function testWhere_EmptySequence_ReturnsEmptySequence()
    {
        $items = array();
        $matching = Linq::from($items)->where(function ($v)
        {
            return true;
        });

        $this->assertEquals(0, $matching->count());
        $array = $matching->toArray();
        $this->assertEquals(0, count($array));
    }

    public function testCountable_implementedSqlInterface()
    {
        $items = array(1,2,3);

        $matching = Linq::from($items);

        $this->assertEquals(3, count($matching));
        $this->assertEquals(3, $matching->count());
    }

    public function testSkipWithTake_Combined_SkipAndTakeValuesByAmount()
    {
        $items = array("a", "b", "c", "d", "e", "f");
        $matching = Linq::from($items)->skip(2)->take(0);
        $this->assertEquals(0, $matching->count());

        $matching = Linq::from($items)->skip(0)->take(0);
        $this->assertEquals(0, $matching->count());

        $matching = Linq::from($items)->skip(0)->take(2);
        $this->assertEquals(2, $matching->count());
        $array = $matching->toArray();
        $this->assertEquals("a", $array[0]);
        $this->assertEquals("b", $array[1]);

        $matching = Linq::from($items)->skip(2)->take(2);
        $this->assertEquals(2, $matching->count());
        $array = $matching->toArray();
        $this->assertEquals("c", $array[0]);
        $this->assertEquals("d", $array[1]);

        $matching = Linq::from($items)->skip(4)->take(99);
        $this->assertEquals(2, $matching->count());
        $array = $matching->toArray();
        $this->assertEquals("e", $array[0]);
        $this->assertEquals("f", $array[1]);
    }

    public function testTakeSkip_Combined_TakeAndSkipValuesByAmount()
    {
        $items = array("a", "b", "c", "d", "e", "f");
        $matching = Linq::from($items)->take(0)->skip(0);
        $this->assertEquals(0, $matching->count());

        $matching = Linq::from($items)->take(2)->skip(2);
        $this->assertEquals(0, $matching->count());

        $matching = Linq::from($items)->take(2)->skip(0);
        $this->assertEquals(2, $matching->count());
        $array = $matching->toArray();
        $this->assertEquals("a", $array[0]);
        $this->assertEquals("b", $array[1]);
    }

    public function testSkip_SkipValuesByAmount()
    {
        $items = array("a", "b", "c", "d", "e", "f");
        $matching = Linq::from($items)->skip(2);
        $this->assertTrue($matching instanceof Linq);

        $this->assertEquals(4, $matching->count());
        $matching = $matching->toArray();

        $this->assertTrue(in_array("c", $matching));
        $this->assertTrue(in_array("d", $matching));
        $this->assertTrue(in_array("e", $matching));
        $this->assertTrue(in_array("f", $matching));

        $items = array("a", "b", "c", "d", "e", "f");

        $matching = Linq::from($items)->skip(0);
        $this->assertEquals(6, $matching->count());
        $array = $matching->toArray();
        $this->assertEquals("a", $array[0]);
        $this->assertEquals("b", $array[1]);
        $this->assertEquals("c", $array[2]);
        $this->assertEquals("d", $array[3]);
        $this->assertEquals("e", $array[4]);
        $this->assertEquals("f", $array[5]);

        $matching = Linq::from($items)->skip(5);
        $this->assertEquals(1, $matching->count());
        $array = $matching->toArray();
        $this->assertEquals("f", $array[0]);

        $matching = Linq::from($items)->skip(6);
        $this->assertEquals(0, $matching->count());

        $matching = Linq::from($items)->skip(7);
        $this->assertEquals(0, $matching->count());

        // Test against empty sequence:

        $matching = Linq::from(array())->skip(0);
        $this->assertEquals(0, $matching->count());

        $matching = Linq::from(array())->skip(6);
        $this->assertEquals(0, $matching->count());
    }

    public function testTake_TakeValuesByAmount()
    {
        $items = array("a", "b", "c", "d", "e", "f");
        $matching = Linq::from($items)->take(4);
        $this->assertTrue($matching instanceof Linq);

        $this->assertEquals(4, $matching->count());
        $matching = $matching->toArray();
        $this->assertTrue(in_array("a", $matching));
        $this->assertTrue(in_array("b", $matching));
        $this->assertTrue(in_array("c", $matching));
        $this->assertTrue(in_array("d", $matching));

        $matching = Linq::from($items)->take(0);
        $this->assertEquals(0, $matching->count());
    }

    public function testAll_WorksCorrectly()
    {
        // All must always return true on empty sequences:
        $items = array();
        $all = Linq::from($items)->all(function($v) { return true; });
        $this->assertTrue($all);

        $all = Linq::from($items)->all(function($v) { return false; });
        $this->assertTrue($all);

        // Test with values:
        $items = array("a", "b");
        $all = Linq::from($items)->all(function($v) { return $v == "a"; });
        $this->assertFalse($all);

        $all = Linq::from($items)->all(function($v) { return $v == "a" || $v == "b"; });
        $this->assertTrue($all);
    }

    public function testAny_WithFunc_CorrectResults()
    {
        // Any must always return false on empty sequences:
        $items = array();
        $any = Linq::from($items)->any(function($v) { return true; });
        $this->assertFalse($any);

        $any = Linq::from($items)->any(function($v) { return false; });
        $this->assertFalse($any);

        // Test with values:
        $items = array("a", "b");
        $any = Linq::from($items)->any(function($v) { return $v == "not existing"; });
        $this->assertFalse($any);

        $any = Linq::from($items)->any(function($v) { return $v == "a"; });
        $this->assertTrue($any);
    }

    public function testAny_WithoutFunc_CorrectResults()
    {
        $items = array();
        $this->assertFalse(Linq::from($items)->any());

        $items = array("a");
        $this->assertTrue(Linq::from($items)->any());

        $items = array("a", "b", "c");
        $this->assertTrue(Linq::from($items)->any());
    }

    public function testAverage_throwsExceptionIfClosureReturnsNotNumericValue()
    {
        $this->assertException(function() {
           $items = array(2, new stdClass());
            Linq::from($items)->average();
        }, self::ExceptionName_UnexpectedValue);

        $this->assertException(function() {
            $items = array(2, "no numeric value");
            Linq::from($items)->average();
        }, self::ExceptionName_UnexpectedValue);

        $this->assertException(function() {
            $cls = new stdClass();
            $cls->value = "no numeric value";
            $items = array($cls);
            Linq::from($items)->average(function($x) { return $x->value; });
        }, self::ExceptionName_UnexpectedValue);
    }

    public function testAverage_CalculatesCorrectAverage()
    {
        $items = array(2, 4, 6);
        $avg = Linq::from($items)->average();
        $this->assertEquals(4, $avg);

        $items = array(2.5, 2.5);
        $avg = Linq::from($items)->average();
        $this->assertEquals(2.5, $avg);

        $items = array(2, "4", "6");
        $avg = Linq::from($items)->average();
        $this->assertEquals(4, $avg);

        $items = array(2, 4, 6);
        $avg = Linq::from($items)->average(function($v) { return 1; });
        $this->assertEquals(1, $avg);

        $items = array(2.5, 2.5);
        $avg = Linq::from($items)->average(function($v) { return $v; });
        $this->assertEquals(2.5, $avg);

        $a = new stdClass();
        $a->value = 2;

        $b = new stdClass();
        $b->value = "4";

        $c = new stdClass();
        $c->value = "6";

        $items = array($a, $b, $c);
        $avg = Linq::from($items)->average(function($v) { return $v->value; });
        $this->assertEquals(4, $avg);
    }

    public function testOrderBy_NumericValues_WithSelector_ReturnsOrderedObjects()
    {
        $a = new stdClass(); $a->value = 77;
        $b = new stdClass(); $b->value = 10;
        $c = new stdClass(); $c->value = 20;
        $items = array($a, $b, $c);

        $ascending = Linq::from($items)->orderBy(function($x)
        {
            return $x->value;
        });
        $this->assertEquals(3, $ascending->count());
        $items = $ascending->toArray();

        $this->assertSame($b, $items[0]);
        $this->assertSame($c, $items[1]);
        $this->assertSame($a, $items[2]);

        $a = new stdClass(); $a->value = 77;
        $b = new stdClass(); $b->value = 10.44;
        $c = new stdClass(); $c->value = 20;
        $d = new stdClass(); $d->value = 20;
        $items = array($a, $b, $c, $d);

        $ascending = Linq::from($items)->orderBy(function($x) { return $x->value; });
        $this->assertEquals(4, $ascending->count());
        $items = $ascending->toArray();

        $this->assertSame($b, $items[0]);

        // It is not predictable in which order objects with the same order key are ordered, both positions are valid:
        $pos1 = $items[1];
        $pos2 = $items[2];
        $this->assertTrue($pos1 === $d || $pos1 === $c);
        $this->assertTrue($pos1 === $d || $pos1 === $c);
        $this->assertNotsame($pos1, $pos2);

        $this->assertSame($a, $items[3]);
    }

    public function testOrderBy_NumericValues_ReturnsCorrectOrders()
    {
        $items = array(77, 10, 20);
        $ascending = Linq::from($items)->orderBy(function($x) { return $x; });
        $this->assertTrue($ascending instanceof Linq);

        $ascending = $ascending->toArray();

        $this->assertEquals(10, $ascending[0]);
        $this->assertEquals(20, $ascending[1]);
        $this->assertEquals(77, $ascending[2]);

        // Verify that original collection is unmodified:
        $this->assertEquals(77, $items[0]);
        $this->assertEquals(10, $items[1]);
        $this->assertEquals(20, $items[2]);

        $items = array(12.33, 8.21, 11.3, 8.21, 33);
        $ascending = Linq::from($items)->orderBy(function($x) { return $x; });

        $ascending = $ascending->toArray();
        $this->assertEquals(8.21, $ascending[0]);
        $this->assertEquals(8.21, $ascending[1]);
        $this->assertEquals(11.3, $ascending[2]);
        $this->assertEquals(12.33, $ascending[3]);
        $this->assertEquals(33, $ascending[4]);
    }

    public function testOrderBy_StringValues_ReturnsCorrectOrders()
    {
        $items = array("e", "a", "c");
        $ascending = Linq::from($items)->orderBy(function($x) { return $x; });
        $this->assertTrue($ascending instanceof Linq);

        $ascending = $ascending->toArray();
        $this->assertEquals("a", $ascending[0]);
        $this->assertEquals("c", $ascending[1]);
        $this->assertEquals("e", $ascending[2]);

        // Verify that original collection is unmodified:
        $this->assertEquals("e", $items[0]);
        $this->assertEquals("a", $items[1]);
        $this->assertEquals("c", $items[2]);
    }

    public function testOrderBy_DateTimeValues_ReturnsCorrectOrders()
    {
        $items = array(new DateTime("27.10.2011"), new DateTime("03.04.2012"), new DateTime("01.01.2005"));
        $ascending = Linq::from($items)->orderBy(function($x) { return $x; });
        $this->assertTrue($ascending instanceof Linq);
        $ascending = $ascending->toArray();

        $this->assertEquals(new DateTime("01.01.2005"), $ascending[0]);
        $this->assertEquals(new DateTime("27.10.2011"), $ascending[1]);
        $this->assertEquals(new DateTime("03.04.2012"), $ascending[2]);

        // Verify that original collection is unmodified:
        $this->assertEquals(new DateTime("27.10.2011"), $items[0]);
        $this->assertEquals(new DateTime("03.04.2012"), $items[1]);
        $this->assertEquals(new DateTime("01.01.2005"), $items[2]);
    }

    public function testOrderByDescending_NumericValues_ReturnsCorrectOrders()
    {
        $items = array(77, 10, 20);
        $desc = Linq::from($items)->orderByDescending(function($x) { return $x; });
        $this->assertTrue($desc instanceof Linq);
        $desc = $desc->toArray();

        $this->assertEquals(77, $desc[0]);
        $this->assertEquals(20, $desc[1]);
        $this->assertEquals(10, $desc[2]);

        // Verify that original collection is unmodified:
        $this->assertEquals(77, $items[0]);
        $this->assertEquals(10, $items[1]);
        $this->assertEquals(20, $items[2]);

        $items = array(12.33, 8.21, 11.3, 8.21, 33);
        $desc = Linq::from($items)->orderByDescending(function($x) { return $x; });
        $desc = $desc->toArray();
        $this->assertEquals(33, $desc[0]);
        $this->assertEquals(12.33, $desc[1]);
        $this->assertEquals(11.3, $desc[2]);
        $this->assertEquals(8.21, $desc[3]);
        $this->assertEquals(8.21, $desc[4]);
    }

    public function testOrderByDescending_NumericValues_WithSelector_ReturnsOrderedObjects()
    {
        $a = new stdClass(); $a->value = 77;
        $b = new stdClass(); $b->value = 10;
        $c = new stdClass(); $c->value = 20;
        $items = array($a, $b, $c);

        $ascending = Linq::from($items)->orderByDescending(function($x) { return $x->value; });
        $this->assertEquals(3, $ascending->count());
        $items = $ascending->toArray();

        $this->assertSame($a, $items[0]);
        $this->assertSame($c, $items[1]);
        $this->assertSame($b, $items[2]);

        $a = new stdClass(); $a->value = 77;
        $b = new stdClass(); $b->value = 10.44;
        $c = new stdClass(); $c->value = 20;
        $d = new stdClass(); $d->value = 20;
        $items = array($a, $b, $c, $d);

        $ascending = Linq::from($items)->orderByDescending(function($x) { return $x->value; });
        $this->assertEquals(4, $ascending->count());
        $items = $ascending->toArray();

        $this->assertSame($a, $items[0]);
        $this->assertSame($c, $items[1]);

        // It is not predictable in which order objects with the same order key are ordered, both positions are valid:
        $pos1 = $items[2];
        $pos2 = $items[3];
        $this->assertTrue($pos1 === $d || $pos1 === $c);
        $this->assertTrue($pos1 === $d || $pos1 === $c);
        $this->assertNotsame($pos1, $pos2);
    }

    public function testOrderByDescending_DateTimeValues_ReturnsCorrectOrders()
    {
        $items = array(new DateTime("27.10.2011"), new DateTime("03.04.2012"), new DateTime("01.01.2005"));
        $desc = Linq::from($items)->orderByDescending(function($x) { return $x; });
        $this->assertTrue($desc instanceof Linq);
        $desc = $desc->toArray();

        $this->assertEquals(new DateTime("03.04.2012"), $desc[0]);
        $this->assertEquals(new DateTime("27.10.2011"), $desc[1]);
        $this->assertEquals(new DateTime("01.01.2005"), $desc[2]);

        // Verify that original collection is unmodified:
        $this->assertEquals(new DateTime("27.10.2011"), $items[0]);
        $this->assertEquals(new DateTime("03.04.2012"), $items[1]);
        $this->assertEquals(new DateTime("01.01.2005"), $items[2]);
    }

    public function testOrderBy_Descending_StringValues_ReturnsCorrectOrders()
    {
        $items = array("e", "a", "c");
        $desc = Linq::from($items)->orderByDescending(function($x) { return $x; });
        $this->assertTrue($desc instanceof Linq);
        $desc = $desc->toArray();

        $this->assertEquals("e", $desc[0]);
        $this->assertEquals("c", $desc[1]);
        $this->assertEquals("a", $desc[2]);

        // Verify that original collection is unmodified:
        $this->assertEquals("e", $items[0]);
        $this->assertEquals("a", $items[1]);
        $this->assertEquals("c", $items[2]);
    }

    public function testOrderBy_DoesMakeLazyEvalution()
    {
        $items = array("e", "a", "c");
        $eval = false;
        $linq = Linq::from($items)->orderByDescending(function($x) use(&$eval)
        {
            $eval = true;
            return $x;
        });

        $this->assertFalse($eval, "OrderBy did execute before iterating!");
        $result = $linq->toArray();
        $this->assertTrue($eval);
    }

    public function testGroupBy()
    {
        $a1 = new stdClass(); $a1->id = 1; $a1->value = "a";
        $a2 = new stdClass(); $a2->id = 2; $a2->value = "a";
        $b1 = new stdClass(); $b1->id = 3; $b1->value = "b";

        $items = array ($a1, $a2, $b1);
        $grouped = Linq::from($items)->groupBy(function($x) {
            return $x->value;
        });

        $this->assertTrue($grouped instanceof Linq);

        $this->assertEquals(2, $grouped->count());
        $aGroup = $grouped->elementAt(0);
        $this->assertTrue($aGroup instanceof Fusonic\Linq\GroupedLinq);

        $this->assertEquals("a", $aGroup->key());
        $this->assertEquals(2, $aGroup->count());
        $this->assertSame($a1, $aGroup->elementAt(0));
        $this->assertSame($a2, $aGroup->elementAt(1));

        $bGroup = $grouped->elementAt(1);
        $this->assertEquals("b", $bGroup->key());
        $this->assertEquals(1, $bGroup->count());
        $this->assertSame($b1, $bGroup->elementAt(0));
    }

    public function testElementAt_ReturnsElementAtPositionOrThrowsException()
    {
        $items = array ("a", "b", "c");
        $this->assertEquals("a", Linq::from($items)->elementAt(0));
        $this->assertEquals("b", Linq::from($items)->elementAt(1));
        $this->assertEquals("c", Linq::from($items)->elementAt(2));

        $items = array("a" => "aValue", "b" => "bValue");
        $this->assertEquals("aValue", Linq::from($items)->elementAt(0));
        $this->assertEquals("bValue", Linq::from($items)->elementAt(1));

        $this->assertException(function() {
            $items = array();
            Linq::from($items)->elementAt(0);
        }, self::ExceptionName_OutOfRange);

        $this->assertException(function() {
            $items = array();
            Linq::from($items)->elementAt(1);
        }, self::ExceptionName_OutOfRange);

        $this->assertException(function() {
            $items = array();
            Linq::from($items)->elementAt(-1);
        }, self::ExceptionName_OutOfRange);

        $this->assertException(function() {
            $items = array("a");
            Linq::from($items)->elementAt(1);
        }, self::ExceptionName_OutOfRange);

        $this->assertException(function() {
            $items = array("a", "b");
            Linq::from($items)->elementAt(2);
        }, self::ExceptionName_OutOfRange);

        $this->assertException(function() {
            $items = array("a", "b");
            Linq::from($items)->elementAt(-1);
        }, self::ExceptionName_OutOfRange);

        $this->assertException(function() {
            $items = array("a" => "value", "b" => "bValue");
            Linq::from($items)->elementAt(2);
        }, self::ExceptionName_OutOfRange);
    }

    public function testElementAtOrNull_ReturnsElementAtPositionOrNull()
    {
        $items = array ("a", "b", "c");
        $this->assertEquals("a", Linq::from($items)->elementAtOrNull(0));
        $this->assertEquals("b", Linq::from($items)->elementAtOrNull(1));
        $this->assertEquals("c", Linq::from($items)->elementAtOrNull(2));

        $this->assertNull(Linq::from($items)->elementAtOrNull(3));
        $this->assertNull(Linq::from($items)->elementAtOrNull(4));
        $this->assertNull(Linq::from($items)->elementAtOrNull(-1));

        $items = array();
        $this->assertNull(Linq::from($items)->elementAtOrNull(3));
        $this->assertNull(Linq::from($items)->elementAtOrNull(4));
        $this->assertNull(Linq::from($items)->elementAtOrNull(-1));

        $items = array("a" => "value", "b" => "bValue");
        $this->assertEquals("value", Linq::from($items)->elementAtOrNull(0));
        $this->assertNull(Linq::from($items)->elementAtOrNull(2));
    }

    public function testSelect_ReturnsProjectedSequence()
    {
        $a1 = new stdClass(); $a1->value = "a1";
        $a2 = new stdClass(); $a2->value = "a2";
        $a3 = new stdClass(); $a3->value = "a3";
        $a4 = new stdClass(); $a4->value = "a4";

        // more than one
        $items = array($a1, $a2, $a3, $a4);

        $projected = Linq::from($items)->select(function($v){
           return $v->value;
        });

        $this->assertTrue($projected instanceof Linq);
        $this->assertEquals(4, $projected->count());

        $projected = $projected->toArray();
        $this->assertEquals("a1", $projected[0]);
        $this->assertEquals("a2", $projected[1]);
        $this->assertEquals("a3", $projected[2]);
        $this->assertEquals("a4", $projected[3]);

        $items = array();

        $projected = Linq::from($items)->select(function($v){
            return $v->value;
        });

        $this->assertEquals(0, $projected->count());
    }

    public function testSelectMany_throwsExceptionIfElementIsNotIterable()
    {
        $a1 = new stdClass(); $a1->value = "a1";
        $items = array($a1);

        $this->assertException(function() use($items) {
            Linq::from($items)->selectMany(function($v) {
                return $v->value;
            })->toArray();

        }, self::ExceptionName_UnexpectedValue);

        $this->assertException(function() use($items) {
            Linq::from($items)->selectMany(function($v) {
                return null;
            })->toArray();
        }, self::ExceptionName_UnexpectedValue);
    }

    public function testSelectMany_ReturnsFlattenedSequence()
    {
        $a1 = new stdClass(); $a1->value = array("a", "b");
        $a2 = new stdClass(); $a2->value = array("c", "d");
        $items = array($a1, $a2);

        $linq = Linq::from($items)->selectMany(function($x)
        {
            return $x->value;
        });

        $this->assertTrue($linq instanceof Linq);

        $this->assertEquals(4, $linq->count());

        $array = $linq->toArray();
        $this->assertEquals("a", $array[0]);
        $this->assertEquals("b", $array[1]);
        $this->assertEquals("c", $array[2]);
        $this->assertEquals("d", $array[3]);

        // Try once again to see if rewinding the iterator works:
        $this->assertEquals(4, $linq->count());

        $array = $linq->toArray();
        $this->assertEquals("a", $array[0]);
        $this->assertEquals("b", $array[1]);
        $this->assertEquals("c", $array[2]);
        $this->assertEquals("d", $array[3]);
    }

    public function testSelectMany_EmptySequence_ReturnsEmptySequence()
    {
        $linq = Linq::from(array())->selectMany(function($x)
        {
            return $x->value;
        });

        $this->assertEquals(0, $linq->count());
        $array = $linq->toArray();
        $this->assertEquals(0, count($array));

        // Try once again to see if rewinding the iterator works:
        $this->assertEquals(0, $linq->count());
        $array = $linq->toArray();
        $this->assertEquals(0, count($array));
    }

    public function testSelectMany_DoesLazyEvaluation()
    {
        $a1 = new stdClass(); $a1->value = array("a", "b");
        $a2 = new stdClass(); $a2->value = array("c", "d");
        $items = array($a1, $a2);

        $eval = false;
        $flattened = Linq::from($items)->selectMany(function($x) use(&$eval)
        {
            $eval = true;
            return $x->value;
        });

        $this->assertFalse($eval, "SelectMany did execute before iterating!");
        $result = $flattened->toArray();
        $this->assertTrue($eval);
    }

    public function testConcat_ReturnsConcatenatedElements()
    {
        $first = array("a", "b");
        $second = array("c", "d");

        $all = Linq::from($first)->concat($second);
        $this->assertTrue($all instanceof Linq);

        $this->assertEquals(4, $all->count());

        $all = $all->toArray();
        $this->assertEquals("a", $all[0]);
        $this->assertEquals("b", $all[1]);
        $this->assertEquals("c", $all[2]);
        $this->assertEquals("d", $all[3]);
    }

    public function testConcat_ThrowsArgumentExceptionIfNoTraversableArgument()
    {
        $this->assertException(function() {
                $input = array();
                $linq = Linq::from($input);
                $linq->concat(null);
            },self::ExceptionName_InvalidArgument);

        $this->assertException(function() {
            $input = array();
            $second = new stdClass();
            Linq::from($input)->concat($second);
        },self::ExceptionName_InvalidArgument);
    }

    public function testLinqFrom_WorksWith_Arrays_Iterators_And_IteratorAggregates()
    {
        $linq = Linq::from(array(1, 2));
        $linq = Linq::from($linq);
        $linq = Linq::from($linq->getIterator());
    }

    public function testMethodsWithSequencesAsArguments_WorkWith_Arrays_Iterators_And_IteratorAggregates()
    {
        $first = Linq::from(array("a", "b"));
        $secondArray = array("c", "d");
        $secondLinq = Linq::from(array("c", "d"));
        $secondIterator = $secondLinq->getIterator();

        $res = $first->concat($secondLinq)->toArray();
        $res = $first->intersect($secondLinq)->toArray();
        $res = $first->except($secondLinq)->toArray();

        $res = $first->concat($secondArray)->toArray();
        $res = $first->intersect($secondArray)->toArray();
        $res = $first->except($secondArray)->toArray();

        $res = $first->concat($secondIterator)->toArray();
        $res = $first->intersect($secondIterator)->toArray();
        $res = $first->except($secondIterator)->toArray();
    }

    public function testIntersect_ReturnsIntersectedElements()
    {
        $first = array("a", "b", "c", "d");
        $second = array("b", "c");

        $linq = Linq::from($first)->intersect($second);
        $this->assertEquals(2, $linq->count());

        $array = $linq->toArray();
        $this->assertEquals("b", $array[0]);
        $this->assertEquals("c", $array[1]);
    }

    public function testIntersect_ThrowsArgumentExceptionIfSecondSequenceIsNotTraversable()
    {
        $this->assertException(function() {
            $input = array();
            $linq = Linq::from($input);
            $linq->intersect(null);
        },self::ExceptionName_InvalidArgument);

        $this->assertException(function() {
            $input = array();
            $linq = Linq::from($input);
            $linq->intersect("Not a sequence");
        },self::ExceptionName_InvalidArgument);
    }

    public function testIntersect_EmptySequence_ReturnsEmptySequence()
    {
        $first = array("a", "b", "c", "d");
        $second = array();

        $linq = Linq::from($first)->intersect($second);
        $this->assertEquals(0, $linq->count());
        $array = $linq->toArray();
        $this->assertEquals(0, count($array));
    }

    public function testExcept_ReturnsAllElementsExceptTheGivenOnes()
    {
        $first = array("a", "b", "c", "d");
        $second = array("b", "c");

        $linq = Linq::from($first)->except($second);
        $this->assertEquals(2, $linq->count());

        $array = $linq->toArray();
        $this->assertEquals("a", $array[0]);
        $this->assertEquals("d", $array[1]);
    }

    public function testExcept_ThrowsArgumentExceptionIfSecondSequenceIsNotTraversable()
    {
        $this->assertException(function() {
            $input = array();
            $linq = Linq::from($input);
            $linq->except(null);
        },self::ExceptionName_InvalidArgument);

        $this->assertException(function() {
            $input = array();
            $linq = Linq::from($input);
            $linq->except("Not a sequence");
        },self::ExceptionName_InvalidArgument);
    }

    public function testExcept_EmptySequence_ReturnsAllElementsFromFirst()
    {
        $first = array("a", "b", "c", "d");
        $second = array();

        $linq = Linq::from($first)->except($second);
        $this->assertEquals(4, $linq->count());

        $array = $linq->toArray();
        $this->assertEquals("a", $array[0]);
        $this->assertEquals("b", $array[1]);
        $this->assertEquals("c", $array[2]);
        $this->assertEquals("d", $array[3]);
    }

    public function testDistinct_ReturnsDistinctElements()
    {
        $items = array("a", "b", "a", "b");

        $distinct = Linq::from($items)->distinct();
        $this->assertTrue($distinct instanceof Linq);

        $this->assertEquals(2, $distinct->count());
        $distinct = $distinct->toArray();
        $this->assertEquals("a", $distinct[0]);
        $this->assertEquals("b", $distinct[1]);

        $a1 = new stdClass(); $a1->id = 1; $a1->value = "a";
        $a2 = new stdClass(); $a2->id = 2; $a2->value = "a";
        $b1 = new stdClass(); $b1->id = 3; $b1->value = "b";

        $items = array($a1, $a2, $b1);
        $distinct = Linq::from($items)->distinct(function($v) { return $v->value; });
        $this->assertEquals(2, $distinct->count());
    }

    public function testDistinct_DoesLazyEvaluation()
    {
        $eval = false;
        $a1 = new stdClass(); $a1->id = 1; $a1->value = "a";
        $a2 = new stdClass(); $a2->id = 2; $a2->value = "a";

        $items = array($a1, $a2);
        $distinct = Linq::from($items)->distinct(function($v) use(&$eval)
        {
            $eval = true;
            return $v->value;
        });

        $this->assertFalse($eval, "SelectMany did execute before iterating!");
        $distinct->toArray();
        $this->assertTrue($eval);
    }

    public function testMin_ReturnsMinValueFromNumerics()
    {
        $items = array(88, 77, 12, 112);
        $min = Linq::from($items)->min();
        $this->assertEquals(12, $min);

        $items = array(13);
        $min = Linq::from($items)->min();
        $this->assertEquals(13, $min);

        $items = array(0);
        $min = Linq::from($items)->min();
        $this->assertEquals(0, $min);

        $items = array(-12);
        $min = Linq::from($items)->min();
        $this->assertEquals(-12, $min);

        $items = array(-12, 0, 100, -33);
        $min = Linq::from($items)->min();
        $this->assertEquals(-33, $min);
    }

    public function testMin_ReturnsMinValueFromStrings()
    {
        $items = array("c", "a", "b", "d");
        $min = Linq::from($items)->min();
        $this->assertEquals("a", $min);

        $items = array("a");
        $min = Linq::from($items)->min();
        $this->assertEquals("a", $min);
    }

    public function testMin_ReturnsMinValueFromDateTimes()
    {
        $items = array(
            new DateTime("2015-01-01 10:00:00"),
            new DateTime("2015-02-01 10:00:00"),
            new DateTime("2015-01-01 09:00:00"),
        );
        $min = Linq::from($items)->min();
        $this->assertEquals($items[2], $min);
    }

    public function testMin_ThrowsExceptionIfSequenceIsEmpty()
    {
        $this->assertException(function()
        {
            $data = array();
            $min = Linq::from($data)->min();
        });
    }

    public function testMin_ThrowsExceptionIfSequenceContainsNoneNumericValuesOrStrings()
    {
        $this->assertException(function()
        {
            $data = array(null);
            $max = Linq::from($data)->min();
        }, self::ExceptionName_UnexpectedValue);

        $this->assertException(function()
        {
            $data = array(new stdClass());
            $min = Linq::from($data)->min();
        }, self::ExceptionName_UnexpectedValue);

        $this->assertException(function()
        {
            $data = array("string value", 1, new stdClass());
            $min = Linq::from($data)->min();
        }, self::ExceptionName_UnexpectedValue);

        $this->assertException(function()
        {
            $a = new stdClass(); $a->nonNumeric = new stdClass();
            $data = array($a);
            $min = Linq::from($data)->min(function($x)
            {
                return $x->nonNumeric;
            });
        }, self::ExceptionName_UnexpectedValue);
    }

    public function testMax_ReturnsMaxValueFromNumerics()
    {
        $items = array(88, 77, 12, 112);
        $max = Linq::from($items)->max();
        $this->assertEquals(112, $max);

        $items = array(13);
        $max = Linq::from($items)->max();
        $this->assertEquals(13, $max);

        $items = array(0);
        $max = Linq::from($items)->max();
        $this->assertEquals(0, $max);

        $items = array(-12);
        $max = Linq::from($items)->max();
        $this->assertEquals(-12, $max);

        $items = array(-12, 0, 100, -33);
        $max = Linq::from($items)->max();
        $this->assertEquals(100, $max);
    }

    public function testMax_ReturnsMaxValueFromDateTimes()
    {
        $items = array(
            new DateTime("2015-01-01 10:00:00"),
            new DateTime("2015-02-01 10:00:00"),
            new DateTime("2015-01-01 09:00:00"),
        );
        $max = Linq::from($items)->max();
        $this->assertEquals($items[1], $max);
    }

    public function testSum_ThrowsExceptionIfSequenceContainsNoneNumericValues()
    {
        $this->assertException(function()
        {
            $data = array(null);
            $max = Linq::from($data)->sum();
        }, self::ExceptionName_UnexpectedValue);

        $this->assertException(function()
        {
            $data = array(new stdClass());
            $min = Linq::from($data)->sum();
        }, self::ExceptionName_UnexpectedValue);

        $this->assertException(function()
        {
            $data = array("string value", 1, new stdClass());
            $min = Linq::from($data)->sum();
        }, self::ExceptionName_UnexpectedValue);

        $this->assertException(function()
        {
            $a = new stdClass(); $a->value = 100; $a->nonNumeric = "asdf";
            $b = new stdClass(); $b-> value = 133;  $a->nonNumeric = "asdf";

            $data = array($a, $b);
            $sum = Linq::from($data)->sum(function($x) {
                return $x->nonNumeric;
            });
        }, self::ExceptionName_UnexpectedValue);
    }

    public function testSum_GetSumOfValues()
    {
        $data = array();
        $sum = Linq::from($data)->sum();
        $this->assertEquals(0, $sum);

        $data = array(4, 9, 100.77);
        $sum = Linq::from($data)->sum();
        $this->assertEquals(113.77, $sum);

        $data = array(12, -12);
        $sum = Linq::from($data)->sum();
        $this->assertEquals(0, $sum);

        $data = array(12, -24);
        $sum = Linq::from($data)->sum();
        $this->assertEquals(-12, $sum);

        $a = new stdClass(); $a->value = 100;
        $b = new stdClass(); $b-> value = 133;

        $data = array($a, $b);
        $sum = Linq::from($data)->sum(function($x) {
            return $x->value;
        });

        $this->assertEquals(233, $sum);
    }

    public function testMax_ReturnsMaxValueFromStrings()
    {
        $items = array("c", "a", "b", "d");
        $max = Linq::from($items)->max();
        $this->assertEquals("d", $max);

        $items = array("a");
        $max = Linq::from($items)->max();
        $this->assertEquals("a", $max);
    }

    public function testMax_ThrowsExceptionIfSequenceIsEmpty()
    {
        $this->assertException(function()
        {
            $data = array();
            $max = Linq::from($data)->max();
        });
    }

    public function testMax_ThrowsExceptionIfSequenceContainsNoneNumericValuesOrStrings()
    {
        $this->assertException(function()
        {
            $data = array(new stdClass());
            $max = Linq::from($data)->max();
        }, self::ExceptionName_UnexpectedValue);

        $this->assertException(function()
        {
            $data = array(null);
            $max = Linq::from($data)->max();
        }, self::ExceptionName_UnexpectedValue);

        $this->assertException(function()
        {
            $data = array("string value", 1, new stdClass());
            $max = Linq::from($data)->max();
        }, self::ExceptionName_UnexpectedValue);

        $this->assertException(function()
        {
            $a = new stdClass(); $a->nonNumeric = new stdClass();
            $data = array($a);
            $min = Linq::from($data)->max(function($x)
            {
                return $x->nonNumeric;
            });
        }, self::ExceptionName_UnexpectedValue);
    }

    public function testEach_PerformsActionOnEachElement()
    {
        $items = array("a", "b", "c");
        $looped = array();
        Linq::from($items)
            ->each(function($x) use(&$looped)
            {
               $looped[] = $x;
            });

        $this->assertEquals(3, count($looped));
        $this->assertEquals("a", $looped[0]);
        $this->assertEquals("b", $looped[1]);
        $this->assertEquals("c", $looped[2]);
    }

    public function testEach_ReturnsOriginalLinqSequence()
    {
        $linq = Linq::from(array(1,2,3,4))
            ->skip(2)->take(1);

        $linqAfterEach = $linq->each(function($x) {});
        $this->assertSame($linq, $linqAfterEach);
    }

    public function testToArray_WithoutKeySelector_ReturnsIteratorValuesAsArray_UsesDefaultNumericArrayKeys()
    {
        $linq = Linq::from(array("a", "b", "c"))
            ->skip(1)->take(3);

        $array = $linq->toArray();
        $this->assertTrue(is_array($array));
        $this->assertEquals(2, count($array));

        $keys = array_keys($array);
        $this->assertEquals(0, $keys[0]);
        $this->assertEquals(1, $keys[1]);

        $this->assertEquals("b", $array[0]);
        $this->assertEquals("c", $array[1]);
    }

    public function testToArray_WithKeySelector_ReturnsIteratorValuesAsArray_UsesKeySelectorValueAsKey()
    {
        $linq = Linq::from(array("a", "b", "c"))
            ->skip(1)->take(3);

        $array = $linq->toArray(function($x) {
            return "keyprefix_" . $x;
        });

        $this->assertTrue(is_array($array));
        $this->assertEquals(2, count($array));

        $keys = array_keys($array);
        $this->assertEquals("keyprefix_b", $keys[0]);
        $this->assertEquals("keyprefix_c", $keys[1]);

        $this->assertEquals("b", $array["keyprefix_b"]);
        $this->assertEquals("c", $array["keyprefix_c"]);
    }

    public function testToArray_WithKeyAndValueSelector_ReturnsArrayWithKeyValueSetsFromClosures()
    {
        $source = array(
            array("catId" => 11, "name" => "Category11", "additionalcolumn" => "foo"),
            array("catId" => 12, "name" => "Category12", "additionalcolumn" => "bar"),
        );
        $linq = Linq::from($source);

        $array = $linq->toArray(function($x) {
            return $x["catId"];
        }, function($y) {
            return $y["name"];
        });

        $this->assertTrue(is_array($array));
        $this->assertEquals(2, count($array));

        $keys = array_keys($array);
        $this->assertEquals(11, $keys[0]);
        $this->assertEquals(12, $keys[1]);

        $this->assertEquals("Category11", $array[11]);
        $this->assertEquals("Category12", $array[12]);
    }

    public function testToArray_WithValueSelector_ReturnsArrayWithDefaultNumericKey_AndValueFromClosure()
    {
        $source = array(
            array("catId" => 11, "name" => "Category11", "additionalcolumn" => "foo"),
            array("catId" => 12, "name" => "Category12", "additionalcolumn" => "bar"),
        );

        $linq = Linq::from($source);

        $array = $linq->toArray(null, function($y) {
            return $y["additionalcolumn"];
        });

        $this->assertTrue(is_array($array));
        $this->assertEquals(2, count($array));

        $keys = array_keys($array);
        $this->assertEquals(0, $keys[0]);
        $this->assertEquals(1, $keys[1]);

        $this->assertEquals("foo", $array[0]);
        $this->assertEquals("bar", $array[1]);
    }

    public function testAggregate_novalues_throwsException()
    {
        $this->assertException(function() {

            Linq::from(array())->aggregate(function() {});
        }, self::ExceptionName_Runtime);


        $this->assertException(function() {

            Linq::from(array())->aggregate(function() {}, null);
        }, self::ExceptionName_Runtime);
    }

    public function testAggregate_returnsCorrectResult()
    {
        $this->assertEquals("value", Linq::from(array("value"))->aggregate(function($a, $b) { throw new Exception("Must not becalled"); }));
        $this->assertEquals(2, Linq::from(array(2))->aggregate(function($a, $b) { throw new Exception("Must not becalled"); }));
        $this->assertEquals(5, Linq::from(array(2, 3))->aggregate(function($a, $b) { return $a + $b; }));
        $this->assertEquals(17, Linq::from(array(2, 3, 3, 4, 5))->aggregate(function($a, $b) { return $a + $b; }));
        $this->assertEquals("abcde", Linq::from(array("a","b","c","d","e"))->aggregate(function($a, $b) { return $a . $b; }));
    }

    public function testAggregate_withSeedValue_returnsCorrectResult()
    {
        $this->assertEquals(9999, Linq::from(array())->aggregate(function() {}, 9999));
        $this->assertEquals(104, Linq::from(array(2))->aggregate(function($a, $b) { return $a + $b; }, 102));
        $this->assertEquals(137, Linq::from(array(2, 2, 20, 11))->aggregate(function($a, $b) { return $a + $b; }, 102));
        $this->assertEquals("begin_abcde", Linq::from(array("a","b","c","d","e"))->aggregate(function($a, $b) { return $a . $b; }, "begin_"));
    }

    public function testRange_throwsExceptionIfCountIsNegative()
    {
        $this->assertException(function() {

            Linq::range(0, -1);
        }, self::ExceptionName_OutOfRange);
    }

    public function testRange_returnsRangeOfIntegers()
    {
        $range = Linq::range(0, 3)->toArray();
        $this->assertEquals(3, count($range));
        $this->assertEquals(0, $range[0]);
        $this->assertEquals(1, $range[1]);
        $this->assertEquals(2, $range[2]);

        $range = Linq::range(6, 3)->toArray();
        $this->assertEquals(3, count($range));
        $this->assertEquals(6, $range[0]);
        $this->assertEquals(7, $range[1]);
        $this->assertEquals(8, $range[2]);

        $range = Linq::range(-3, 5)->toArray();
        $this->assertEquals(5, count($range));
        $this->assertEquals(-3, $range[0]);
        $this->assertEquals(-2, $range[1]);
        $this->assertEquals(-1, $range[2]);
        $this->assertEquals(0, $range[3]);
        $this->assertEquals(1, $range[4]);
    }

    public function testContains_defaultComparison()
    {
        $items = array("2", 2);
        $linq = Linq::from($items);
        $this->assertTrue($linq->contains(2));
        $this->assertTrue($linq->contains("2"));
        $this->assertFalse($linq->contains(true));

        $this->assertFalse($linq->contains(3));
        $this->assertFalse($linq->contains("3"));

        $this->assertFalse($linq->contains(3));
        $this->assertFalse($linq->contains(null));

        $a = new stdClass();
        $b = new stdClass();
        $c = new stdClass();
        $linq = Linq::from(array($a, $b));
        $this->assertTrue($linq->contains($a));
        $this->assertTrue($linq->contains($b));
        $this->assertFalse($linq->contains($c));
    }

    public function testChunk_throwsException_IfchunksizeIsInvalid()
    {
        $this->assertException(function() {
           Linq::from(array())->chunk(0);
        }, self::ExceptionName_InvalidArgument);

        $this->assertException(function() {
            Linq::from(array())->chunk(-1);
        }, self::ExceptionName_InvalidArgument);
    }

    public function testChunk_ReturnsChunkedElementsAccordingToChunksize()
    {
        $groups = Linq::from(array())->chunk(2);
        $this->assertEquals(0, $groups->count());

        $groups = Linq::from(array("a"))->chunk(2);
        $this->assertEquals(1, $groups->count());
        $this->assertEquals(1, $groups->ElementAt(0)->count());
        $this->assertEquals("a", $groups->ElementAt(0)->ElementAt(0));

        $groups = Linq::from(array("a","b","c","d","e"))->chunk(2);
        $this->assertEquals(3, $groups->count());
        $this->assertEquals(2, $groups->ElementAt(0)->count());
        $this->assertEquals("a", $groups->ElementAt(0)->ElementAt(0));
        $this->assertEquals("b", $groups->ElementAt(0)->ElementAt(1));

        $this->assertEquals(2, $groups->ElementAt(1)->count());
        $this->assertEquals("c", $groups->ElementAt(1)->ElementAt(0));
        $this->assertEquals("d", $groups->ElementAt(1)->ElementAt(1));

        $this->assertEquals(1, $groups->ElementAt(2)->count());
        $this->assertEquals("e", $groups->ElementAt(2)->ElementAt(0));

        $groups = Linq::from(array("a","b","c","d","e"))->chunk(3);
        $this->assertEquals(2, $groups->count());

        $groups = Linq::from(array("a","b","c","d","e"))->chunk(4);
        $this->assertEquals(2, $groups->count());

        $groups = Linq::from(array("a","b","c","d","e"))->chunk(5);
        $this->assertEquals(1, $groups->count());

        $groups = Linq::from(array("a","b","c","d","e"))->chunk(117);
        $this->assertEquals(1, $groups->count());
    }

    public function testIssue3_emtpyCollectionOrdering()
    {
        Linq::from(array())
            ->orderBy(function(array $x) { return $x["name"]; })
            ->toArray();
    }

        /**
         * @test
         */
        public function when_ofType_is_called_with_empty_array()
        {
                /** @var array $result */
                $result = Linq::from(array())
                              ->ofType('StubInterface')
                              ->toArray();

                $this->assertNotNull($result);
                $this->assertCount(0, $result);
        }

        /**
         * @test
         */
        public function when_ofType_is_called_with_array_containing_expected_interface()
        {
                /** @var Stub $expectedResult */
                $expectedResult = new Stub();

                /** @var array $result */
                $result = Linq::from(array($expectedResult,
                                           new StubWithoutInterface()))
                              ->ofType('StubInterface')
                              ->toArray();

                $this->assertNotNull($result);
                $this->assertCount(1, $result);
                $this->assertSame($expectedResult, $result[0]);
        }

        /**
         * @test
         */
        public function when_ofType_is_called_with_array_containing_expected_object_type()
        {
                /** @var StubWithoutInterface $expectedResult1 */
                $expectedResult1 = new StubWithoutInterface();

                /** @var array $result */
                $result = Linq::from(array(new Stub(),
                                           $expectedResult1))
                              ->ofType('StubWithoutInterface')
                              ->toArray();

                $this->assertNotNull($result);
                $this->assertCount(1, $result);
                $this->assertSame($expectedResult1, $result[0]);

                /** @var StubWithoutInterface $expectedResult2 */
                $expectedResult2 = new Stub();

                $result = Linq::from(array($expectedResult2,
                                           new StubWithoutInterface()))
                              ->ofType('Stub')
                              ->toArray();

                $this->assertNotNull($result);
                $this->assertCount(1, $result);
                $this->assertSame($expectedResult2, $result[0]);
        }

        /**
         * @test
         */
        public function when_ofType_is_called_with_array_not_containing_expected_interface()
        {
                /** @var array $result */
                $result = Linq::from(array(new StubWithoutInterface(),
                                           new StubWithoutInterface()))
                              ->ofType('StubInterface')
                              ->toArray();

                $this->assertNotNull($result);
                $this->assertCount(0, $result);
        }

        /**
         * @test
         */
        public function when_ofType_is_called_with_array_not_containing_expected_object_type()
        {
                /** @var array $result */
                $result = Linq::from(array(new Stub(),
                                           new Stub()))
                              ->ofType('StubWithoutInterface')
                              ->toArray();

                $this->assertNotNull($result);
                $this->assertCount(0, $result);
        }

        /**
         * @test
         */
        public function when_ofType_is_called_with_unknown_interface()
        {
                /** @var array $result */
                $result = Linq::from(array(new Stub(),
                                           new Stub()))
                              ->ofType('UnknownInterface')
                              ->toArray();

                $this->assertNotNull($result);
                $this->assertCount(0, $result);
        }

        /**
         * @test
         */
        public function when_ofType_is_called_with_unknown_object_type()
        {
                /** @var array $result */
                $result = Linq::from(array(new Stub(),
                                           new Stub()))
                              ->ofType('UnknownObject')
                              ->toArray();

                $this->assertNotNull($result);
                $this->assertCount(0, $result);
        }

        /**
         * @test
         */
        public function when_ofType_is_called_with_int_as_type()
        {
                /** @var int[] $expectedResult */
                $expectedResult = array(1,
                                        2,
                                        10,
                                        20);

                $result = Linq::from(array(1,
                                           2,
                                           new Stub(),
                                           10,
                                           NULL,
                                           20))
                              ->ofType('int')
                              ->toArray();

                $this->assertNotNull($result);
                $this->assertEquals($expectedResult, $result);
        }

        /**
         * @test
         */
        public function when_ofType_is_called_with_bool_as_type()
        {
                /** @var int[] $expectedResult */
                $expectedResult = array(TRUE,
                                        FALSE);

                $result = Linq::from(array(0,
                                           'string',
                                           'true',
                                           TRUE,
                                           'false',
                                           FALSE))
                              ->ofType('bool')
                              ->toArray();

                $this->assertNotNull($result);
                $this->assertEquals($expectedResult, $result);
        }

        /**
         * @test
         */
        public function when_ofType_is_called_with_string_as_type()
        {
                /** @var int[] $expectedResult */
                $expectedResult = array('string',
                                        'true',
                                        'false');

                $result = Linq::from(array(0,
                                           'string',
                                           'true',
                                           TRUE,
                                           'false',
                                           FALSE))
                              ->ofType('string')
                              ->toArray();

                $this->assertNotNull($result);
                $this->assertEquals($expectedResult, $result);
        }

        /**
         * @test
         */
        public function when_ofType_is_called_with_float_as_type()
        {
                /** @var int[] $expectedResult */
                $expectedResult = array(2.5,
                                        10.0,
                                        0.3);

                $result = Linq::from(array(0,
                                           'string',
                                           2.5,
                                           10.0,
                                           11,
                                           'false',
                                           0.3))
                              ->ofType('float')
                              ->toArray();

                $this->assertNotNull($result);
                $this->assertEquals($expectedResult, $result);
        }

        /**
         * @test
         */
        public function when_ofType_is_called_with_double_as_type()
        {
                /** @var int[] $expectedResult */
                $expectedResult = array(2.5,
                                        10.0,
                                        0.3);

                $result = Linq::from(array(0,
                                           'string',
                                           2.5,
                                           10.0,
                                           NULL,
                                           11,
                                           'false',
                                           0.3))
                              ->ofType('double')
                              ->toArray();

                $this->assertNotNull($result);
                $this->assertEquals($expectedResult, $result);
        }

        private function assertException($closure, $expected = self::ExceptionName_Runtime)
    {
        try
        {
            $closure();
        }
        catch(Exception $ex)
        {
            $exName = get_class($ex);

            if($exName != $expected)
            {
                $this->fail("Wrong exception raised. Expected: '" . $expected . "' Actual: '" . get_class($ex) . "'. Message: " . $ex->getMessage());
            }
            return;
        }

        $this->fail($expected .' has not been raised.');
    }
}

Generated by GNU Enscript 1.6.5.90.