From 33410eae1d1c76f16e5aad57dddc5553c82adf3a Mon Sep 17 00:00:00 2001
From: sgiehl <stefangiehl@gmail.com>
Date: Sat, 23 Jun 2012 21:18:56 +0000
Subject: [PATCH] refs #3227 more and more tests completely converted :)

git-svn-id: http://dev.piwik.org/svn/trunk@6495 59fd770c-687e-43c8-a1e3-f5a4ff64c105
---
 tests/PHPUnit/Core/IPTest.php                | 812 +++++++++++++++++++
 tests/PHPUnit/Core/ScheduledTaskTest.php     |  45 +
 tests/PHPUnit/Core/ScheduledTimeTest.php     | 560 +++++++++++++
 tests/PHPUnit/Core/SegmentExpressionTest.php | 128 +++
 tests/PHPUnit/Core/SegmentTest.php           | 465 +++++++++++
 5 files changed, 2010 insertions(+)
 create mode 100644 tests/PHPUnit/Core/IPTest.php
 create mode 100644 tests/PHPUnit/Core/ScheduledTaskTest.php
 create mode 100644 tests/PHPUnit/Core/ScheduledTimeTest.php
 create mode 100644 tests/PHPUnit/Core/SegmentExpressionTest.php
 create mode 100644 tests/PHPUnit/Core/SegmentTest.php

diff --git a/tests/PHPUnit/Core/IPTest.php b/tests/PHPUnit/Core/IPTest.php
new file mode 100644
index 0000000000..bd9321543c
--- /dev/null
+++ b/tests/PHPUnit/Core/IPTest.php
@@ -0,0 +1,812 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ * @version $Id$
+ */
+class IPTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Dataprovider for testSanitizeIp
+     */
+    public function getIPData() {
+        return array( // input, output
+            // single IPv4 address
+            array('127.0.0.1', '127.0.0.1'),
+
+            // single IPv6 address (ambiguous)
+            array('::1', '::1'),
+            array('::ffff:127.0.0.1', '::ffff:127.0.0.1'),
+            array('2001:5c0:1000:b::90f8', '2001:5c0:1000:b::90f8'),
+
+            // single IPv6 address
+            array('[::1]', '::1'),
+            array('[2001:5c0:1000:b::90f8]', '2001:5c0:1000:b::90f8'),
+            array('[::ffff:127.0.0.1]', '::ffff:127.0.0.1'),
+
+            // single IPv4 address (CIDR notation)
+            array('192.168.1.1/32', '192.168.1.1'),
+
+            // single IPv6 address (CIDR notation)
+            array('::1/128', '::1'),
+            array('::ffff:127.0.0.1/128', '::ffff:127.0.0.1'),
+            array('2001:5c0:1000:b::90f8/128', '2001:5c0:1000:b::90f8'),
+
+            // IPv4 address with port
+            array('192.168.1.2:80', '192.168.1.2'),
+
+            // IPv6 address with port
+            array('[::1]:80', '::1'),
+            array('[::ffff:127.0.0.1]:80', '::ffff:127.0.0.1'),
+            array('[2001:5c0:1000:b::90f8]:80', '2001:5c0:1000:b::90f8'),
+
+            // hostnames with port?
+            array('localhost', 'localhost'),
+            array('localhost:80', 'localhost'),
+            array('www.example.com', 'www.example.com'),
+            array('example.com:80', 'example.com'),
+        );
+    }
+    
+    /**
+     * @dataProvider getIPData
+     * @group Core
+     * @group IP
+     */
+    public function testSanitizeIp($ip, $expected)
+    {
+        $this->assertEquals($expected, Piwik_IP::sanitizeIp($ip));
+    }
+
+    /**
+     * Dataprovider for testSanitizeIpRange
+     */
+    public function getIPRangeData()
+    {
+        return array(
+            array('', false),
+            array(' 127.0.0.1 ', '127.0.0.1/32'),
+            array('192.168.1.0', '192.168.1.0/32'),
+            array('192.168.1.1/24', '192.168.1.1/24'),
+            array('192.168.1.2/16', '192.168.1.2/16'),
+            array('192.168.1.3/8', '192.168.1.3/8'),
+            array('192.168.2.*', '192.168.2.0/24'),
+            array('192.169.*.*', '192.169.0.0/16'),
+            array('193.*.*.*', '193.0.0.0/8'),
+            array('*.*.*.*', '0.0.0.0/0'),
+            array('*.*.*.1', false),
+            array('*.*.1.1', false),
+            array('*.1.1.1', false),
+            array('1.*.1.1', false),
+            array('1.1.*.1', false),
+            array('1.*.*.1', false),
+            array('::1', '::1/128'),
+            array('::ffff:127.0.0.1', '::ffff:127.0.0.1/128'),
+            array('2001:5c0:1000:b::90f8', '2001:5c0:1000:b::90f8/128'),
+            array('::1/64', '::1/64'),
+            array('::ffff:127.0.0.1/64', '::ffff:127.0.0.1/64'),
+            array('2001:5c0:1000:b::90f8/64', '2001:5c0:1000:b::90f8/64'),
+        );
+    }
+    
+    /**
+     * @dataProvider getIPRangeData
+     * @group Core
+     * @group IP
+     */
+    public function testSanitizeIpRange($ip, $expected)
+    {
+        $this->assertEquals($expected, Piwik_IP::sanitizeIpRange($ip));
+    }
+
+    /**
+     * Dataprovider for testP2N
+     */
+    public function getP2NTestData()
+    {
+        return array(
+            // IPv4
+            array('0.0.0.0', "\x00\x00\x00\x00"),
+            array('127.0.0.1', "\x7F\x00\x00\x01"),
+            array('192.168.1.12', "\xc0\xa8\x01\x0c"),
+            array('255.255.255.255', "\xff\xff\xff\xff"),
+
+            // IPv6
+            array('::', "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
+            array('::1', "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"),
+            array('::fffe:7f00:1', "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x7f\x00\x00\x01"),
+            array('::ffff:127.0.0.1', "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x7f\x00\x00\x01"),
+            array('2001:5c0:1000:b::90f8', "\x20\x01\x05\xc0\x10\x00\x00\x0b\x00\x00\x00\x00\x00\x00\x90\xf8"),
+        );
+    }
+
+    /**
+     * @dataProvider getP2NTestData
+     * @group Core
+     * @group IP
+     */
+    public function testP2N($P, $N)
+    {
+        $this->assertEquals($N, Piwik_IP::P2N($P));
+    }
+
+    /**
+     * Dataprovider for testP2NInvalidInput
+     */
+    public function getP2NInvalidInputData()
+    {
+        return array(
+            // not a series of dotted numbers
+            array(null),
+            array(''),
+            array('alpha'),
+            array('...'),
+
+            // missing an octet
+            array('.0.0.0'),
+            array('0..0.0'),
+            array('0.0..0'),
+            array('0.0.0.'),
+
+            // octets must be 0-255
+            array('-1.0.0.0'),
+            array('1.1.1.256'),
+
+            // leading zeros not supported (i.e., can be ambiguous, e.g., octal)
+            array('07.07.07.07'),
+        );
+    }
+
+    /**
+     * @group Core
+     * @group IP
+     * @dataProvider getP2NInvalidInputData
+     */
+    public function testP2NInvalidInput($P)
+    {
+        $this->assertEquals( "\x00\x00\x00\x00", Piwik_IP::P2N($P) );
+    }
+
+    /**
+     * @group Core
+     * @group IP
+     */
+    public function getN2PTestData()
+    {
+        // a valid network address is either 4 or 16 bytes; those lines are intentionally left blank ;)
+        return array(
+            array(null),
+            array(''),
+            array("\x01"),
+            array("\x01\x00"),
+            array("\x01\x00\x00"),
+
+            array("\x01\x00\x00\x00\x00"),
+            array("\x01\x00\x00\x00\x00\x00"),
+            array("\x01\x00\x00\x00\x00\x00\x00"),
+            array("\x01\x00\x00\x00\x00\x00\x00\x00"),
+            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00"),
+            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
+            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
+            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
+            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
+            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
+            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
+
+            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"),
+        );
+    }
+
+    /**
+     * @dataProvider getP2NTestData
+     * @group Core
+     * @group IP
+     */
+    public function testN2P($P, $N)
+    {
+        $this->assertEquals($P, Piwik_IP::N2P($N), "$P vs" . Piwik_IP::N2P($N));
+    }
+
+    /**
+     * @dataProvider getN2PTestData
+     * @group Core
+     * @group IP
+     */
+    public function testN2PinvalidInput($N)
+    {
+        $this->assertEquals("0.0.0.0", Piwik_IP::N2P($N), bin2hex($N));
+    }
+
+    /**
+     * @dataProvider getP2NTestData
+     * @group Core
+     * @group IP
+     */
+    public function testPrettyPrint($P, $N)
+    {
+        $this->assertEquals($P, Piwik_IP::prettyPrint($N), "$P vs" . Piwik_IP::N2P($N));
+    }
+
+    /**
+     * @dataProvider getN2PTestData
+     * @group Core
+     * @group IP
+     */
+    public function testPrettyPrintInvalidInput($N)
+    {
+        $this->assertEquals("0.0.0.0", Piwik_IP::prettyPrint($N), bin2hex($N));
+    }
+
+    /**
+     * Dataprovider for IP4 test data
+     */
+    public function getIPv4Data()
+    {
+        // a valid network address is either 4 or 16 bytes; those lines are intentionally left blank ;)
+        return array(
+            // invalid
+            array(null, false),
+            array("", false),
+
+            // IPv4
+            array("\x00\x00\x00\x00", true),
+            array("\x7f\x00\x00\x01", true),
+
+            // IPv4-compatible (this transitional format is deprecated in RFC 4291, section 2.5.5.1)
+            array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8\x01\x01", true),
+
+            // IPv4-mapped (RFC 4291, 2.5.5.2)
+            array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc0\xa8\x01\x02", true),
+
+            // other IPv6 address
+            array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\xc0\xa8\x01\x03", false),
+            array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\xa8\x01\x04", false),
+            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8\x01\x05", false),
+
+            /*
+             * We assume all stored IP addresses (pre-Piwik 1.4) were converted from UNSIGNED INT to VARBINARY.
+             * The following is just for informational purposes.
+             */
+
+            // 192.168.1.0
+            array('-1062731520', false),
+            array('3232235776', false),
+
+            // 10.10.10.10
+            array('168430090', false),
+
+            // 0.0.39.15 - this is the ambiguous case (i.e., 4 char string)
+            array('9999', true),
+            array("\x39\x39\x39\x39", true),
+
+            // 0.0.3.231
+            array('999', false),
+            array("\x39\x39\x39", false),
+        );
+
+    }
+
+    /**
+     * @dataProvider getIPv4Data
+     * @group Core
+     * @group IP
+     */
+    public function testIsIPv4($ip, $bool)
+    {
+        $this->assertEquals($bool, Piwik_IP::isIPv4($ip), bin2hex($ip));
+    }
+
+    /**
+     * Dataprovider for long2ip test
+     */
+    public function getLong2IPTestData()
+    {
+        // a valid network address is either 4 or 16 bytes; those lines are intentionally left blank ;)
+        return array(
+            // invalid
+            array(null, '0.0.0.0'),
+            array("", '0.0.0.0'),
+
+            // IPv4
+            array("\x7f\x00\x00\x01", '127.0.0.1'),
+
+            // IPv4-compatible (this transitional format is deprecated in RFC 4291, section 2.5.5.1)
+            array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8\x01\x01", '192.168.1.1'),
+
+            // IPv4-mapped (RFC 4291, 2.5.5.2)
+            array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xc0\xa8\x01\x02", '192.168.1.2'),
+
+            // other IPv6 address
+            array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\xc0\xa8\x01\x03", '0.0.0.0'),
+            array("\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\xc0\xa8\x01\x04", '0.0.0.0'),
+            array("\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc0\xa8\x01\x05", '0.0.0.0'),
+
+            /*
+             * We assume all stored IP addresses (pre-Piwik 1.4) were converted from UNSIGNED INT to VARBINARY.
+             * The following is just for informational purposes.
+             */
+
+            // 192.168.1.0
+            array('-1062731520', '0.0.0.0'),
+            array('3232235776', '0.0.0.0'),
+
+            // 10.10.10.10
+            array('168430090', '0.0.0.0'),
+
+            // 0.0.39.15 - this is the ambiguous case (i.e., 4 char string)
+            array('9999', '57.57.57.57'),
+            array("\x39\x39\x39\x39", '57.57.57.57'),
+
+            // 0.0.3.231
+            array('999', '0.0.0.0'),
+            array("\x39\x39\x39", '0.0.0.0'),
+        );
+    }
+
+    /**
+     * @dataProvider getLong2IPTestData
+     * @group Core
+     * @group IP
+     */
+    public function testLong2ip($N, $P)
+    {
+        $this->assertEquals($P, Piwik_IP::long2ip($N), bin2hex($N));
+        // this is our compatibility function
+        $this->assertEquals($P, Piwik_Common::long2ip($N), bin2hex($N));
+    }
+
+    /**
+     * Dataprovider for ip range test
+     */
+    public function getIPsForRangeTest()
+    {
+        return array(
+
+            // invalid ranges
+            array(null, false),
+            array('', false),
+            array('0', false),
+
+            // single IPv4
+            array('127.0.0.1', array( "\x7f\x00\x00\x01", "\x7f\x00\x00\x01" )),
+
+            // IPv4 with wildcards
+            array('192.168.1.*', array( "\xc0\xa8\x01\x00", "\xc0\xa8\x01\xff" )),
+            array('192.168.*.*', array( "\xc0\xa8\x00\x00", "\xc0\xa8\xff\xff" )),
+            array('192.*.*.*', array( "\xc0\x00\x00\x00", "\xc0\xff\xff\xff" )),
+            array('*.*.*.*', array( "\x00\x00\x00\x00", "\xff\xff\xff\xff" )),
+
+            // single IPv4 in expected CIDR notation
+            array('192.168.1.1/24', array( "\xc0\xa8\x01\x00", "\xc0\xa8\x01\xff" )),
+
+            array('192.168.1.127/32', array( "\xc0\xa8\x01\x7f", "\xc0\xa8\x01\x7f" )),
+            array('192.168.1.127/31', array( "\xc0\xa8\x01\x7e", "\xc0\xa8\x01\x7f" )),
+            array('192.168.1.127/30', array( "\xc0\xa8\x01\x7c", "\xc0\xa8\x01\x7f" )),
+            array('192.168.1.127/29', array( "\xc0\xa8\x01\x78", "\xc0\xa8\x01\x7f" )),
+            array('192.168.1.127/28', array( "\xc0\xa8\x01\x70", "\xc0\xa8\x01\x7f" )),
+            array('192.168.1.127/27', array( "\xc0\xa8\x01\x60", "\xc0\xa8\x01\x7f" )),
+            array('192.168.1.127/26', array( "\xc0\xa8\x01\x40", "\xc0\xa8\x01\x7f" )),
+            array('192.168.1.127/25', array( "\xc0\xa8\x01\x00", "\xc0\xa8\x01\x7f" )),
+
+            array('192.168.1.255/32', array( "\xc0\xa8\x01\xff", "\xc0\xa8\x01\xff" )),
+            array('192.168.1.255/31', array( "\xc0\xa8\x01\xfe", "\xc0\xa8\x01\xff" )),
+            array('192.168.1.255/30', array( "\xc0\xa8\x01\xfc", "\xc0\xa8\x01\xff" )),
+            array('192.168.1.255/29', array( "\xc0\xa8\x01\xf8", "\xc0\xa8\x01\xff" )),
+            array('192.168.1.255/28', array( "\xc0\xa8\x01\xf0", "\xc0\xa8\x01\xff" )),
+            array('192.168.1.255/27', array( "\xc0\xa8\x01\xe0", "\xc0\xa8\x01\xff" )),
+            array('192.168.1.255/26', array( "\xc0\xa8\x01\xc0", "\xc0\xa8\x01\xff" )),
+            array('192.168.1.255/25', array( "\xc0\xa8\x01\x80", "\xc0\xa8\x01\xff" )),
+
+            array('192.168.255.255/24', array( "\xc0\xa8\xff\x00", "\xc0\xa8\xff\xff" )),
+            array('192.168.255.255/23', array( "\xc0\xa8\xfe\x00", "\xc0\xa8\xff\xff" )),
+            array('192.168.255.255/22', array( "\xc0\xa8\xfc\x00", "\xc0\xa8\xff\xff" )),
+            array('192.168.255.255/21', array( "\xc0\xa8\xf8\x00", "\xc0\xa8\xff\xff" )),
+            array('192.168.255.255/20', array( "\xc0\xa8\xf0\x00", "\xc0\xa8\xff\xff" )),
+            array('192.168.255.255/19', array( "\xc0\xa8\xe0\x00", "\xc0\xa8\xff\xff" )),
+            array('192.168.255.255/18', array( "\xc0\xa8\xc0\x00", "\xc0\xa8\xff\xff" )),
+            array('192.168.255.255/17', array( "\xc0\xa8\x80\x00", "\xc0\xa8\xff\xff" )),
+
+            // single IPv6
+            array('::1', array( "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" )),
+
+            // single IPv6 in expected CIDR notation
+            array('::1/128', array( "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" )),
+            array('::1/127', array( "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01" )),
+            array('::fffe:7f00:1/120', array( "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x7f\x00\x00\x00", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xfe\x7f\x00\x00\xff" )),
+            array('::ffff:127.0.0.1/120', array( "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x7f\x00\x00\x00", "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x7f\x00\x00\xff" )),
+
+            array('2001:ca11:911::b0b:15:dead/128', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xad", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xad" )),
+            array('2001:ca11:911::b0b:15:dead/127', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xac", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xad" )),
+            array('2001:ca11:911::b0b:15:dead/126', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xac", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xaf" )),
+            array('2001:ca11:911::b0b:15:dead/125', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xa8", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xaf" )),
+            array('2001:ca11:911::b0b:15:dead/124', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xa0", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xaf" )),
+            array('2001:ca11:911::b0b:15:dead/123', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xa0", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xbf" )),
+            array('2001:ca11:911::b0b:15:dead/122', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\x80", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xbf" )),
+            array('2001:ca11:911::b0b:15:dead/121', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\x80", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xff" )),
+            array('2001:ca11:911::b0b:15:dead/120', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\xff" )),
+            array('2001:ca11:911::b0b:15:dead/119', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xde\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xdf\xff" )),
+            array('2001:ca11:911::b0b:15:dead/118', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xdc\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xdf\xff" )),
+            array('2001:ca11:911::b0b:15:dead/117', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xd8\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xdf\xff" )),
+            array('2001:ca11:911::b0b:15:dead/116', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xd0\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xdf\xff" )),
+            array('2001:ca11:911::b0b:15:dead/115', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xc0\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xdf\xff" )),
+            array('2001:ca11:911::b0b:15:dead/114', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xc0\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xff\xff" )),
+            array('2001:ca11:911::b0b:15:dead/113', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\x80\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xff\xff" )),
+            array('2001:ca11:911::b0b:15:dead/112', array( "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\x00\x00", "\x20\x01\xca\x11\x09\x11\x00\x00\x00\x00\x0b\x0b\x00\x15\xff\xff" )),
+        );
+    }
+
+    /**
+     * @dataProvider getIPsForRangeTest
+     * @group Core
+     * @group IP
+     */
+    public function testGetIpsForRange($range, $expected)
+    {
+        $this->assertEquals($expected, Piwik_IP::getIpsForRange($range));
+    }
+
+    /**
+     * Dataprovider for testIsIpInRange
+     */
+    public function getIpsInRangeData()
+    {
+        return array(
+            array('192.168.1.10', array(
+                '192.168.1.9' => false,
+                '192.168.1.10' => true,
+                '192.168.1.11' => false,
+
+                // IPv6 addresses (including IPv4 mapped) have to be compared against IPv6 address ranges
+                '::ffff:192.168.1.10' => false,
+            )),
+
+            array('::ffff:192.168.1.10', array(
+                '::ffff:192.168.1.9' => false,
+                '::ffff:192.168.1.10' => true,
+                '::ffff:c0a8:010a' => true,
+                '0000:0000:0000:0000:0000:ffff:c0a8:010a' => true,
+                '::ffff:192.168.1.11' => false,
+
+                // conversely, IPv4 addresses have to be compared against IPv4 address ranges
+                '192.168.1.10' => false,
+            )),
+
+            array('192.168.1.10/32', array(
+                '192.168.1.9' => false,
+                '192.168.1.10' => true,
+                '192.168.1.11' => false,
+            )),
+
+            array('192.168.1.10/31', array(
+                '192.168.1.9' => false,
+                '192.168.1.10' => true,
+                '192.168.1.11' => true,
+                '192.168.1.12' => false,
+            )),
+
+            array('192.168.1.128/25', array(
+                '192.168.1.127' => false,
+                '192.168.1.128' => true,
+                '192.168.1.255' => true,
+                '192.168.2.0' => false,
+            )),
+
+            array('192.168.1.10/24', array(
+                '192.168.0.255' => false,
+                '192.168.1.0' => true,
+                '192.168.1.1' => true,
+                '192.168.1.2' => true,
+                '192.168.1.3' => true,
+                '192.168.1.4' => true,
+                '192.168.1.7' => true,
+                '192.168.1.8' => true,
+                '192.168.1.15' => true,
+                '192.168.1.16' => true,
+                '192.168.1.31' => true,
+                '192.168.1.32' => true,
+                '192.168.1.63' => true,
+                '192.168.1.64' => true,
+                '192.168.1.127' => true,
+                '192.168.1.128' => true,
+                '192.168.1.255' => true,
+                '192.168.2.0' => false,
+            )),
+
+            array('192.168.1.*', array(
+                '192.168.0.255' => false,
+                '192.168.1.0' => true,
+                '192.168.1.1' => true,
+                '192.168.1.2' => true,
+                '192.168.1.3' => true,
+                '192.168.1.4' => true,
+                '192.168.1.7' => true,
+                '192.168.1.8' => true,
+                '192.168.1.15' => true,
+                '192.168.1.16' => true,
+                '192.168.1.31' => true,
+                '192.168.1.32' => true,
+                '192.168.1.63' => true,
+                '192.168.1.64' => true,
+                '192.168.1.127' => true,
+                '192.168.1.128' => true,
+                '192.168.1.255' => true,
+                '192.168.2.0' => false,
+            )),
+        );
+    }
+
+    /**
+     * @group Core
+     * @group IP
+     * @dataProvider getIpsInRangeData
+     */
+    public function testIsIpInRange($range, $test)
+    {
+        foreach($test as $ip => $expected)
+        {
+            // range as a string
+            $this->assertEquals( $expected, Piwik_IP::isIpInRange(Piwik_IP::P2N($ip), array($range)), "$ip in $range" );
+
+            // range as an array(low, high)
+            $aRange = Piwik_IP::getIpsForRange($range);
+            $aRange[0] = Piwik_IP::N2P($aRange[0]);
+            $aRange[1] = Piwik_IP::N2P($aRange[1]);
+            $this->assertEquals( $expected, Piwik_IP::isIpInRange(Piwik_IP::P2N($ip), array($aRange)), "$ip in $range" );
+        }
+    }
+
+    /**
+     * Dataprovider for ip from header tests
+     */
+    public function getIpFromHeaderTestData()
+    {
+        return array(
+            array('localhost inside LAN', array('127.0.0.1', '', null, null, '127.0.0.1')),
+            array('outside LAN, no proxy', array('128.252.135.4', '', null, null, '128.252.135.4')),
+            array('outside LAN, no (trusted) proxy', array('128.252.135.4', '137.18.2.13, 128.252.135.4', '', null, '128.252.135.4')),
+            array('outside LAN, one trusted proxy', array('192.168.1.10', '137.18.2.13, 128.252.135.4, 192.168.1.10', 'HTTP_X_FORWARDED_FOR', null, '128.252.135.4')),
+            array('outside LAN, proxy', array('192.168.1.10', '128.252.135.4, 192.168.1.10', 'HTTP_X_FORWARDED_FOR', null, '128.252.135.4')),
+            array('outside LAN, misconfigured proxy', array('192.168.1.10', '128.252.135.4, 192.168.1.10, 192.168.1.10', 'HTTP_X_FORWARDED_FOR', null, '128.252.135.4')),
+            array('outside LAN, multiple proxies', array('192.168.1.10', '128.252.135.4, 192.168.1.20, 192.168.1.10', 'HTTP_X_FORWARDED_FOR', '192.168.1.*', '128.252.135.4')),
+            array('outside LAN, multiple proxies', array('[::ffff:7f00:10]', '128.252.135.4, [::ffff:7f00:20], [::ffff:7f00:10]', 'HTTP_X_FORWARDED_FOR', '::ffff:7f00:0/120', '128.252.135.4')),
+        );
+    }
+
+    /**
+     * @dataProvider getIpFromHeaderTestData
+     * @group Core
+     * @group IP
+     */
+    public function testGetIpFromHeader($description, $test)
+    {
+        Piwik::createConfigObject();
+        Piwik_Config::getInstance()->setTestEnvironment();
+
+        $_SERVER['REMOTE_ADDR'] = $test[0];
+        $_SERVER['HTTP_X_FORWARDED_FOR'] = $test[1];
+        Piwik_Config::getInstance()->General['proxy_client_headers'] = array($test[2]);
+        Piwik_Config::getInstance()->General['proxy_ips'] = array($test[3]);
+        $this->assertEquals($test[4], Piwik_IP::getIpFromHeader(), $description);
+    }
+
+    /**
+     * Dataprovider
+     * @return array
+     */
+    public function getIpTestData()
+    {
+        return array(
+            array('0.0.0.0'),
+            array('72.14.204.99'),
+            array('127.0.0.1'),
+            array('169.254.0.1'),
+            array('208.80.152.2'),
+            array('224.0.0.1'),
+        );
+    }
+
+
+    /**
+     * @group Core
+     * @group IP
+     * @dataProvider getIpTestData
+     */
+    public function testGetNonProxyIpFromHeader($ip)
+    {
+        $this->assertEquals($ip, Piwik_IP::getNonProxyIpFromHeader($ip, array()));
+    }
+
+    /**
+     * @group Core
+     * @group IP
+     * @dataProvider getIpTestData
+     */
+    public function testGetNonProxyIpFromHeader2($ip)
+    {
+        // 1.1.1.1 is not a trusted proxy
+        $_SERVER['REMOTE_ADDR'] = '1.1.1.1';
+        $_SERVER['HTTP_X_FORWARDED_FOR'] = '';
+        $this->assertEquals('1.1.1.1', Piwik_IP::getNonProxyIpFromHeader('1.1.1.1', array('HTTP_X_FORWARDED_FOR')));
+    }
+
+    /**
+     * @group Core
+     * @group IP
+     * @dataProvider getIpTestData
+     */
+    public function testGetNonProxyIpFromHeader3($ip)
+    {
+        // 1.1.1.1 is a trusted proxy
+        $_SERVER['REMOTE_ADDR'] = '1.1.1.1';
+
+        $_SERVER['HTTP_X_FORWARDED_FOR'] = $ip;
+        $this->assertEquals($ip, Piwik_IP::getNonProxyIpFromHeader('1.1.1.1', array('HTTP_X_FORWARDED_FOR')));
+
+        $_SERVER['HTTP_X_FORWARDED_FOR'] = '1.2.3.4, ' . $ip;
+        $this->assertEquals($ip, Piwik_IP::getNonProxyIpFromHeader('1.1.1.1', array('HTTP_X_FORWARDED_FOR')));
+
+        // misconfiguration
+        $_SERVER['HTTP_X_FORWARDED_FOR'] = $ip . ', 1.1.1.1';
+        $this->assertEquals($ip, Piwik_IP::getNonProxyIpFromHeader('1.1.1.1', array('HTTP_X_FORWARDED_FOR')));
+    }
+
+    /**
+     * Dataprovider for testGetLastIpFromList
+     */
+    public function getLastIpFromListTestData()
+    {
+        return array(
+            array('', ''),
+            array('127.0.0.1', '127.0.0.1'),
+            array(' 127.0.0.1 ', '127.0.0.1'),
+            array(' 192.168.1.1, 127.0.0.1', '127.0.0.1'),
+            array('192.168.1.1 ,127.0.0.1 ', '127.0.0.1'),
+            array('192.168.1.1,', ''),
+        );
+    }
+
+    /**
+     * @group Core
+     * @group IP
+     * @dataProvider getLastIpFromListTestData
+     */
+    public function testGetLastIpFromList($csv, $expected)
+    {
+        // without excluded IPs
+        $this->assertEquals($expected, Piwik_IP::getLastIpFromList($csv));
+
+        // with excluded Ips
+        $this->assertEquals($expected, Piwik_IP::getLastIpFromList($csv . ', 10.10.10.10', array('10.10.10.10')));
+    }
+
+    /**
+     * @group Core
+     * @group IP
+     */
+    public function testGetHostByAddr()
+    {
+        $hosts = array( 'localhost', strtolower(@php_uname('n')), '127.0.0.1' );
+        $this->assertTrue( in_array(strtolower(Piwik_IP::getHostByAddr('127.0.0.1')), $hosts), '127.0.0.1 -> localhost' );
+
+        if (!Piwik_Common::isWindows() || PHP_VERSION >= '5.3')
+        {
+            $hosts = array( 'ip6-localhost', strtolower(@php_uname('n')), '::1' );
+            $this->assertTrue( in_array(strtolower(Piwik_IP::getHostByAddr('::1')), $hosts), '::1 -> ip6-localhost' );
+        }
+    }
+
+    /**
+     * Dataprovider for testPhpCompatInetNtop
+     */
+    public function getInetNtopData()
+    {
+        return array(
+            array('127.0.0.1',                 '7f000001'),
+            array('192.232.131.222',           'c0e883de'),
+            array('255.0.0.0',                 'ff000000'),
+            array('255.255.255.255',           'ffffffff'),
+            array('::1',                       '00000000000000000000000000000001'),
+            array('::101',                     '00000000000000000000000000000101'),
+            array('::0.1.1.1',                 '00000000000000000000000000010101'),
+            array('2001:260:0:10::1',          '20010260000000100000000000000001'),
+            array('2001:0:0:260::1',           '20010000000002600000000000000001'),
+            array('2001::260:0:0:10:1',        '20010000000002600000000000100001'),
+            array('2001:5c0:1000:b::90f8',     '200105c01000000b00000000000090f8'),
+            array('fe80::200:4cff:fe43:172f',  'fe8000000000000002004cfffe43172f'),
+            array('::ffff:127.0.0.1',          '00000000000000000000ffff7f000001'),
+            array('::127.0.0.1',               '0000000000000000000000007f000001'),
+            array('::fff0:7f00:1',             '00000000000000000000fff07f000001'),
+        );
+    }
+
+    /**
+     * @group Core
+     * @group IP
+     * @dataProvider getInetNtopData
+     */
+    public function testPhpCompatInetNtop($k, $v)
+    {
+        $this->assertEquals($k, php_compat_inet_ntop(pack('H*', $v)));
+        if(!Piwik_Common::isWindows()) {
+            $this->assertEquals($k, @inet_ntop(pack('H*', $v)));
+        }
+    }
+
+    /**
+     * Dataprovider for testPhpCompatInetPton
+     * @return array
+     */
+    public function getInetPtonTestData()
+    {
+        return array(
+            array('127.0.0.1',                  '7f000001'),
+            array('192.232.131.222',            'c0e883de'),
+            array('255.0.0.0',                  'ff000000'),
+            array('255.255.255.255',            'ffffffff'),
+            array('::',                         '00000000000000000000000000000000'),
+            array('::0',                        '00000000000000000000000000000000'),
+            array('0::',                        '00000000000000000000000000000000'),
+            array('0::0',                       '00000000000000000000000000000000'),
+            array('::1',                        '00000000000000000000000000000001'),
+            array('2001:260:0:10::1',           '20010260000000100000000000000001'),
+            array('2001:5c0:1000:b::90f8',      '200105c01000000b00000000000090f8'),
+            array('fe80::200:4cff:fe43:172f',   'fe8000000000000002004cfffe43172f'),
+            array('::ffff:127.0.0.1',           '00000000000000000000ffff7f000001'),
+            array('::127.0.0.1',                '0000000000000000000000007f000001'),
+
+            // relaxed rules
+            array('00000::',                    '00000000000000000000000000000000'),
+            array('1:2:3:4:5:ffff:127.0.0.1',   '00010002000300040005ffff7f000001'),
+
+            // invalid input
+            array(null,                         false),
+            array(false,                        false),
+            array(true,                         false),
+            array('',                           false),
+            array('0',                          false),
+            array('07.07.07.07',                false),
+            array('1.',                         false),
+            array('.1',                         false),
+            array('1.1',                        false),
+            array('.1.1.',                      false),
+            array('1.1.1.',                     false),
+            array('.1.1.1',                     false),
+            array('1.2.3.4.',                   false),
+            array('.1.2.3.4',                   false),
+            array('1.2.3.256',                  false),
+            array('a.b.c.d',                    false),
+            array('::1::',                      false),
+            array('1:2:3:4:::5:6',              false),
+            array('1:2:3:4:5:6:',               false),
+            array(':1:2:3:4:5:6',               false),
+            array('1:2:3:4:5:6:7:',             false),
+            array(':1:2:3:4:5:6:7',             false),
+            array('::11111:0',                  false),
+            array('::g',                        false),
+            array('::ffff:127.00.0.1',          false),
+            array('::ffff:127.0.0.01',          false),
+            array('::ffff:256.0.0.1',           false),
+            array('::ffff:1.256.0.1',           false),
+            array('::ffff:65536.0.0.1',         false),
+            array('::ffff:256.65536.0.1',       false),
+            array('::ffff:65536.65536.0.1',     false),
+            array('::ffff:7f01:0.1',            false),
+            array('ffff:127.0.0.1:ffff::',      false),
+        );
+    }
+
+    /**
+     * @group Core
+     * @group IP
+     * @dataProvider getInetPtonTestData
+     */
+    public function testPhpCompatInetPton($k, $v)
+    {
+        $this->assertEquals($v, bin2hex(php_compat_inet_pton($k)));
+        if(!Piwik_Common::isWindows()) {
+            $this->assertEquals($v, bin2hex(@inet_pton($k)));
+        }
+    }
+}
diff --git a/tests/PHPUnit/Core/ScheduledTaskTest.php b/tests/PHPUnit/Core/ScheduledTaskTest.php
new file mode 100644
index 0000000000..2eba437c66
--- /dev/null
+++ b/tests/PHPUnit/Core/ScheduledTaskTest.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ * @version $Id$
+ */
+class ScheduledTaskTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @group Core
+     * @group ScheduledTask
+     */
+    public function testGetClassName()
+    {
+        $scheduledTask = new Piwik_ScheduledTask ( "className", null, null );
+        $className = $scheduledTask->getClassName();
+        $this->assertInternalType('string', $className);
+        $this->assertNotEmpty($className);
+    }
+    
+    /**
+     * @group Core
+     * @group ScheduledTask
+     */
+    public function testGetMethodName()
+    {
+        $scheduledTask = new Piwik_ScheduledTask ( null, "methodname", null );
+        $methodName = $scheduledTask->getMethodName();
+        $this->assertInternalType('string', $methodName);
+        $this->assertNotEmpty($methodName);
+    }
+    
+    /**
+     * @group Core
+     * @group ScheduledTask
+     */
+    public function testGetScheduledTime()
+    {
+        $scheduledTask = new Piwik_ScheduledTask ( null, null, new Piwik_ScheduledTime_Hourly() );
+        $scheduledTime = $scheduledTask->getScheduledTime();
+        $this->assertInstanceOf("Piwik_ScheduledTime_Hourly", $scheduledTime);
+    }
+}
diff --git a/tests/PHPUnit/Core/ScheduledTimeTest.php b/tests/PHPUnit/Core/ScheduledTimeTest.php
new file mode 100644
index 0000000000..d7ecca515a
--- /dev/null
+++ b/tests/PHPUnit/Core/ScheduledTimeTest.php
@@ -0,0 +1,560 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ * @version $Id$
+ */
+class ScheduledTimeTest extends PHPUnit_Framework_TestCase
+{
+    private $_JANUARY_01_1971_09_00_00;
+    private $_JANUARY_01_1971_09_10_00;
+    private $_JANUARY_01_1971_10_00_00;
+    private $_JANUARY_01_1971_12_10_00;
+    private $_JANUARY_02_1971_00_00_00;
+    private $_JANUARY_02_1971_09_00_00;
+    private $_JANUARY_04_1971_00_00_00;
+    private $_JANUARY_04_1971_09_00_00;
+    private $_JANUARY_05_1971_09_00_00;
+    private $_JANUARY_11_1971_00_00_00;
+    private $_JANUARY_15_1971_00_00_00;
+    private $_JANUARY_15_1971_09_00_00;
+    private $_FEBRUARY_01_1971_00_00_00;
+    private $_FEBRUARY_02_1971_00_00_00;
+    private $_FEBRUARY_28_1971_00_00_00;
+
+    public function setUp()
+    {
+        parent::setUp();
+        $this->_JANUARY_01_1971_09_00_00 = mktime(9,00,00,1,1,1971);
+        $this->_JANUARY_01_1971_09_10_00 = mktime(9,10,00,1,1,1971);
+        $this->_JANUARY_01_1971_10_00_00 = mktime(10,00,00,1,1,1971);
+        $this->_JANUARY_01_1971_12_10_00 = mktime(12,10,00,1,1,1971);
+        $this->_JANUARY_02_1971_00_00_00 = mktime(0,00,00,1,2,1971);
+        $this->_JANUARY_02_1971_09_00_00 = mktime(9,00,00,1,2,1971);
+        $this->_JANUARY_04_1971_00_00_00 = mktime(0,00,00,1,4,1971);
+        $this->_JANUARY_04_1971_09_00_00 = mktime(9,00,00,1,4,1971);
+        $this->_JANUARY_05_1971_09_00_00 = mktime(9,00,00,1,5,1971);
+        $this->_JANUARY_11_1971_00_00_00 = mktime(0,00,00,1,11,1971);
+        $this->_JANUARY_15_1971_00_00_00 = mktime(0,00,00,1,15,1971);
+        $this->_JANUARY_15_1971_09_00_00 = mktime(9,00,00,1,15,1971);
+        $this->_FEBRUARY_01_1971_00_00_00 = mktime(0,00,00,2,1,1971);
+        $this->_FEBRUARY_02_1971_00_00_00 = mktime(0,00,00,2,2,1971);
+        $this->_FEBRUARY_28_1971_00_00_00 = mktime(0,00,00,2,28,1971);
+    }
+    
+    /**
+     * Tests forbidden call to setHour on Piwik_ScheduledTime_Hourly
+     * @group Core
+     * @group ScheduledTime
+     * @expectedException Exception
+     */
+    public function testSetHourScheduledTimeHourly()
+    {
+        $hourlySchedule = new Piwik_ScheduledTime_Hourly();
+        $hourlySchedule->setHour(0);
+    }
+
+    /**
+     * Tests invalid call to setHour on Piwik_ScheduledTime_Daily
+     * @group Core
+     * @group ScheduledTime
+     * @expectedException Exception
+     */
+    public function testSetHourScheduledTimeDailyNegative()
+    {
+        $dailySchedule = new Piwik_ScheduledTime_Daily();
+        $dailySchedule->setHour(-1);
+    }
+
+    /**
+     * Tests invalid call to setHour on Piwik_ScheduledTime_Daily
+     * @group Core
+     * @group ScheduledTime
+     * @expectedException Exception
+     */
+    public function testSetHourScheduledTimeDailyOver24()
+    {
+        $dailySchedule = new Piwik_ScheduledTime_Daily();
+        $dailySchedule->setHour(25);
+    }
+    
+    /**
+     * Tests invalid call to setHour on Piwik_ScheduledTime_Weekly
+     * @group Core
+     * @group ScheduledTime
+     * @expectedException Exception
+     */
+    public function testSetHourScheduledTimeWeeklyNegative()
+    {
+        $weeklySchedule = new Piwik_ScheduledTime_Weekly();
+        $weeklySchedule->setHour(-1);
+    }
+
+    /**
+     * Tests invalid call to setHour on Piwik_ScheduledTime_Weekly
+     * @group Core
+     * @group ScheduledTime
+     * @expectedException Exception
+     */
+    public function testSetHourScheduledTimeWeeklyOver24()
+    {
+        $weeklySchedule = new Piwik_ScheduledTime_Weekly();
+        $weeklySchedule->setHour(25);
+    }
+    
+    /**
+     * Tests invalid call to setHour on Piwik_ScheduledTime_Monthly
+     * @group Core
+     * @group ScheduledTime
+     * @expectedException Exception
+     */
+    public function testSetHourScheduledTimeMonthlyNegative()
+    {
+        $monthlySchedule = new Piwik_ScheduledTime_Monthly();
+        $monthlySchedule->setHour(-1);
+    }
+
+    /**
+     * Tests invalid call to setHour on Piwik_ScheduledTime_Monthly
+     * @group Core
+     * @group ScheduledTime
+     * @expectedException Exception
+     */
+    public function testSetHourScheduledTimMonthlyOver24()
+    {
+        $monthlySchedule = new Piwik_ScheduledTime_Monthly();
+        $monthlySchedule->setHour(25);
+    }
+
+    /**
+     * Tests forbidden call to setDay on Piwik_ScheduledTime_Hourly
+     * @group Core
+     * @group ScheduledTime
+     * @expectedException Exception
+     */
+    public function testSetDayScheduledTimeHourly()
+    {
+        $hourlySchedule = new Piwik_ScheduledTime_Hourly();
+        $hourlySchedule->setDay(1);
+    }
+    
+    /**
+     * Tests forbidden call to setDay on Piwik_ScheduledTime_Daily
+     * @group Core
+     * @group ScheduledTime
+     * @expectedException Exception
+     */
+    public function testSetDayScheduledTimeDaily()
+    {
+        $dailySchedule = new Piwik_ScheduledTime_Daily();
+        $dailySchedule->setDay(1);
+    }
+    
+    /**
+     * Tests invalid call to setDay on Piwik_ScheduledTime_Weekly
+     * @group Core
+     * @group ScheduledTime
+     * @expectedException Exception
+     */
+    public function testSetDayScheduledTimeWeeklyDay0()
+    {
+        $weeklySchedule = new Piwik_ScheduledTime_Weekly();
+        $weeklySchedule->setDay(0);
+    }
+
+    /**
+     * Tests invalid call to setDay on Piwik_ScheduledTime_Weekly
+     * @group Core
+     * @group ScheduledTime
+     * @expectedException Exception
+     */
+    public function testSetDayScheduledTimeWeeklyOver7()
+    {
+        $weeklySchedule = new Piwik_ScheduledTime_Weekly();
+        $weeklySchedule->setDay(8);
+    }
+    
+    /**
+     * Tests invalid call to setDay on Piwik_ScheduledTime_Monthly
+     * @group Core
+     * @group ScheduledTime
+     * @expectedException Exception
+     */
+    public function testSetDayScheduledTimeMonthlyDay0()
+    {
+        $monthlySchedule = new Piwik_ScheduledTime_Monthly();
+        $monthlySchedule->setDay(0);
+    }
+
+    /**
+     * Tests invalid call to setDay on Piwik_ScheduledTime_Monthly
+     * @group Core
+     * @group ScheduledTime
+     * @expectedException Exception
+     */
+    public function testSetDayScheduledTimeMonthlyOver31()
+    {
+        $monthlySchedule = new Piwik_ScheduledTime_Monthly();
+        $monthlySchedule->setDay(32);
+    }
+    
+
+    /**
+     * Tests getRescheduledTime on Piwik_ScheduledTime_Hourly
+     * @group Core
+     * @group ScheduledTime
+     */
+    public function testGetRescheduledTimeHourly()
+    {
+        /*
+         * Test 1
+         *
+         * Context :
+         *  - getRescheduledTime called Friday January 1 1971 09:00:00 GMT
+         *
+         * Expected :
+         *  getRescheduledTime returns Friday January 1 1971 10:00:00 GMT
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Hourly', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_01_1971_09_00_00));
+        $this->assertEquals($this->_JANUARY_01_1971_10_00_00, $mock->getRescheduledTime());
+
+        /*
+         * Test 2
+         *
+         * Context :
+         *  - getRescheduledTime called Friday January 1 1971 09:10:00 GMT
+         *
+         * Expected :
+         *  getRescheduledTime returns Friday January 1 1971 10:00:00 GMT
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Hourly', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_01_1971_09_10_00));
+        $this->assertEquals($this->_JANUARY_01_1971_10_00_00, $mock->getRescheduledTime());
+    }
+
+    /**
+     * Tests getRescheduledTime on Piwik_ScheduledTime_Daily with unspecified hour
+     * @group Core
+     * @group ScheduledTime
+     */
+    public function testGetRescheduledTimeDailyUnspecifiedHour()
+    {
+        /*
+         * Test 1
+         *
+         * Context :
+         *  - getRescheduledTime called Friday January 1 1971 09:10:00 UTC
+         *  - setHour is not called, defaulting to midnight
+         *
+         * Expected :
+         *  getRescheduledTime returns Saturday January 2 1971 00:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Daily', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_01_1971_09_10_00));
+        $this->assertEquals($this->_JANUARY_02_1971_00_00_00, $mock->getRescheduledTime());
+    }
+
+    /**
+     * Tests getRescheduledTime on Piwik_ScheduledTime_Daily with specified hour
+     * @group Core
+     * @group ScheduledTime
+     */
+    public function testGetRescheduledTimeDailySpecifiedHour()
+    {
+        /*
+         * Test 1
+         *
+         * Context :
+         *  - getRescheduledTime called Friday January 1 1971 09:00:00 UTC
+         *  - setHour is set to 9
+         *
+         * Expected :
+         *  getRescheduledTime returns Saturday January 2 1971 09:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Daily', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_01_1971_09_00_00));
+        $mock->setHour(9);
+        $this->assertEquals($this->_JANUARY_02_1971_09_00_00, $mock->getRescheduledTime());
+
+        /*
+         * Test 2
+         *
+         * Context :
+         *  - getRescheduledTime called Friday January 1 1971 12:10:00 UTC
+         *  - setHour is set to 9
+         *
+         * Expected :
+         *  getRescheduledTime returns Saturday January 2 1971 09:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Daily', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_01_1971_12_10_00));
+        $mock->setHour(9);
+        $this->assertEquals($this->_JANUARY_02_1971_09_00_00, $mock->getRescheduledTime());
+
+        /*
+         * Test 3
+         *
+         * Context :
+         *  - getRescheduledTime called Friday January 1 1971 12:10:00 UTC
+         *  - setHour is set to 0
+         *
+         * Expected :
+         *  getRescheduledTime returns Saturday January 2 1971 00:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Daily', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_01_1971_12_10_00));
+        $mock->setHour(0);
+        $this->assertEquals($this->_JANUARY_02_1971_00_00_00, $mock->getRescheduledTime());
+    }
+    
+    /**
+     * Tests getRescheduledTime on Piwik_ScheduledTime_Weekly with unspecified hour and unspecified day
+     * @group Core
+     * @group ScheduledTime
+     */
+    public function testGetRescheduledTimeWeeklyUnspecifiedHourUnspecifiedDay()
+    {
+        /*
+         * Test 1
+         *
+         * Context :
+         *  - getRescheduledTime called Friday January 1 1971 09:10:00 UTC
+         *  - setHour is not called, defaulting to midnight
+         *  - setDay is not called, defaulting to monday
+         *
+         * Expected :
+         *  getRescheduledTime returns Monday January 4 1971 00:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Weekly', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_01_1971_09_10_00));
+        $this->assertEquals($this->_JANUARY_04_1971_00_00_00, $mock->getRescheduledTime());
+    }
+    
+    /**
+     * Tests getRescheduledTime on Piwik_ScheduledTime_Weekly with specified hour and unspecified day
+     * @group Core
+     * @group ScheduledTime
+     */
+    public function testGetRescheduledTimeWeeklySpecifiedHourUnspecifiedDay()
+    {
+        /*
+         * Test 1
+         *
+         * Context :
+         *  - getRescheduledTime called Friday January 1 1971 09:10:00 UTC
+         *  - setHour is set to 9
+         *  - setDay is not called, defaulting to monday
+         *
+         * Expected :
+         *  getRescheduledTime returns Monday January 4 1971 09:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Weekly', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_01_1971_09_10_00));
+        $mock->setHour(9);
+        $this->assertEquals($this->_JANUARY_04_1971_09_00_00, $mock->getRescheduledTime());
+    }
+
+    /**
+     * Tests getRescheduledTime on Piwik_ScheduledTime_Weekly with unspecified hour and specified day
+     * @group Core
+     * @group ScheduledTime
+     */
+    public function testGetRescheduledTimeWeeklyUnspecifiedHourSpecifiedDay()
+    {
+        /*
+         * Test 1
+         *
+         * Context :
+         *  - getRescheduledTime called Monday January 4 1971 09:00:00 UTC
+         *  - setHour is not called, defaulting to midnight
+         *  - setDay is set to 1, Monday
+         *
+         * Expected :
+         *  getRescheduledTime returns Monday January 11 1971 00:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Weekly', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_04_1971_09_00_00));
+        $mock->setDay(1);
+        $this->assertEquals($this->_JANUARY_11_1971_00_00_00, $mock->getRescheduledTime());
+        
+        /*
+         * Test 2
+         *
+         * Context :
+         *  - getRescheduledTime called Tuesday 5 1971 09:00:00 UTC
+         *  - setHour is not called, defaulting to midnight
+         *  - setDay is set to 1, Monday
+         *
+         * Expected :
+         *  getRescheduledTime returns Monday January 11 1971 00:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Weekly', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_05_1971_09_00_00));
+        $mock->setDay(1);
+        $this->assertEquals($this->_JANUARY_11_1971_00_00_00, $mock->getRescheduledTime());
+
+        /*
+         * Test 3
+         *
+         * Context :
+         *  - getRescheduledTime called Monday January 4 1971 09:00:00 UTC
+         *  - setHour is not called, defaulting to midnight
+         *  - setDay is set to 1, Friday
+         *
+         * Expected :
+         *  getRescheduledTime returns Friday January 15 1971 00:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Weekly', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_04_1971_09_00_00));
+        $mock->setDay(5);
+        $this->assertEquals($this->_JANUARY_15_1971_00_00_00, $mock->getRescheduledTime());
+    }
+
+    /**
+     * Tests getRescheduledTime on Piwik_ScheduledTime_Monthly with unspecified hour and unspecified day
+     * @group Core
+     * @group ScheduledTime
+     */
+    public function testGetRescheduledTimeMonthlyUnspecifiedHourUnspecifiedDay()
+    {
+        /*
+         * Test 1
+         *
+         * Context :
+         *  - getRescheduledTime called Friday January 1 1971 09:00:00 UTC
+         *  - setHour is not called, defaulting to midnight
+         *  - setDay is not called, defaulting to first day of the month
+         *
+         * Expected :
+         *  getRescheduledTime returns Monday February 1 1971 00:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Monthly', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_01_1971_09_00_00));
+        $this->assertEquals($this->_FEBRUARY_01_1971_00_00_00, $mock->getRescheduledTime());
+        
+        /*
+         * Test 2
+         *
+         * Context :
+         *  - getRescheduledTime called Tuesday January 5 1971 09:00:00 UTC
+         *  - setHour is not called, defaulting to midnight
+         *  - setDay is not called, defaulting to first day of the month
+         *
+         * Expected :
+         *  getRescheduledTime returns Monday February 1 1971 00:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Monthly', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_05_1971_09_00_00));
+        $this->assertEquals($this->_FEBRUARY_01_1971_00_00_00, $mock->getRescheduledTime());
+    }
+
+    
+    /**
+     * Tests getRescheduledTime on Piwik_ScheduledTime_Monthly with unspecified hour and specified day
+     * @group Core
+     * @group ScheduledTime
+     */
+    public function testGetRescheduledTimeMonthlyUnspecifiedHourSpecifiedDay()
+    {
+        /*
+         * Test 1
+         *
+         * Context :
+         *  - getRescheduledTime called Friday January 1 1971 09:00:00 UTC
+         *  - setHour is not called, defaulting to midnight
+         *  - setDay is set to 1
+         *
+         * Expected :
+         *  getRescheduledTime returns Monday February 1 1971 00:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Monthly', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_01_1971_09_00_00));
+        $mock->setDay(1);
+        $this->assertEquals($this->_FEBRUARY_01_1971_00_00_00, $mock->getRescheduledTime());
+        
+        /*
+         * Test 2
+         *
+         * Context :
+         *  - getRescheduledTime called Saturday January 2 1971 09:00:00 UTC
+         *  - setHour is not called, defaulting to midnight
+         *  - setDay is set to 2
+         *
+         * Expected :
+         *  getRescheduledTime returns Tuesday February 2 1971 00:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Monthly', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_02_1971_09_00_00));
+        $mock->setDay(2);
+        $this->assertEquals($this->_FEBRUARY_02_1971_00_00_00, $mock->getRescheduledTime());
+        
+        /*
+         * Test 3
+         *
+         * Context :
+         *  - getRescheduledTime called Friday January 15 1971 09:00:00 UTC
+         *  - setHour is not called, defaulting to midnight
+         *  - setDay is set to 2
+         *
+         * Expected :
+         *  getRescheduledTime returns Tuesday February 1 1971 00:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Monthly', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_15_1971_09_00_00));
+        $mock->setDay(2);
+        $this->assertEquals($this->_FEBRUARY_02_1971_00_00_00, $mock->getRescheduledTime());
+
+        /*
+         * Test 4
+         *
+         * Context :
+         *  - getRescheduledTime called Friday January 15 1971 09:00:00 UTC
+         *  - setHour is not called, defaulting to midnight
+         *  - setDay is set to 31
+         *
+         * Expected :
+         *  getRescheduledTime returns Sunday February 28 1971 00:00:00 UTC
+         */
+        $mock = $this->getMock('Piwik_ScheduledTime_Monthly', array('getTime'));
+        $mock->expects($this->any())
+             ->method('getTime')
+             ->will($this->returnValue($this->_JANUARY_15_1971_09_00_00));
+        $mock->setDay(31);
+        $this->assertEquals($this->_FEBRUARY_28_1971_00_00_00, $mock->getRescheduledTime());
+    }
+}
diff --git a/tests/PHPUnit/Core/SegmentExpressionTest.php b/tests/PHPUnit/Core/SegmentExpressionTest.php
new file mode 100644
index 0000000000..9c3a88f65b
--- /dev/null
+++ b/tests/PHPUnit/Core/SegmentExpressionTest.php
@@ -0,0 +1,128 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ * @version $Id$
+ */
+class SegmentExpressionTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Dataprovider for testSegmentSqlSimpleNoOperation
+     * @return array
+     */
+    public function getSimpleSegmentExpressions()
+    {
+        return array(
+            // classic expressions
+            array('A', " A "),
+            array('A,B', " (A OR B )"),
+            array('A;B', " A AND B "),
+            array('A;B;C', " A AND B AND C "),
+            array('A,B;C,D;E,F,G', " (A OR B) AND (C OR D) AND (E OR F OR G )"),
+
+            // unescape the backslash
+            array('A\,B\,C,D', " (A,B,C OR D )"),
+            array('\,A', ' ,A '),
+            // unescape only when it was escaping a known delimiter
+            array('\\\A', ' \\\A '),
+            // unescape at the end
+            array('\,\;\A\B,\,C,D\;E\,', ' (,;\A\B OR ,C OR D;E, )'),
+
+            // only replace when a following expression is detected
+            array('A,', ' A, '),
+            array('A;', ' A; '),
+            array('A;B;', ' A AND B; '),
+            array('A,B,', ' (A OR B, )'),
+        );
+    }
+
+    /**
+     * @dataProvider getSimpleSegmentExpressions
+     * @group Core
+     * @group SegmentExpression
+     */
+    public function testSegmentSqlSimpleNoOperation($expression, $expectedSql)
+    {
+        $segment = new Piwik_SegmentExpression($expression);
+        $expected = array('where' => $expectedSql, 'bind' => array(), 'join' => '');
+        $processed = $segment->getSql();
+        $this->assertEquals($expected, $processed);
+    }
+
+    /**
+     * Dataprovider for testSegmentSqlWithOperations
+     * @return array
+     */
+    public function getOperationSegmentExpressions()
+    {
+        // Filter expression => SQL string + Bind values
+        return array(
+            array('A==B%', array('where' => " A = ? ", 'bind' => array('B%'))),
+            array('ABCDEF====B===', array('where' => " ABCDEF = ? ", 'bind' => array('==B==='))),
+            array('A===B;CDEF!=C!=', array('where' => " A = ? AND CDEF <> ? ", 'bind' => array('=B', 'C!=' ))),
+            array('A==B,C==D', array('where' => " (A = ? OR C = ? )", 'bind' => array('B', 'D'))),
+            array('A!=B;C==D', array('where' => " A <> ? AND C = ? ", 'bind' => array('B', 'D'))),
+            array('A!=B;C==D,E!=Hello World!=', array('where' => " A <> ? AND (C = ? OR E <> ? )", 'bind' => array('B', 'D', 'Hello World!='))),
+
+            array('A>B', array('where' => " A > ? ", 'bind' => array('B'))),
+            array('A<B', array('where' => " A < ? ", 'bind' => array('B'))),
+            array('A<=B', array('where' => " A <= ? ", 'bind' => array('B'))),
+            array('A>=B', array('where' => " A >= ? ", 'bind' => array('B'))),
+            array('ABCDEF>=>=>=B===', array('where' => " ABCDEF >= ? ", 'bind' => array('>=>=B==='))),
+            array('A>=<=B;CDEF>G;H>=I;J<K;L<=M', array('where' => " A >= ? AND CDEF > ? AND H >= ? AND J < ? AND L <= ? ", 'bind' => array('<=B', 'G','I','K','M' ))),
+            array('A>=B;C>=D,E<w_ow great!', array('where' => " A >= ? AND (C >= ? OR E < ? )", 'bind' => array('B', 'D', 'w_ow great!'))),
+
+            array('A=@B_', array('where' => " A LIKE ? ", 'bind' => array('%B\_%'))),
+            array('A!@B%', array('where' => " A NOT LIKE ? ", 'bind' => array('%B\%%'))),
+        );
+    }
+
+    /**
+     * @dataProvider getOperationSegmentExpressions
+     * @group Core
+     * @group SegmentExpression
+     */
+    public function testSegmentSqlWithOperations($expression, $expectedSql)
+    {
+        $segment = new Piwik_SegmentExpression($expression);
+        $segment->parseSubExpressions();
+        $segment->parseSubExpressionsIntoSqlExpressions();
+        $processed = $segment->getSql();
+        $expectedSql['join'] = '';
+        $this->assertEquals($expectedSql, $processed);
+    }
+
+    /**
+     * Dataprovider for testBogusFiltersExpectExceptionThrown
+     * @return array
+     */
+    public function getBogusFilters()
+    {
+        return array(
+            array('A=B'),
+            array('C!D'),
+            array(''),
+            array('      '),
+            array(',;,'),
+            array(','),
+            array(',,'),
+            array('==='),
+            array('!=')
+        );
+    }
+
+    /**
+     * @dataProvider getBogusFilters
+     * @group Core
+     * @group SegmentExpression
+     * @expectedException Exception
+     */
+    public function testBogusFiltersExpectExceptionThrown($bogus)
+    {
+        $segment = new Piwik_SegmentExpression($bogus);
+        $segment->parseSubExpressions();
+        $segment->getSql();
+    }
+}
diff --git a/tests/PHPUnit/Core/SegmentTest.php b/tests/PHPUnit/Core/SegmentTest.php
new file mode 100644
index 0000000000..7117bcc2d1
--- /dev/null
+++ b/tests/PHPUnit/Core/SegmentTest.php
@@ -0,0 +1,465 @@
+<?php
+/**
+ * Piwik - Open source web analytics
+ *
+ * @link http://piwik.org
+ * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later
+ * @version $Id$
+ */
+class SegmentTest extends PHPUnit_Framework_TestCase
+{
+    public function setUp()
+    {
+        parent::setUp();
+
+        // setup the access layer (required in Segment contrustor testing if anonymous is allowed to use segments)
+        $pseudoMockAccess = new FakeAccess;
+        FakeAccess::$superUser = true;
+        Zend_Registry::set('access', $pseudoMockAccess);
+        
+        // Load and install plugins
+        $pluginsManager = Piwik_PluginsManager::getInstance();
+        $pluginsManager->loadPlugins( Piwik_Config::getInstance()->Plugins['Plugins'] );
+    }
+
+    public function tearDown()
+    {
+        parent::tearDown();
+        Piwik_PluginsManager::getInstance()->unloadPlugins();
+    }
+
+    protected function _filterWhitsSpaces($valueToFilter)
+    {
+        if(is_array($valueToFilter)) {
+            foreach($valueToFilter AS $key => $value) {
+                $valueToFilter[$key] = $this->_filterWhitsSpaces($value);
+            }
+            return $valueToFilter;
+        } else {
+            return preg_replace('/[\s]+/', ' ', $valueToFilter);
+        }
+    }
+
+
+    public function getCommonTestData()
+    {
+        return array(
+            // Normal segment
+            array('country==France', array(
+                'where' => ' log_visit.location_country = ? ',
+                'bind' => array('France'))),
+
+            // unescape the comma please
+            array('country==a\,==', array(
+                'where' => ' log_visit.location_country = ? ',
+                'bind' => array('a,=='))),
+
+            // AND, with 2 values rewrites
+            array('country==a;visitorType!=returning;visitorType==new', array(
+                'where' => ' log_visit.location_country = ? AND log_visit.visitor_returning <> ? AND log_visit.visitor_returning = ? ',
+                'bind' => array('a', '1', '0'))),
+
+            // OR, with 2 value rewrites
+            array('referrerType==search,referrerType==direct', array(
+                'where'=>' (log_visit.referer_type = ? OR log_visit.referer_type = ? )',
+                'bind' => array(Piwik_Common::REFERER_TYPE_SEARCH_ENGINE,
+                    Piwik_Common::REFERER_TYPE_DIRECT_ENTRY))),
+        );
+    }
+
+    /**
+     * @dataProvider getCommonTestData
+     * @group Core
+     * @group Segment
+     */
+    public function testCommon($segment, $expected)
+    {
+        $select = 'log_visit.idvisit';
+        $from = 'log_visit';
+
+        $expected = array(
+            'sql' => '
+                SELECT
+                    log_visit.idvisit
+                FROM
+                    '.Piwik_Common::prefixTable('log_visit').' AS log_visit
+                WHERE
+                    '.$expected['where'],
+            'bind' => $expected['bind']
+        );
+
+        $segment = new Piwik_Segment($segment, $idSites = array());
+        $sql = $segment->getSelectQuery($select, $from, false);
+
+        $this->assertEquals($this->_filterWhitsSpaces($expected), $this->_filterWhitsSpaces($sql));
+
+        // calling twice should give same results
+        $sql = $segment->getSelectQuery($select, array($from));
+        $this->assertEquals($this->_filterWhitsSpaces($expected), $this->_filterWhitsSpaces($sql));
+
+        $this->assertEquals(32, strlen($segment->getHash()));
+    }
+    
+    /**
+     * @group Core
+     * @group Segment
+     */
+    public function testGetSelectQueryNoJoin()
+    {
+        $select = '*';
+        $from = 'log_visit';
+        $where = 'idsite = ?';
+        $bind = array(1);
+        
+        $segment = 'customVariableName1==Test;visitorType==new';
+        $segment = new Piwik_Segment($segment, $idSites = array());
+        
+        $query = $segment->getSelectQuery($select, $from, $where, $bind);
+        
+        $expected = array(
+            "sql" => "
+                SELECT
+                    *
+                FROM
+                    ".Piwik_Common::prefixTable('log_visit')." AS log_visit
+                WHERE
+                    ( idsite = ? )
+                    AND
+                    ( log_visit.custom_var_k1 = ? AND log_visit.visitor_returning = ? )",
+            "bind" => array(1, 'Test', 0));
+        
+        $this->assertEquals($this->_filterWhitsSpaces($expected), $this->_filterWhitsSpaces($query));
+    }
+    
+    /**
+     * @group Core
+     * @group Segment
+     */
+    public function testGetSelectQueryJoinVisitOnAction()
+    {
+        $select = '*';
+        $from = 'log_link_visit_action';
+        $where = 'log_link_visit_action.idvisit = ?';
+        $bind = array(1);
+        
+        $segment = 'customVariablePageName1==Test;visitorType==new';
+        $segment = new Piwik_Segment($segment, $idSites = array());
+        
+        $query = $segment->getSelectQuery($select, $from, $where, $bind);
+        
+        $expected = array(
+            "sql" => "
+                SELECT
+                    *
+                FROM
+                    ".Piwik_Common::prefixTable('log_link_visit_action')." AS log_link_visit_action
+                    LEFT JOIN ".Piwik_Common::prefixTable('log_visit')." AS log_visit ON log_visit.idvisit = log_link_visit_action.idvisit
+                WHERE
+                    ( log_link_visit_action.idvisit = ? )
+                    AND
+                    ( log_link_visit_action.custom_var_k1 = ? AND log_visit.visitor_returning = ? )",
+            "bind" => array(1, 'Test', 0));
+        
+        $this->assertEquals($this->_filterWhitsSpaces($expected), $this->_filterWhitsSpaces($query));
+    }
+
+    /**
+     * @group Core
+     * @group Segment
+     */
+    public function testGetSelectQueryJoinActionOnVisit()
+    {
+        $select = 'sum(log_visit.visit_total_actions) as nb_actions, max(log_visit.visit_total_actions) as max_actions, sum(log_visit.visit_total_time) as sum_visit_length';
+        $from = 'log_visit';
+        $where = 'log_visit.idvisit = ?';
+        $bind = array(1);
+        
+        $segment = 'customVariablePageName1==Test;visitorType==new';
+        $segment = new Piwik_Segment($segment, $idSites = array());
+        
+        $query = $segment->getSelectQuery($select, $from, $where, $bind);
+        
+        $expected = array(
+            "sql" => "
+                SELECT
+                    sum(log_inner.visit_total_actions) as nb_actions, max(log_inner.visit_total_actions) as max_actions, sum(log_inner.visit_total_time) as sum_visit_length
+                FROM
+                    (
+                SELECT
+                    log_visit.visit_total_actions,
+                    log_visit.visit_total_time
+                FROM
+                    ".Piwik_Common::prefixTable('log_visit')." AS log_visit
+                    LEFT JOIN ".Piwik_Common::prefixTable('log_link_visit_action')." AS log_link_visit_action ON log_link_visit_action.idvisit = log_visit.idvisit
+                WHERE
+                    ( log_visit.idvisit = ? )
+                    AND
+                    ( log_link_visit_action.custom_var_k1 = ? AND log_visit.visitor_returning = ? )
+                GROUP BY log_visit.idvisit
+                    ) AS log_inner",
+            "bind" => array(1, 'Test', 0));
+        
+        $this->assertEquals($this->_filterWhitsSpaces($expected), $this->_filterWhitsSpaces($query));
+    }
+    
+    /**
+     * @group Core
+     * @group Segment
+     */
+    public function testGetSelectQueryJoinConversionOnAction()
+    {
+        $select = '*';
+        $from = 'log_link_visit_action';
+        $where = 'log_link_visit_action.idvisit = ?';
+        $bind = array(1);
+        
+        $segment = 'customVariablePageName1==Test;visitConvertedGoalId==1;customVariablePageName2==Test2';
+        $segment = new Piwik_Segment($segment, $idSites = array());
+        
+        $query = $segment->getSelectQuery($select, $from, $where, $bind);
+        
+        $expected = array(
+            "sql" => "
+                SELECT
+                    *
+                FROM
+                    ".Piwik_Common::prefixTable('log_link_visit_action')." AS log_link_visit_action
+                    LEFT JOIN ".Piwik_Common::prefixTable('log_conversion')." AS log_conversion ON log_conversion.idlink_va = log_link_visit_action.idlink_va AND log_conversion.idsite = log_link_visit_action.idsite
+                WHERE
+                    ( log_link_visit_action.idvisit = ? )
+                    AND
+                    ( log_link_visit_action.custom_var_k1 = ? AND log_conversion.idgoal = ? AND log_link_visit_action.custom_var_k2 = ? )",
+            "bind" => array(1, 'Test', 1, 'Test2'));
+        
+        $this->assertEquals($this->_filterWhitsSpaces($expected), $this->_filterWhitsSpaces($query));
+    }
+    
+    /**
+     * @group Core
+     * @group Segment
+     */
+    public function testGetSelectQueryJoinActionOnConversion()
+    {
+        $select = '*';
+        $from = 'log_conversion';
+        $where = 'log_conversion.idvisit = ?';
+        $bind = array(1);
+        
+        $segment = 'visitConvertedGoalId!=2;customVariablePageName1==Test;visitConvertedGoalId==1';
+        $segment = new Piwik_Segment($segment, $idSites = array());
+        
+        $query = $segment->getSelectQuery($select, $from, $where, $bind);
+        
+        $expected = array(
+            "sql" => "
+                SELECT
+                    *
+                FROM
+                    ".Piwik_Common::prefixTable('log_conversion')." AS log_conversion
+                    LEFT JOIN ".Piwik_Common::prefixTable('log_link_visit_action')." AS log_link_visit_action ON log_conversion.idlink_va = log_link_visit_action.idlink_va
+                WHERE
+                    ( log_conversion.idvisit = ? )
+                    AND
+                    ( log_conversion.idgoal <> ? AND log_link_visit_action.custom_var_k1 = ? AND log_conversion.idgoal = ? )",
+            "bind" => array(1, 2, 'Test', 1));
+        
+        $this->assertEquals($this->_filterWhitsSpaces($expected), $this->_filterWhitsSpaces($query));
+    }
+    
+    /**
+     * @group Core
+     * @group Segment
+     */
+    public function testGetSelectQueryJoinConversionOnVisit()
+    {
+        $select = 'log_visit.*';
+        $from = 'log_visit';
+        $where = 'log_visit.idvisit = ?';
+        $bind = array(1);
+        
+        $segment = 'visitConvertedGoalId==1';
+        $segment = new Piwik_Segment($segment, $idSites = array());
+        
+        $query = $segment->getSelectQuery($select, $from, $where, $bind);
+        
+        $expected = array(
+            "sql" => "
+                SELECT
+                    log_inner.*
+                FROM
+                    (
+                SELECT
+                    log_visit.*
+                FROM
+                    ".Piwik_Common::prefixTable('log_visit')." AS log_visit
+                    LEFT JOIN ".Piwik_Common::prefixTable('log_conversion')." AS log_conversion ON log_conversion.idvisit = log_visit.idvisit
+                WHERE
+                    ( log_visit.idvisit = ? )
+                    AND
+                    ( log_conversion.idgoal = ? )
+                GROUP BY log_visit.idvisit
+                    ) AS log_inner",
+            "bind" => array(1, 1));
+        
+        $this->assertEquals($this->_filterWhitsSpaces($expected), $this->_filterWhitsSpaces($query));
+    }
+    
+    /**
+     * @group Core
+     * @group Segemnt
+     */
+    public function testGetSelectQueryConversionOnly()
+    {
+        $select = 'log_conversion.*';
+        $from = 'log_conversion';
+        $where = 'log_conversion.idvisit = ?';
+        $bind = array(1);
+        
+        $segment = 'visitConvertedGoalId==1';
+        $segment = new Piwik_Segment($segment, $idSites = array());
+        
+        $query = $segment->getSelectQuery($select, $from, $where, $bind);
+        
+        $expected = array(
+            "sql" => "
+                SELECT
+                    log_conversion.*
+                FROM
+                    ".Piwik_Common::prefixTable('log_conversion')." AS log_conversion
+                WHERE
+                    ( log_conversion.idvisit = ? )
+                    AND
+                    ( log_conversion.idgoal = ? )",
+            "bind" => array(1, 1));
+        
+        $this->assertEquals($this->_filterWhitsSpaces($expected), $this->_filterWhitsSpaces($query));
+    }
+    
+    /**
+     * @group Core
+     * @group Segment
+     */
+    public function testGetSelectQueryJoinVisitOnConversion()
+    {
+        $select = '*';
+        $from = 'log_conversion';
+        $where = 'log_conversion.idvisit = ?';
+        $bind = array(1);
+        
+        $segment = 'visitConvertedGoalId==1,visitServerHour==12';
+        $segment = new Piwik_Segment($segment, $idSites = array());
+        
+        $query = $segment->getSelectQuery($select, $from, $where, $bind);
+        
+        $expected = array(
+            "sql" => "
+                SELECT
+                    *
+                FROM
+                    ".Piwik_Common::prefixTable('log_conversion')." AS log_conversion
+                    LEFT JOIN ".Piwik_Common::prefixTable('log_visit')." AS log_visit ON log_conversion.idvisit = log_visit.idvisit
+                WHERE
+                    ( log_conversion.idvisit = ? )
+                    AND
+                    ( (log_conversion.idgoal = ? OR HOUR(log_visit.visit_last_action_time) = ? ))",
+            "bind" => array(1, 1, 12));
+        
+        $this->assertEquals($this->_filterWhitsSpaces($expected), $this->_filterWhitsSpaces($query));
+    }
+    
+    /**
+     * visit is joined on action, then conversion is joined
+     * make sure that conversion is joined on action not visit
+     * 
+     * @group Core
+     * @group Segment
+     */
+    public function testGetSelectQueryJoinVisitAndConversionOnAction()
+    {
+        $select = '*';
+        $from = 'log_link_visit_action';
+        $where = false;
+        $bind = array();
+        
+        $segment = 'visitServerHour==12;visitConvertedGoalId==1';
+        $segment = new Piwik_Segment($segment, $idSites = array());
+        
+        $query = $segment->getSelectQuery($select, $from, $where, $bind);
+        
+        $expected = array(
+            "sql" => "
+                SELECT
+                    *
+                FROM
+                    ".Piwik_Common::prefixTable('log_link_visit_action')." AS log_link_visit_action
+                    LEFT JOIN ".Piwik_Common::prefixTable('log_visit')." AS log_visit ON log_visit.idvisit = log_link_visit_action.idvisit
+                    LEFT JOIN ".Piwik_Common::prefixTable('log_conversion')." AS log_conversion ON log_conversion.idlink_va = log_link_visit_action.idlink_va AND log_conversion.idsite = log_link_visit_action.idsite
+                WHERE
+                     HOUR(log_visit.visit_last_action_time) = ? AND log_conversion.idgoal = ? ",
+            "bind" => array(12, 1));
+        
+        $this->assertEquals($this->_filterWhitsSpaces($expected), $this->_filterWhitsSpaces($query));
+    }
+    
+    /**
+     * join conversion on visit, then actions
+     * make sure actions are joined before conversions
+     * 
+     * @group Core
+     * @group Segment
+     */
+    public function testGetSelectQueryJoinConversionAndActionOnVisit()
+    {
+        $select = 'log_visit.*';
+        $from = 'log_visit';
+        $where = false;
+        $bind = array();
+        
+        $segment = 'visitConvertedGoalId==1;visitServerHour==12;customVariablePageName1==Test';
+        $segment = new Piwik_Segment($segment, $idSites = array());
+        
+        $query = $segment->getSelectQuery($select, $from, $where, $bind);
+        
+        $expected = array(
+            "sql" => "
+                SELECT
+                    log_inner.*
+                FROM
+                    (
+                SELECT
+                    log_visit.*
+                FROM
+                    ".Piwik_Common::prefixTable('log_visit')." AS log_visit
+                    LEFT JOIN ".Piwik_Common::prefixTable('log_link_visit_action')." AS log_link_visit_action ON log_link_visit_action.idvisit = log_visit.idvisit
+                    LEFT JOIN ".Piwik_Common::prefixTable('log_conversion')." AS log_conversion ON log_conversion.idlink_va = log_link_visit_action.idlink_va AND log_conversion.idsite = log_link_visit_action.idsite
+                WHERE
+                     log_conversion.idgoal = ? AND HOUR(log_visit.visit_last_action_time) = ? AND log_link_visit_action.custom_var_k1 = ?
+                GROUP BY log_visit.idvisit
+                    ) AS log_inner",
+            "bind" => array(1, 12, 'Test'));
+        
+        $this->assertEquals($this->_filterWhitsSpaces($expected), $this->_filterWhitsSpaces($query));
+    }
+
+    /**
+     * Dataprovider for testBogusSegmentThrowsException
+     */
+    public function getBogusSegments()
+    {
+        return array(
+            array('referrerType==not'),
+            array('someRandomSegment==not'),
+            array('A=B')
+        );
+    }
+    /**
+     * @group Core
+     * @group Segment
+     * @dataProvider getBogusSegments
+     * @expectedException Exception
+     */
+    public function testBogusSegmentThrowsException($segment)
+    {
+        $segment = new Piwik_Segment($segment, $idSites = array());
+    }
+}
-- 
GitLab