diff --git a/tests/PHPUnit/Core/ArchiveProcessing/DayTest.php b/tests/PHPUnit/Core/ArchiveProcessing/DayTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e55af992fe503bc9890430bceede609a60fbe307
--- /dev/null
+++ b/tests/PHPUnit/Core/ArchiveProcessing/DayTest.php
@@ -0,0 +1,261 @@
+<?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 ArchiveProcessing_DayTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @group Core
+     * @group ArchiveProcessing
+     * @group ArchiveProcessing_Day
+     */
+    public function testGenerateDataTableSimple()
+    {
+        $row1 = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => 'page1', 'visits' => 1, 'actions' => 2, '666' => 'evil' )));
+                            
+        $input = array(
+            'page1' => $row1,
+        );
+                    
+        $table = new Piwik_DataTable;
+        $table->addRow($row1);
+        
+        $tableGenerated = Piwik_ArchiveProcessing_Day::generateDataTable($input);
+
+        $this->assertTrue(Piwik_DataTable::isEqual($table,$tableGenerated));
+    }
+    
+    /**
+     * @group Core
+     * @group ArchiveProcessing
+     * @group ArchiveProcessing_Day
+     */
+    public function testGenerateDataTable2rows()
+    {
+        $row1 = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => 'page1', 'visits' => 1, 'actions' => 2)));
+        $row2 = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => 'page2', 'visits' => 3, 'actions' => 5)));
+                            
+        $input = array(
+            'page1' => $row1,
+            'page2' => $row2,
+        );
+                    
+        $table = new Piwik_DataTable;
+        $table->addRow($row1);
+        $table->addRow($row2);
+        
+        $tableGenerated = Piwik_ArchiveProcessing_Day::generateDataTable($input);
+        
+        $this->assertTrue(Piwik_DataTable::isEqual($table,$tableGenerated));
+    }
+    
+    /**
+     * @group Core
+     * @group ArchiveProcessing
+     * @group ArchiveProcessing_Day
+     */
+    public function testGenerateDataTable1row2level()
+    {
+        $row1 = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => 'cat1', 'visits' => 3, 'actions' => 5 )));
+        
+        $rowLevel2 = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => 'page1', 'visits' => 3, 'actions' => 5)));
+        $subtable = new Piwik_DataTable;
+        $subtable->addRow($rowLevel2);
+        $row1->addSubtable($subtable);
+        
+        $table = new Piwik_DataTable;
+        $table->addRow($row1);
+        
+        $input = array(
+            'cat1' => array(
+                'page1' => $rowLevel2,
+            )
+        );
+        
+        $tableGenerated = Piwik_ArchiveProcessing_Day::generateDataTable($input);
+        
+        $r1 = new Piwik_DataTable_Renderer_Console();
+        $r1->setTable($table);
+        $r2 = new Piwik_DataTable_Renderer_Console();
+        $r2->setTable($tableGenerated);
+        
+        $this->assertTrue(Piwik_DataTable::isEqual($table,$tableGenerated));
+    }
+    
+    /**
+     * @group Core
+     * @group ArchiveProcessing
+     * @group ArchiveProcessing_Day
+     */
+    public function testGenerateDataTable2rows2level()
+    {
+        $table = new Piwik_DataTable;
+        
+        //FIRST ROW + SUBTABLE
+        $row1 = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array(    'label' => 'cat1', 'visits' => 3, 'actions' => 5 )));
+        
+        $rowLevel2a = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array(    'label' => 'page1', 'visits' => 3, 'actions' => 5)));
+        $subtable = new Piwik_DataTable;
+        $subtable->addRow($rowLevel2a);
+        $row1->addSubtable($subtable);
+        
+        //-- add
+        $table->addRow($row1);
+            
+        //SECOND ROW + SUBTABLE MULTI ROWS
+        $row1 = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => 'cat2', 'visits' => 13, 'actions' => 9 )));
+        
+        $rowLevel2b1 = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => 'page2a', 'visits' => 6, 'actions' => 8)));
+        
+        $rowLevel2b2 = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => 'page2b', 'visits' => 7, 'actions' => 1)));
+        $subtable = new Piwik_DataTable;
+        $subtable->addRow($rowLevel2b1);
+        $subtable->addRow($rowLevel2b2);
+        $row1->addSubtable($subtable);
+        
+        //-- add
+        $table->addRow($row1);
+        
+        // WHAT WE TEST
+        $input = array(
+        'cat1' => array(
+                'page1' => $rowLevel2a,
+                    ),
+        'cat2' => array(
+                'page2a' => $rowLevel2b1,
+                'page2b' => $rowLevel2b2,
+                    )
+                );                
+        $tableGenerated = Piwik_ArchiveProcessing_Day::generateDataTable($input);
+        
+        $r1 = new Piwik_DataTable_Renderer_Console();
+        $r1->setTable($table);
+        $r2 = new Piwik_DataTable_Renderer_Console();
+        $r2->setTable($tableGenerated);
+        
+        $this->assertTrue(Piwik_DataTable::isEqual($table,$tableGenerated));
+    }
+    
+    /**
+     * @group Core
+     * @group ArchiveProcessing
+     * @group ArchiveProcessing_Day
+     */
+    public function testGenerateDataTable1row4levelMultiRows()
+    {
+        $table = new Piwik_DataTable;
+        
+        //FIRST ROW + SUBTABLE
+        $rowcat2 = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => '456', 'visits' => 3, 'actions' => 5 )));
+        
+        $cat2 =  new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => 'cat2', 'visits' => 3, 'actions' => 5 )));
+
+        $rowcat1 = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => 'pagecat1', 'visits' => 6, 'actions' => 4)));
+        
+        $cat1 =  new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => 'cat1', 'visits' => 9, 'actions' => 9 )));
+        
+        $subtablecat2 = new Piwik_DataTable;
+        $subtablecat2->addRow($rowcat2);
+        $cat2->addSubtable($subtablecat2);
+        
+        $subtablecat1 = new Piwik_DataTable;
+        $subtablecat1->addRow($rowcat1);
+        $subtablecat1->addRow($cat2);
+        
+        $cat1->addSubtable($subtablecat1);
+        
+        //-- add
+        $table->addRow($cat1);
+        
+        // WHAT WE TEST
+        $input = array(
+            'cat1' => array(
+                'pagecat1' => $rowcat1,
+                'cat2' => array(
+                    'pagecat2' => $rowcat2,
+                ),
+            ),
+        );                
+        $tableGenerated = Piwik_ArchiveProcessing_Day::generateDataTable($input);
+        
+        $r1 = new Piwik_DataTable_Renderer_Console();
+        $r1->setTable($table);
+        $r2 = new Piwik_DataTable_Renderer_Console();
+        $r2->setTable($tableGenerated);
+        
+        $this->assertTrue(Piwik_DataTable::isEqual($table,$tableGenerated));
+    }
+    
+    /**
+     * @group Core
+     * @group ArchiveProcessing
+     * @group ArchiveProcessing_Day
+     */
+    public function testGenerateDataTable1row4level()
+    {
+        $table = new Piwik_DataTable;
+        
+        $rowpagecat3 = new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => '123123', 'visits' => 3, 'actions' => 5 )));
+        
+        $rowcat3 =  new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => '789.654', 'visits' => 3, 'actions' => 5 )));
+        $rowcat2 =  new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => 'cat2', 'visits' => 3, 'actions' => 5 )));
+        $rowcat1 =  new Piwik_DataTable_Row( array( Piwik_DataTable_Row::COLUMNS => 
+                            array('label' => '&*()', 'visits' => 3, 'actions' => 5 )));
+
+        $subtablerowpagecat3 = new Piwik_DataTable;
+        $subtablerowpagecat3->addRow($rowpagecat3);
+        $rowcat3->addSubtable($subtablerowpagecat3);
+        
+        $subtablecat2 = new Piwik_DataTable;
+        $subtablecat2->addRow($rowcat3);
+        $rowcat2->addSubtable($subtablecat2);
+        
+        
+        $subtablecat1 = new Piwik_DataTable;
+        $subtablecat1->addRow($rowcat2);
+        $rowcat1->addSubtable($subtablecat1);
+        
+        //-- add
+        $table->addRow($rowcat1);
+        
+        // WHAT WE TEST
+        $input = array(
+            '&*()' => array(
+                'cat2' => array(
+                    '789.654' => array(
+                        '123123' => $rowpagecat3,
+                    ),
+                ),
+            ),
+        );
+        
+        $tableGenerated = Piwik_ArchiveProcessing_Day::generateDataTable($input);
+        
+        $r1 = new Piwik_DataTable_Renderer_Console();
+        $r1->setTable($table);
+        $r2 = new Piwik_DataTable_Renderer_Console();
+        $r2->setTable($tableGenerated);
+        $this->assertTrue(Piwik_DataTable::isEqual($table,$tableGenerated));
+    }
+}
diff --git a/tests/PHPUnit/Core/DataTable/Filter/AddSummaryRowTest.php b/tests/PHPUnit/Core/DataTable/Filter/AddSummaryRowTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..09e74a65d9d8c5bcfd2795eb6a608dacf9a33bee
--- /dev/null
+++ b/tests/PHPUnit/Core/DataTable/Filter/AddSummaryRowTest.php
@@ -0,0 +1,200 @@
+<?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 DataTable_Filter_AddSummaryRowTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_AddSummaryRow
+     */
+    public function testOffsetIsCountSummaryRowShouldBeTheRow()
+    {
+        $table = $this->getDataTableCount5();
+        $filter = new Piwik_DataTable_Filter_AddSummaryRow($table, 5);
+        $filter->filter($table);
+        $this->assertEquals(5, $table->getRowsCount());
+        $this->assertTrue(Piwik_DataTable_Row::isEqual($table->getLastRow(), $this->getRow4()));
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_AddSummaryRow
+     */
+    public function testOffsetIsLessThanCountSummaryRowShouldBeTheSum()
+    {
+        $table = $this->getDataTableCount5();
+        $filter = new Piwik_DataTable_Filter_AddSummaryRow($table, 2);
+        $filter->filter($table);
+        $this->assertEquals(3, $table->getRowsCount());
+        $expectedRow = new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label'=>Piwik_DataTable::LABEL_SUMMARY_ROW, 'nb' => 111)));
+        $this->assertTrue(Piwik_DataTable_Row::isEqual($table->getLastRow(), $expectedRow));
+        // check that column 'label' is forced to be first in summary row
+        $this->assertEquals(array_keys($table->getLastRow()->getColumns()), array_keys($expectedRow->getColumns()));
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_AddSummaryRow
+     */
+    public function testOffsetIsMoreThanCountShouldNotAddSummaryRow()
+    {
+        $table = $this->getDataTableCount5();
+        $filter = new Piwik_DataTable_Filter_AddSummaryRow($table, 6);
+        $filter->filter($table);
+        $this->assertEquals(5, $table->getRowsCount());
+        $this->assertTrue(Piwik_DataTable_Row::isEqual($table->getLastRow(), $this->getRow4()));
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_AddSummaryRow
+     */
+    public function testWhenThereIsAlreadyASummaryRowShouldReplaceTheSummaryRow()
+    {
+        $table = $this->getDataTableCount5();
+        $filter1 = new Piwik_DataTable_Filter_AddSummaryRow($table, 3);
+        $filter1->filter($table);
+        $filter2 = new Piwik_DataTable_Filter_AddSummaryRow($table, 2);
+        $filter2->filter($table);
+        $this->assertEquals(3, $table->getRowsCount());
+        $expectedRow = new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label'=>Piwik_DataTable::LABEL_SUMMARY_ROW, 'nb' => 111)));
+        $this->assertTrue(Piwik_DataTable_Row::isEqual($table->getLastRow(), $expectedRow));
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_AddSummaryRow
+     */
+    public function testSumTablesWithSummaryRowShouldSumTheSummaryRow()
+    {
+        // row0, row1, row2, rowSummary1
+        $table1 = $this->getDataTableCount5();
+        $filter = new Piwik_DataTable_Filter_AddSummaryRow($table1, 3);
+        $filter->filter($table1);
+        
+        // row0, row1, rowSummary2
+        $table2 = $this->getDataTableCount5();
+        $filter = new Piwik_DataTable_Filter_AddSummaryRow($table2, 2);
+        $filter->filter($table2);
+        
+        // we expect row0+row0, row1+row1, row2, rowSummary1+rowSummary2
+        $expectedTable = new Piwik_DataTable;
+        $expectedTable->addRow( new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label'=>'amazon', 'nb' => 20000) )));
+        $expectedTable->addRow( new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label'=>'yahoo', 'nb' => 2000) )));
+        $expectedTable->addRow( new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label'=>'piwik', 'nb' => 100) )));
+        $expectedTable->addRow( new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label'=>Piwik_DataTable::LABEL_SUMMARY_ROW, 'nb' => 122) )));
+        
+        $table1->addDataTable($table2);
+        $this->assertTrue(Piwik_DataTable::isEqual($expectedTable, $table1));
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_AddSummaryRow
+     */
+    public function testAddOneTableWithSummaryRow()
+    {
+        // row0, row1, row2, rowSummary1
+        $table1 = $this->getDataTableCount5();
+        $filter = new Piwik_DataTable_Filter_AddSummaryRow($table1, 3);
+        $filter->filter($table1);
+        
+        // row0, row1, row2, row3, row4
+        $table2 = $this->getDataTableCount5();
+        
+        // we expect row0+row0, row1+row1, row2+row2, row3, row4, rowSummary1
+        $expectedTable = new Piwik_DataTable;
+        $expectedTable->addRow( new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label'=>'amazon', 'nb' => 20000) )));
+        $expectedTable->addRow( new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label'=>'yahoo', 'nb' => 2000) )));
+        $expectedTable->addRow( new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label'=>'piwik', 'nb' => 200) )));
+        $expectedTable->addRow( $this->getRow3());
+        $expectedTable->addRow( $this->getRow4());
+        $expectedTable->addRow( new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label'=>Piwik_DataTable::LABEL_SUMMARY_ROW, 'nb' => 11))));
+        
+        $table1->addDataTable($table2);
+        $this->assertTrue(Piwik_DataTable::isEqual($expectedTable, $table1));
+        
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_AddSummaryRow
+     */
+    public function testWhenRowsInRandomOrderButSortSpecifiedShouldComputeSummaryRowAfterSort()
+    {
+        $table = new Piwik_DataTable;
+        $table->addRow( $this->getRow3() );
+        $table->addRow( $this->getRow2() );
+        $table->addRow( $this->getRow4() );
+        $table->addRow( $this->getRow1() );
+        $table->addRow( $this->getRow0() );
+        
+        $filter = new Piwik_DataTable_Filter_AddSummaryRow($table, 2, Piwik_DataTable::LABEL_SUMMARY_ROW, $columnToSortBy = 'nb');
+        $filter->filter($table);
+        $this->assertEquals(3, $table->getRowsCount());
+        $expectedRow = new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label'=>Piwik_DataTable::LABEL_SUMMARY_ROW, 'nb' => 111)));
+        $this->assertTrue(Piwik_DataTable_Row::isEqual($table->getLastRow(), $expectedRow));
+    }
+    
+    /**
+     * Returns table used for the tests
+     *
+     * @return Piwik_DataTable
+     */
+    protected function getDataTableCount5()
+    {
+        $table = new Piwik_DataTable;
+        $table->addRow( $this->getRow0() );
+        $table->addRow( $this->getRow1() );
+        $table->addRow( $this->getRow2() );
+        $table->addRow( $this->getRow3() );
+        $table->addRow( $this->getRow4() );
+        return $table;
+    }
+    protected function getRow0()
+    {
+        return new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('nb' => 10000, 'label'=>'amazon')));
+    }
+    protected function getRow1()
+    {
+        return new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label'=>'yahoo', 'nb' => 1000)));
+    }
+    protected function getRow2()
+    {
+        return new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label'=>'piwik', 'nb' => 100)));
+    }
+    protected function getRow3()
+    {
+        return new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('label'=>'ask', 'nb' => 10)));
+    }
+    protected function getRow4()
+    {
+        return new Piwik_DataTable_Row(array( Piwik_DataTable_Row::COLUMNS => array('nb' => 1, 'label'=>'google')));
+    }
+}
diff --git a/tests/PHPUnit/Core/DataTable/Filter/ExcludeLowPopulationTest.php b/tests/PHPUnit/Core/DataTable/Filter/ExcludeLowPopulationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..90349d2c7bfaab3e140f53fca3bf3c54845982a9
--- /dev/null
+++ b/tests/PHPUnit/Core/DataTable/Filter/ExcludeLowPopulationTest.php
@@ -0,0 +1,134 @@
+<?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 DataTable_Filter_ExcludeLowPopulationTest extends PHPUnit_Framework_TestCase
+{
+    protected function getTestDataTable()
+    {
+        $table = new Piwik_DataTable;
+        $table->addRowsFromArray(
+            array(
+                array(Piwik_DataTable_Row::COLUMNS => array('label'=>'zero',        'count' => 0)),
+                array(Piwik_DataTable_Row::COLUMNS => array('label'=>'one',         'count' => 1)),
+                array(Piwik_DataTable_Row::COLUMNS => array('label'=>'onedotfive',  'count' => 1.5)),
+                array(Piwik_DataTable_Row::COLUMNS => array('label'=>'ten',         'count' => 10)),
+                array(Piwik_DataTable_Row::COLUMNS => array('label'=>'ninety',      'count' => 90)),
+                array(Piwik_DataTable_Row::COLUMNS => array('label'=>'hundred',     'count' => 100)),
+            )
+        );
+        return $table;
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_ExcludeLowPopulation
+     */
+    public function testStandardTable()
+    {
+        $table = $this->getTestDataTable();    
+        $filter = new Piwik_DataTable_Filter_ExcludeLowPopulation($table, 'count', 1.1);
+        $filter->filter($table);
+        $this->assertEquals(4, $table->getRowsCount());
+        $this->assertEquals(array(1.5, 10, 90, 100), $table->getColumn('count'));
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_ExcludeLowPopulation
+     */
+    public function testFilterEqualOneDoesFilter()
+    {
+        $table = $this->getTestDataTable();    
+        $filter = new Piwik_DataTable_Filter_ExcludeLowPopulation($table, 'count', 1);
+        $filter->filter($table);
+        $this->assertEquals(5, $table->getRowsCount());
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_ExcludeLowPopulation
+     */
+    public function testFilterEqualZeroDoesFilter()
+    {
+        $table = $this->getTestDataTable();    
+        $filter = new Piwik_DataTable_Filter_ExcludeLowPopulation($table, 'count', 0);
+        $filter->filter($table);
+        $this->assertEquals(3, $table->getRowsCount());
+        $this->assertEquals(array(10, 90, 100), $table->getColumn('count'));
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_ExcludeLowPopulation
+     */
+    public function testFilterSpecifyExcludeLowPopulationThresholdDoesFilter()
+    {
+        $table = $this->getTestDataTable();    
+        $filter = new Piwik_DataTable_Filter_ExcludeLowPopulation($table, 'count', 0, 0.4); //40%
+        $filter->filter($table);
+        $this->assertEquals(2, $table->getRowsCount());
+        $this->assertEquals(array(90, 100), $table->getColumn('count'));
+    }
+    
+
+    
+    /**
+     * Test to exclude low population filter
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_ExcludeLowPopulation
+     */
+    function testFilterLowpop1()
+    {
+        
+        $idcol = Piwik_DataTable_Row::COLUMNS;
+        
+        $table = new Piwik_DataTable();
+        $rows = array(
+            array( $idcol => array('label'=>'google',   'nb_visits' => 897)),//0
+            array( $idcol => array('label'=>'ask',      'nb_visits' => -152)),//1
+            array( $idcol => array('label'=>'piwik',    'nb_visits' => 1.5)),//2
+            array( $idcol => array('label'=>'piwik2',   'nb_visits' => 1.4)),//2
+            array( $idcol => array('label'=>'yahoo',    'nb_visits' => 154)),//3
+            array( $idcol => array('label'=>'amazon',   'nb_visits' => 30)),//4
+            array( $idcol => array('label'=>'238949',   'nb_visits' => 0)),//5
+            array( $idcol => array('label'=>'Q*(%&*',   'nb_visits' => 1)),//6
+            array( $idcol => array('label'=>'Q*(%&*2',  'nb_visits' => -1.5)),//6
+        );
+        $table->addRowsFromArray( $rows );
+        
+        $expectedtable = new Piwik_DataTable();
+        $rows = array(
+            array( $idcol => array('label'=>'google',   'nb_visits' => 897)),//0
+            array( $idcol => array('label'=>'piwik',    'nb_visits' => 1.5)),//2
+            array( $idcol => array('label'=>'piwik2',   'nb_visits' => 1.4)),//2
+            array( $idcol => array('label'=>'yahoo',    'nb_visits' => 154)),//3
+            array( $idcol => array('label'=>'amazon',   'nb_visits' => 30)),//4
+        );
+        $expectedtable->addRowsFromArray( $rows );
+        
+        $filter = new Piwik_DataTable_Filter_ExcludeLowPopulation($table, 'nb_visits', 1.4);
+        $filter->filter($table);
+        
+        $this->assertTrue(Piwik_DataTable::isEqual($table, $expectedtable));
+    }
+}
diff --git a/tests/PHPUnit/Core/DataTable/Filter/LimitTest.php b/tests/PHPUnit/Core/DataTable/Filter/LimitTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..30b5183e1928039a0727e013e32ebacc0a84c0ae
--- /dev/null
+++ b/tests/PHPUnit/Core/DataTable/Filter/LimitTest.php
@@ -0,0 +1,387 @@
+<?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 DataTable_Filter_LimitTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Returns table used for the tests
+     *
+     * @return Piwik_DataTable
+     */
+    protected function getDataTableCount10()
+    {
+        $table = new Piwik_DataTable;
+        $idcol = Piwik_DataTable_Row::COLUMNS;
+        $rows = array(
+            array( $idcol => array('label'=>'google',     'idRow' => 0)),
+            array( $idcol => array('label'=>'ask',         'idRow' => 1)),
+            array( $idcol => array('label'=>'piwik',     'idRow' => 2)),
+            array( $idcol => array('label'=>'yahoo',     'idRow' => 3)),
+            array( $idcol => array('label'=>'amazon',     'idRow' => 4)),
+            array( $idcol => array('label'=>'238949',     'idRow' => 5)),
+            array( $idcol => array('label'=>'test',     'idRow' => 6)),
+            array( $idcol => array('label'=>'amazing',     'idRow' => 7)),
+            array( $idcol => array('label'=>'great',     'idRow' => 8)),
+            Piwik_DataTable::ID_SUMMARY_ROW => array( $idcol => array('label'=>'summary row',    'idRow' => 9)),
+        );
+        $table->addRowsFromArray( $rows );
+        return $table;
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Limit
+     */
+    public function testNormal()
+    {
+        $offset = 2;
+        $limit = 3;
+        $table = $this->getDataTableCount10();
+        $filter = new Piwik_DataTable_Filter_Limit($table, $offset, $limit);
+        $filter->filter($table);
+        $this->assertEquals(3, $table->getRowsCount());
+        $this->assertEquals(2, $table->getFirstRow()->getColumn('idRow'));
+        $this->assertEquals(4, $table->getLastRow()->getColumn('idRow'));
+        $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter());
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Limit
+     */
+    public function testLimitLessThanCountShouldReturnCountLimit()
+    {
+        $offset = 2;
+        $limit = 7;
+        $table = $this->getDataTableCount10();
+        $filter = new Piwik_DataTable_Filter_Limit($table, $offset, $limit);
+        $filter->filter($table);
+        $this->assertEquals(7, $table->getRowsCount());
+        $this->assertEquals(2, $table->getFirstRow()->getColumn('idRow'));
+        $this->assertEquals(8, $table->getLastRow()->getColumn('idRow'));
+        $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter());
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Limit
+     */
+    public function testLimitIsCountShouldNotDeleteAnything()
+    {
+        $offset = 0;
+        $limit = 10;
+        $table = $this->getDataTableCount10();
+        $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter());
+        $filter = new Piwik_DataTable_Filter_Limit($table, $offset, $limit);
+        $filter->filter($table);
+        $this->assertEquals(10, $table->getRowsCount());
+        $this->assertEquals(0, $table->getFirstRow()->getColumn('idRow'));
+        $this->assertEquals(9, $table->getLastRow()->getColumn('idRow'));
+        $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter());
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Limit
+     */
+    public function testLimitGreaterThanCountShouldReturnCountUntilCount()
+    {
+        $offset = 5;
+        $limit = 20;
+        $table = $this->getDataTableCount10();
+        $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter());
+        $filter = new Piwik_DataTable_Filter_Limit($table, $offset, $limit);
+        $filter->filter($table);
+        $this->assertEquals(5, $table->getRowsCount());
+        $this->assertEquals(5, $table->getFirstRow()->getColumn('idRow'));
+        $this->assertEquals(9, $table->getLastRow()->getColumn('idRow'));
+        $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter());
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Limit
+     */
+    public function testLimitIsNullShouldReturnCountIsOffset()
+    {
+        $offset = 1;
+        $table = $this->getDataTableCount10();
+        $filter = new Piwik_DataTable_Filter_Limit($table, $offset);
+        $filter->filter($table);
+        $this->assertEquals(9, $table->getRowsCount());
+        $this->assertEquals(1, $table->getFirstRow()->getColumn('idRow'));
+        $this->assertEquals(9, $table->getLastRow()->getColumn('idRow'));
+        $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter());
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Limit
+     */
+    public function testOffsetJustBeforeSummaryRowShouldJustReturnSummaryRow()
+    {
+        $offset = 9;
+        $limit = 1;
+        $table = $this->getDataTableCount10();
+        $filter = new Piwik_DataTable_Filter_Limit($table, $offset, $limit);
+        $filter->filter($table);
+        $this->assertEquals(1, $table->getRowsCount());
+        $this->assertEquals(9, $table->getFirstRow()->getColumn('idRow'));
+        $this->assertEquals(9, $table->getLastRow()->getColumn('idRow'));
+        $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter());
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Limit
+     */
+    public function testOffsetJustBeforeSummaryRowWithBigLimitShouldJustReturnSummaryRow()
+    {
+        $offset = 9;
+        $limit = 100;
+        $table = $this->getDataTableCount10();
+        $filter = new Piwik_DataTable_Filter_Limit($table, $offset, $limit);
+        $filter->filter($table);
+        $this->assertEquals(1, $table->getRowsCount());
+        $this->assertEquals(9, $table->getFirstRow()->getColumn('idRow'));
+        $this->assertEquals(9, $table->getLastRow()->getColumn('idRow'));
+        $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter());
+    }
+
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Limit
+     */
+    public function testOffsetBeforeSummaryRowShouldJustReturnRowAndSummaryRow()
+    {
+        $offset = 8;
+        $limit = 3;
+        $table = $this->getDataTableCount10();
+        $filter = new Piwik_DataTable_Filter_Limit($table, $offset, $limit);
+        $filter->filter($table);
+        $this->assertEquals(2, $table->getRowsCount());
+        $this->assertEquals(8, $table->getFirstRow()->getColumn('idRow'));
+        $this->assertEquals(9, $table->getLastRow()->getColumn('idRow'));
+        $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter());
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Limit
+     */
+    public function testOffsetGreaterThanCountShouldReturnEmptyTable()
+    {
+        $offset = 10;
+        $limit = 10;
+        $table = $this->getDataTableCount10();
+        $filter = new Piwik_DataTable_Filter_Limit($table, $offset, $limit);
+        $filter->filter($table);
+        $this->assertEquals(0, $table->getRowsCount());
+        $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter());
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Limit
+     */
+    public function testLimitIsZeroShouldReturnEmptyTable()
+    {
+        $offset = 0;
+        $limit = 0;
+        $table = $this->getDataTableCount10();
+        $filter = new Piwik_DataTable_Filter_Limit($table, $offset, $limit);
+        $filter->filter($table);
+        $this->assertEquals(0, $table->getRowsCount());
+        $this->assertEquals(10, $table->getRowsCountBeforeLimitFilter());
+    }
+    
+    /**
+     * Test to filter a table with a offset, limit
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Limit
+     */
+    public function testFilterOffsetLimit()
+    {
+        $table = new Piwik_DataTable;
+        
+        $idcol = Piwik_DataTable_Row::COLUMNS;
+        
+        $rows = array(
+            array( $idcol => array('label'=>'google')),//0
+            array( $idcol => array('label'=>'ask')),//1
+            array( $idcol => array('label'=>'piwik')),//2
+            array( $idcol => array('label'=>'yahoo')),//3
+            array( $idcol => array('label'=>'amazon')),//4
+            array( $idcol => array('label'=>'238975247578949')),//5
+            array( $idcol => array('label'=>'Q*(%&*("$&%*(&"$*")"))'))//6
+        );
+        
+        $table->addRowsFromArray( $rows );
+        
+        $expectedtable = clone $table;
+        $expectedtable->deleteRows(array(0,1,6));
+        
+        $filter = new Piwik_DataTable_Filter_Limit($table, 2, 4);
+        $filter->filter($table);
+        
+        $colAfter=$colExpected=array();
+        foreach($table->getRows() as $row) $colAfter[] = $row->getColumn('label');
+        foreach($expectedtable->getRows() as $row) $colExpected[] = $row->getColumn('label');
+        
+        $this->assertEquals(array_values($expectedtable->getRows()), array_values($table->getRows()));
+    }
+    
+    /**
+     * Test to filter a column with a offset, limit off bound
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Limit
+     */
+    public function testFilterOffsetLimitOffbound()
+    {
+        $table = new Piwik_DataTable;
+        
+        $idcol = Piwik_DataTable_Row::COLUMNS;
+        
+        $rows = array(
+            array( $idcol => array('label'=>'google')),//0
+            array( $idcol => array('label'=>'ask')),//1
+            array( $idcol => array('label'=>'piwik')),//2
+            array( $idcol => array('label'=>'yahoo')),//3
+            array( $idcol => array('label'=>'amazon')),//4
+            array( $idcol => array('label'=>'238975247578949')),//5
+            array( $idcol => array('label'=>'Q*(%&*("$&%*(&"$*")"))'))//6
+        );
+        
+        $table->addRowsFromArray( $rows );
+        
+        $expectedtable = clone $table;
+        $expectedtable->deleteRows(array(0,1,3,4,5,6));
+        
+        $filter = new Piwik_DataTable_Filter_Limit($table, 2, 1);
+        $filter->filter($table);
+        
+        $colAfter=$colExpected=array();
+        foreach($table->getRows() as $row) $colAfter[] = $row->getColumn('label');
+        foreach($expectedtable->getRows() as $row) $colExpected[] = $row->getColumn('label');
+        
+        $this->assertEquals(array_values($expectedtable->getRows()), array_values($table->getRows()));
+    }
+    
+    /**
+     * Test to filter a column with a offset, limit 2
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Limit
+     */
+    public function testFilterOffsetLimit2()
+    {
+        $table = new Piwik_DataTable;
+        
+        $idcol = Piwik_DataTable_Row::COLUMNS;
+        
+        $rows = array(
+            array( $idcol => array('label'=>'google')),//0
+            array( $idcol => array('label'=>'ask')),//1
+            array( $idcol => array('label'=>'piwik')),//2
+            array( $idcol => array('label'=>'yahoo')),//3
+            array( $idcol => array('label'=>'amazon')),//4
+            array( $idcol => array('label'=>'238975247578949')),//5
+            array( $idcol => array('label'=>'Q*(%&*("$&%*(&"$*")"))'))//6
+        );
+        
+        $table->addRowsFromArray( $rows );
+        
+        $expectedtable = clone $table;
+        
+        $filter = new Piwik_DataTable_Filter_Limit($table, 0, 15);
+        $filter->filter($table);
+        
+        $colAfter=$colExpected=array();
+        foreach($table->getRows() as $row) $colAfter[] = $row->getColumn('label');
+        foreach($expectedtable->getRows() as $row) $colExpected[] = $row->getColumn('label');
+        
+        $this->assertEquals(array_values($expectedtable->getRows()), array_values($table->getRows()));
+    }
+    
+    /**
+     * Test to filter a column with a offset, limit 3
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Limit
+     */
+    public function testFilterOffsetLimit3()
+    {
+        $table = new Piwik_DataTable;
+        
+        $idcol = Piwik_DataTable_Row::COLUMNS;
+        
+        $rows = array(
+            array( $idcol => array('label'=>'google')),//0
+            array( $idcol => array('label'=>'ask')),//1
+            array( $idcol => array('label'=>'piwik')),//2
+            array( $idcol => array('label'=>'yahoo')),//3
+            array( $idcol => array('label'=>'amazon')),//4
+            array( $idcol => array('label'=>'238975247578949')),//5
+            array( $idcol => array('label'=>'Q*(%&*("$&%*(&"$*")"))'))//6
+        );
+        
+        $table->addRowsFromArray( $rows );
+
+        $expectedtable = new Piwik_DataTable;
+        
+        $filter = new Piwik_DataTable_Filter_Limit($table, 8, 15);
+        $filter->filter($table);
+        
+        $colAfter=$colExpected=array();
+        foreach($table->getRows() as $row) $colAfter[] = $row->getColumn('label');
+        foreach($expectedtable->getRows() as $row) $colExpected[] = $row->getColumn('label');
+        
+        $this->assertEquals(array_values($expectedtable->getRows()), array_values($table->getRows()));
+    }
+    
+}
diff --git a/tests/PHPUnit/Core/DataTable/Filter/PatternRecursiveTest.php b/tests/PHPUnit/Core/DataTable/Filter/PatternRecursiveTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..bdbfe65d3a398cae32235d1fdce66d474abf8b4e
--- /dev/null
+++ b/tests/PHPUnit/Core/DataTable/Filter/PatternRecursiveTest.php
@@ -0,0 +1,79 @@
+<?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 DataTable_Filter_PatternRecursiveTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Returns a data table for testing
+     * @return Piwik_DataTable
+     */
+    protected function getTable()
+    {
+        $subtableAskPath1 = new Piwik_DataTable();
+        $subtableAskPath1->addRowsFromArray(array (
+            array ( Piwik_DataTable_Row::COLUMNS => array( 'label' => 'path1-index-page.html') ),
+            array ( Piwik_DataTable_Row::COLUMNS => array( 'label' => 'another-page') ),
+        ));
+
+        $subtableAsk = new Piwik_DataTable();
+        $subtableAsk->addRowsFromArray(array (
+            array ( Piwik_DataTable_Row::COLUMNS => array( 'label' => 'path1'),
+                    Piwik_DataTable_Row::DATATABLE_ASSOCIATED => $subtableAskPath1),
+            array ( Piwik_DataTable_Row::COLUMNS => array( 'label' => 'index.html') ),
+        ));
+
+        $table = new Piwik_DataTable;
+        $rows = array(
+            array(  Piwik_DataTable_Row::COLUMNS => array('label'=>'http://www.ask.com'),
+                    Piwik_DataTable_Row::DATATABLE_ASSOCIATED => $subtableAsk),
+            array( Piwik_DataTable_Row::COLUMNS => array('label'=>'yahoo')),
+        );
+        $table->addRowsFromArray( $rows );
+        return $table;
+    }
+
+    /**
+     * Dataprovider for testFilterPattern
+     */
+    public function getTestData()
+    {
+        return array(
+            // level 0
+            array(array('hoo', array(1))),
+            // level 1
+            array(array('path1', array(0))),
+            // level 2
+            array(array('path1-index-page', array(0))),
+            // not found
+            array(array('not found', array())),
+        );
+    }
+
+    /**
+     * Test to filter a column with a pattern
+     *
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_PatternRecursive
+     * @dataProvider getTestData
+     */
+    public function testFilterPattern($test)
+    {
+        $table = $this->getTable();
+        $rowIds = array_keys($table->getRows());
+        $pattern = $test[0];
+        $expectedRows = $test[1];
+        $rowToDelete = array_diff($rowIds, $expectedRows);
+        $expectedtable = clone $table;
+        $expectedtable->deleteRows($rowToDelete);
+        $filteredTable = clone $table;
+        $filteredTable->filter('PatternRecursive', array('label', $pattern));
+        $this->assertEquals($expectedtable->getRows(), $filteredTable->getRows());
+    }
+}
diff --git a/tests/PHPUnit/Core/DataTable/Filter/PatternTest.php b/tests/PHPUnit/Core/DataTable/Filter/PatternTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9347921ac7ecf9e164e0bc557427a479944011e3
--- /dev/null
+++ b/tests/PHPUnit/Core/DataTable/Filter/PatternTest.php
@@ -0,0 +1,62 @@
+<?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 DataTable_Filter_PatternTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Dataprovider for testFilterPattern
+     */
+    public function getTestData()
+    {
+        return array(
+            array(array('ask', array(1))),
+            array(array('oo', array(0,3))),
+            array(array('^yah', array(3))),
+            array(array('\*', array(6))),
+            array(array('2/4', array(5))),
+            array(array('amazon|yahoo', array(3,4))),
+        );
+    }
+
+    /**
+     * Test to filter a column with a pattern
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Pattern
+     * @dataProvider getTestData
+     */
+    public function testFilterPattern($test)
+    {
+        $table = new Piwik_DataTable;
+        
+        $idcol = Piwik_DataTable_Row::COLUMNS;
+        
+        $rows = array(
+            array( $idcol => array('label'=>'google')),
+            array( $idcol => array('label'=>'ask')),
+            array( $idcol => array('label'=>'piwik')),
+            array( $idcol => array('label'=>'yahoo')),
+            array( $idcol => array('label'=>'amazon')),
+            array( $idcol => array('label'=>'2389752/47578949')),
+            array( $idcol => array('label'=>'Q*(%&*("$&%*(&"$*")"))'))
+        );
+        $table->addRowsFromArray( $rows );
+        $rowIds = array_keys($rows);
+        
+        $pattern = $test[0];
+        $expectedRows = $test[1];
+        $rowToDelete = array_diff($rowIds, $expectedRows);
+        $expectedtable = clone $table;
+        $expectedtable->deleteRows($rowToDelete);
+        $filteredTable = clone $table;
+        $filteredTable->filter('Pattern', array('label', $pattern));
+        $this->assertEquals($expectedtable->getRows(), $filteredTable->getRows());
+    }
+}
diff --git a/tests/PHPUnit/Core/DataTable/Filter/SortTest.php b/tests/PHPUnit/Core/DataTable/Filter/SortTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9ccdd3a07f830dc1483dbb14007db257bb0ca6db
--- /dev/null
+++ b/tests/PHPUnit/Core/DataTable/Filter/SortTest.php
@@ -0,0 +1,188 @@
+<?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 DataTable_Filter_SortTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Sort
+     */
+    public function testNormalSortDescending ()
+    {
+        $table = new Piwik_DataTable();
+        $table->addRowsFromArray(array(
+                                      array(Piwik_DataTable_Row::COLUMNS => array('label' => 'ask',         'count' => 100)),
+                                      array(Piwik_DataTable_Row::COLUMNS => array('label' => 'nintendo',    'count' => 0)),
+                                      array(Piwik_DataTable_Row::COLUMNS => array('label' => 'yahoo',       'count' => 10)
+                                 )));
+        $filter = new Piwik_DataTable_Filter_Sort($table, 'count', 'desc');
+        $filter->filter($table);
+        $expectedOrder = array('ask' , 'yahoo' , 'nintendo');
+        $this->assertEquals($expectedOrder, $table->getColumn('label'));
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Sort
+     */
+    public function testNormalSortAscending ()
+    {
+        $table = new Piwik_DataTable();
+        $table->addRowsFromArray(array(
+                                       array(Piwik_DataTable_Row::COLUMNS => array('label' => 'ask',        'count' => 100.5)),
+                                       array(Piwik_DataTable_Row::COLUMNS => array('label' => 'nintendo',   'count' => 0.5)),
+                                       array(Piwik_DataTable_Row::COLUMNS => array('label' => 'yahoo',      'count' => 10.5)
+                                 )));
+        $filter = new Piwik_DataTable_Filter_Sort($table, 'count', 'asc');
+        $filter->filter($table);
+        $expectedOrder = array('nintendo' , 'yahoo' , 'ask');
+        $this->assertEquals($expectedOrder, $table->getColumn('label'));
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Sort
+     */
+    public function testMissingColumnValuesShouldAppearLastAfterSortAsc ()
+    {
+        $table = new Piwik_DataTable();
+        $table->addRowsFromArray(array(
+                                       array(Piwik_DataTable_Row::COLUMNS => array('label' => 'nintendo',   'count' => 1)),
+                                       array(Piwik_DataTable_Row::COLUMNS => array('label' => 'nocolumn')),
+                                       array(Piwik_DataTable_Row::COLUMNS => array('label' => 'nocolumnbis')),
+                                       array(Piwik_DataTable_Row::COLUMNS => array('label' => 'ask',        'count' => 2)),
+                                       array(Piwik_DataTable_Row::COLUMNS => array('label' => 'amazing')),
+                                       Piwik_DataTable::ID_SUMMARY_ROW => array(Piwik_DataTable_Row::COLUMNS => array('label' => 'summary' , 'count' => 10)
+                                 )));
+        $filter = new Piwik_DataTable_Filter_Sort($table, 'count', 'asc');
+        $filter->filter($table);
+        $expectedOrder = array('nintendo' , 'ask' , 'amazing' , 'nocolumnbis' , 'nocolumn' , 'summary');
+        $this->assertEquals($expectedOrder, $table->getColumn('label'));
+    }
+    
+    /**
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Sort
+     */
+    public function testMissingColumnValuesShouldAppearLastAfterSortDesc ()
+    {
+        $table = new Piwik_DataTable();
+        $table->addRowsFromArray(array(
+                                       array(Piwik_DataTable_Row::COLUMNS => array('label' => 'nintendo' , 'count' => 1)),
+                                       array(Piwik_DataTable_Row::COLUMNS => array('label' => 'ask' , 'count' => 2)),
+                                       array(Piwik_DataTable_Row::COLUMNS => array('label' => 'amazing')),
+                                       Piwik_DataTable::ID_SUMMARY_ROW => array(Piwik_DataTable_Row::COLUMNS => array('label' => 'summary' , 'count' => 10)
+                                 )));
+        $filter = new Piwik_DataTable_Filter_Sort($table, 'count', 'desc');
+        $filter->filter($table);
+        $expectedOrder = array('ask' , 'nintendo' , 'amazing' , 'summary');
+        $this->assertEquals($expectedOrder, $table->getColumn('label'));
+    }
+    
+    /**
+     * Test to sort by label
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Sort
+     */
+    public function testFilterSortString ()
+    {
+        $idcol = Piwik_DataTable_Row::COLUMNS;
+        $table = new Piwik_DataTable();
+        $rows = array(
+            array($idcol => array('label' => 'google')) , //0
+            array($idcol => array('label' => 'ask')) , //1
+            array($idcol => array('label' => 'piwik')) , //2
+            array($idcol => array('label' => 'yahoo')) , //3
+            array($idcol => array('label' => 'amazon')) , //4
+            array($idcol => array('label' => '238975247578949')) , //5
+            array($idcol => array('label' => 'Q*(%&*("$&%*(&"$*")"))')) //6
+        );
+        $table->addRowsFromArray($rows);
+        $expectedtable = new Piwik_DataTable();
+        $rows = array(
+            array($idcol => array('label' => '238975247578949')) , //5
+            array($idcol => array('label' => 'amazon')) , //4
+            array($idcol => array('label' => 'ask')) , //1
+            array($idcol => array('label' => 'google')) , //0
+            array($idcol => array('label' => 'piwik')) , //2
+            array($idcol => array('label' => 'Q*(%&*("$&%*(&"$*")"))')) , //6
+            array($idcol => array('label' => 'yahoo') )//3
+        );
+        $expectedtable->addRowsFromArray($rows);
+        $expectedtableReverse = new Piwik_DataTable();
+        $expectedtableReverse->addRowsFromArray(array_reverse($rows));
+
+        $filter = new Piwik_DataTable_Filter_Sort($table, 'label', 'asc');
+        $filter->filter($table);
+        $this->assertTrue(Piwik_DataTable::isEqual($expectedtable, $table));
+
+        $filter = new Piwik_DataTable_Filter_Sort($table, 'label', 'desc');
+        $filter->filter($table);
+        $this->assertTrue(Piwik_DataTable::isEqual($table, $expectedtableReverse));
+    }
+    
+    /**
+     * Test to sort by visit
+     * 
+     * @group Core
+     * @group DataTable
+     * @group DataTable_Filter
+     * @group DataTable_Filter_Sort
+     */
+    public function testFilterSortNumeric ()
+    {
+        $idcol = Piwik_DataTable_Row::COLUMNS;
+        $table = new Piwik_DataTable();
+        $rows = array(
+            array($idcol => array('label' => 'google',    'nb_visits' => 897)) , //0
+            array($idcol => array('label' => 'ask',       'nb_visits' => - 152)) , //1
+            array($idcol => array('label' => 'piwik',     'nb_visits' => 1.5)) , //2
+            array($idcol => array('label' => 'yahoo',     'nb_visits' => 154)) , //3
+            array($idcol => array('label' => 'amazon',    'nb_visits' => 30)) , //4
+            array($idcol => array('label' => '238949',    'nb_visits' => 0)) , //5
+            array($idcol => array('label' => 'Q*(%&*',    'nb_visits' => 1))//6
+        );
+        $table->addRowsFromArray($rows);
+        $expectedtable = new Piwik_DataTable();
+        $rows = array(
+            array($idcol => array('label' => 'ask',       'nb_visits' => - 152)) , //1
+            array($idcol => array('label' => '238949',    'nb_visits' => 0)) , //5
+            array($idcol => array('label' => 'Q*(%&*',    'nb_visits' => 1)) , //6
+            array($idcol => array('label' => 'piwik',     'nb_visits' => 1.5)) , //2
+            array($idcol => array('label' => 'amazon',    'nb_visits' => 30)) , //4
+            array($idcol => array('label' => 'yahoo',     'nb_visits' => 154)) , //3
+            array($idcol => array('label' => 'google',    'nb_visits' => 897))//0
+         );
+        $expectedtable->addRowsFromArray($rows);
+        $expectedtableReverse = new Piwik_DataTable();
+        $expectedtableReverse->addRowsFromArray(array_reverse($rows));
+
+        $filter = new Piwik_DataTable_Filter_Sort($table, 'nb_visits', 'asc');
+        $filter->filter($table);
+        $this->assertTrue(Piwik_DataTable::isEqual($table, $expectedtable));
+
+        $filter = new Piwik_DataTable_Filter_Sort($table, 'nb_visits', 'desc');
+        $filter->filter($table);
+        $this->assertTrue(Piwik_DataTable::isEqual($table, $expectedtableReverse));
+    }
+}
diff --git a/tests/PHPUnit/Core/DataTableTest.php b/tests/PHPUnit/Core/DataTableTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6714d4574a8381a590b70af7f5c2b0f699b2441a
--- /dev/null
+++ b/tests/PHPUnit/Core/DataTableTest.php
@@ -0,0 +1,711 @@
+<?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 DataTableTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * @group Core
+     * @group DataTable
+     */
+    public function testApplyFilter()
+    {
+        $table = $this->_getDataTable1ForTest();
+        $this->assertEquals(4, $table->getRowsCount());
+        $table->filter('Limit', array(2,2));
+        $this->assertEquals(2, $table->getRowsCount());
+        $table->filter('Piwik_DataTable_Filter_Limit', array(0,1));
+        $this->assertEquals(1, $table->getRowsCount());
+    }
+    
+    protected function _getSimpleTestDataTable()
+    {
+        $table = new Piwik_DataTable;
+        $table->addRowsFromArray(
+        array(
+            array(Piwik_DataTable_Row::COLUMNS => array('label'=>'ten',     'count' => 10)),
+            array(Piwik_DataTable_Row::COLUMNS => array('label'=>'ninety',  'count' => 90)),
+            array(Piwik_DataTable_Row::COLUMNS => array('label'=>'hundred', 'count' => 100)),
+            Piwik_DataTable::ID_SUMMARY_ROW => array( Piwik_DataTable_Row::COLUMNS => array('label'=> 'summary', 'count' => 200))
+            )
+        );
+        return $table;
+    }
+    
+    /**
+     * @group Core
+     * @group DataTable
+     */
+    public function testRenameColumn()
+    {
+        $table = $this->_getSimpleTestDataTable();
+        $this->assertEquals(array(10,90,100,200), $table->getColumn('count'));
+        $table->renameColumn('count', 'renamed');
+        $this->assertEquals(array(false, false, false, false), $table->getColumn('count'));
+        $this->assertEquals(array(10,90,100,200), $table->getColumn('renamed'));
+    }
+
+    /**
+     * @group Core
+     * @group DataTable
+     */
+    public function testDeleteColumn()
+    {
+        $table = $this->_getSimpleTestDataTable();
+        $this->assertEquals(array(10,90,100,200), $table->getColumn('count'));
+        $table->deleteColumn('count');
+        $this->assertEquals(array(false, false, false, false), $table->getColumn('count'));
+    }
+
+    /**
+     * @group Core
+     * @group DataTable
+     */
+    public function testDeleteRow()
+    {
+        $table = $this->_getSimpleTestDataTable();
+        
+        // normal row
+        $idToDelete = 1;
+        $this->assertEquals(2, count($table->getRowFromId($idToDelete)->getColumns()));
+        $table->deleteRow($idToDelete);
+        $this->assertFalse($table->getRowFromId($idToDelete));
+
+        // summary row special case
+        $idToDelete = Piwik_DataTable::ID_SUMMARY_ROW;
+        $this->assertEquals(2, count($table->getRowFromId($idToDelete)->getColumns()));
+        $table->deleteRow($idToDelete);
+        $this->assertFalse($table->getRowFromId($idToDelete));
+    }
+    
+    /**
+     * @group Core
+     * @group DataTable
+     */
+    public function testGetLastRow()
+    {
+        $table = $this->_getSimpleTestDataTable();
+        $rowsCount = $table->getRowsCount();
+
+        $this->assertEquals($table->getLastRow(), $table->getRowFromId(Piwik_DataTable::ID_SUMMARY_ROW));
+        $table->deleteRow(Piwik_DataTable::ID_SUMMARY_ROW);
+        
+        $this->assertEquals($table->getLastRow(), $table->getRowFromId($rowsCount-2));
+    }
+    
+    /**
+     * @group Core
+     * @group DataTable
+     */
+    public function testGetRowFromIdSubDataTable()
+    {
+        $table1 = $this->_getDataTable1ForTest();
+        $idTable1 = $table1->getId();
+        $table2 = $this->_getDataTable2ForTest();
+        $this->assertFalse($table2->getRowFromIdSubDataTable($idTable1));
+        
+        $table2->getFirstRow()->addSubtable($table1);
+        $this->assertEquals($table2->getRowFromIdSubDataTable($idTable1), $table2->getFirstRow());
+        
+        $table3 = $this->_getDataTable1ForTest();
+        $idTable3 = $table3->getId();
+        $table2->getLastRow()->addSubtable($table3);
+        $this->assertEquals($table2->getRowFromIdSubDataTable($idTable3), $table2->getLastRow());
+    }
+    
+    /**
+     * we test the count rows and the count rows recursive version
+     * on a Simple array (1 level only)
+     * 
+     * @group Core
+     * @group DataTable
+     */
+    function testCountRowsSimple()
+    {
+        
+         $table = new Piwik_DataTable;
+         $idcol = Piwik_DataTable_Row::COLUMNS;
+         $rows = array(
+              array( $idcol => array('label'=>'google')),
+              array( $idcol => array('label'=>'ask')),
+              array( $idcol => array('label'=>'piwik')),
+              array( $idcol => array('label'=>'yahoo')),
+              array( $idcol => array('label'=>'amazon')),
+              array( $idcol => array('label'=>'238975247578949')),
+              array( $idcol => array('label'=>'Q*(%&*("$&%*(&"$*")"))')));
+         
+         $table->addRowsFromArray( $rows );
+         
+         $this->assertEquals(count($rows), $table->getRowsCount());
+         $this->assertEquals(count($rows), $table->getRowsCountRecursive());
+    }
+    /**
+     * we test the count rows and the count rows recursive version
+     * on a Complex array (rows with 2 and 3 levels only)
+     * 
+     * the recursive count returns 
+     *         the sum of the number of rows of all the subtables 
+     *         + the number of rows in the parent table
+     * 
+     * @group Core
+     * @group DataTable
+     */
+    function testCountRowsComplex()
+    {
+        
+        $idcol = Piwik_DataTable_Row::COLUMNS;
+        $idsubtable = Piwik_DataTable_Row::DATATABLE_ASSOCIATED;
+        
+        // table to go in the SUB table of RoW1
+        $tableSubOfSubOfRow1 = new Piwik_DataTable;
+        $rows1sub = array(
+              array( $idcol => array('label'=>'google')),
+              array( $idcol => array('label'=>'google78')),
+              array( $idcol => array('label'=>'googlaegge')),
+              array( $idcol => array('label'=>'gogeoggle')),
+              array( $idcol => array('label'=>'goaegaegaogle')),
+              array( $idcol => array('label'=>'ask')),
+              array( $idcol => array('label'=>'238975247578949')),
+        );
+        $tableSubOfSubOfRow1->addRowsFromArray( $rows1sub );
+        
+        // table to go in row1
+        $tableSubOfRow1 = new Piwik_DataTable;
+        $rows1 = array(
+              array( $idcol => array('label'=>'google'), $idsubtable =>$tableSubOfSubOfRow1),
+              array( $idcol => array('label'=>'ask')),
+              array( $idcol => array('label'=>'238975247578949')),
+        );
+        $tableSubOfRow1->addRowsFromArray( $rows1 );
+        
+        // table to go in row2
+        $tableSubOfRow2 = new Piwik_DataTable;
+        $rows2 = array(
+              array( $idcol => array('label'=>'google')),
+              array( $idcol => array('label'=>'ask')),
+              array( $idcol => array('label'=>'238975247578949')),
+              array( $idcol => array('label'=>'agaegaesk')),
+              array( $idcol => array('label'=>'23g  8975247578949')),
+        );
+        $tableSubOfRow2->addRowsFromArray( $rows2 );
+        
+        // main parent table
+        $table = new Piwik_DataTable;
+        $rows = array(
+              array( $idcol => array('label'=>'row1')),
+              array( $idcol => array('label'=>'row2'), 
+                          $idsubtable => $tableSubOfRow1),
+              array( $idcol => array('label'=>'row3'), 
+                          $idsubtable => $tableSubOfRow2),
+        );
+        $table->addRowsFromArray( $rows );
+        
+        
+        $this->assertEquals(count($rows), $table->getRowsCount());
+        $countAllRows =  count($rows)+count($rows1)+count($rows2) + count($rows1sub);
+        $this->assertEquals($countAllRows, $table->getRowsCountRecursive());
+    }
+    
+    /**
+     * Simple test of the DataTable_Row
+     * 
+     * @group Core
+     * @group DataTable
+     */
+    function testRow()
+    {
+        $columns = array('test_column'=> 145,
+                        092582495 => new Piwik_Timer,
+                        'super'=>array('this column has an array value, amazing'));
+        $metadata = array('logo'=> 'piwik.png',
+                        'super'=>array('this column has an array value, amazing'));
+        $arrayRow = array(
+            Piwik_DataTable_Row::COLUMNS => $columns,
+            Piwik_DataTable_Row::METADATA => $metadata,
+            'fake useless key'=>38959,
+            43905724897=>'value');
+        $row = new Piwik_DataTable_Row($arrayRow);
+        
+        $this->assertEquals($columns, $row->getColumns());
+        $this->assertEquals($metadata, $row->getMetadata());
+        $this->assertNull($row->getIdSubDataTable());
+        
+    }
+    
+    /**
+     * Simple test of the DataTable_Row
+     * 
+     * @group Core
+     * @group DataTable
+     */
+    function testSumRow()
+    {
+        $columns = array('test_int'=> 145,
+                        'test_float'=> 145.5,
+                        'test_float3'=> 1.5,
+                        'test_stringint'=> "145",
+                        "test" => 'string fake',
+                        'super'=>array('this column has an array string that will be 0 when algorithm sums the value'),
+                        'integerArrayToSum'=>array( 1 => 1, 2 => 10.0, 3 => array(1 => 2, 2 => 3)),
+                        );
+        $metadata = array('logo'=> 'piwik.png',
+                        'super'=>array('this column has an array value, amazing'));
+        $arrayRow = array(
+            Piwik_DataTable_Row::COLUMNS => $columns,
+            Piwik_DataTable_Row::METADATA => $metadata,
+            'fake useless key'=>38959,
+            43905724897=>'value');
+        $row1 = new Piwik_DataTable_Row($arrayRow);
+        
+        $columns2 = array('test_int'=> 5,
+                        'test_float'=> 4.5,
+                        'test_float2'=> 14.5,
+                        'test_stringint'=> "5",
+                        0925824 => 'toto',
+                        'super'=>array('this column has geagaean array value, amazing'),
+                        'integerArrayToSum'=>array( 1 => 5, 2 => 5.5, 3 => array(2 => 4)),
+                    );
+        $finalRow = new Piwik_DataTable_Row( array(Piwik_DataTable_Row::COLUMNS => $columns2));
+        $finalRow->sumRow($row1);
+        $columnsWanted = array('test_int'=> 150,
+                        'test_float'=> 150.0,
+                        'test_float2'=> 14.5,
+                        'test_float3'=> 1.5,
+                        'test_stringint'=> 150, //add also strings!!
+                        'super'=>array(0),
+                        'test' => 0,
+                        'integerArrayToSum' => array( 1 => 6, 2 => 15.5, 3 => array(1 => 2, 2 => 7)),
+                        0925824 => 'toto',
+                );
+        $rowWanted = new Piwik_DataTable_Row( array(Piwik_DataTable_Row::COLUMNS => $columnsWanted));
+        $this->assertTrue( Piwik_DataTable_Row::isEqual($rowWanted, $finalRow));
+    }
+    
+    /**
+     * Test serialize with an infinite recursion (a row linked to a table in the parent hierarchy)
+     * After 100 recursion must throw an exception
+     * 
+     * @group Core
+     * @group DataTable
+     * @expectedException Exception
+     */
+    function testSerializeWithInfiniteRecursion()
+    {
+        $table = new Piwik_DataTable;
+        $table->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array( 'visits'=>245,'visitors'=>245),
+                              Piwik_DataTable_Row::DATATABLE_ASSOCIATED => $table,));
+                              
+        $table->getSerialized();
+    }
+    
+    
+    /**
+     * Test queing filters
+     * 
+     * @group Core
+     * @group DataTable
+     */
+     function testFilterQueueSortString()
+     {
+        
+        $idcol = Piwik_DataTable_Row::COLUMNS;
+        
+        $table = new Piwik_DataTable;
+        $rows = array(
+              array( $idcol => array('label'=>'google')),//0
+              array( $idcol => array('label'=>'tsk')),//1
+              array( $idcol => array('label'=>'Q*(%&*("$&%*(&"$*")"))')),//2
+              );
+        $table->addRowsFromArray( $rows );
+        
+        $expectedtable = new Piwik_DataTable;
+        $rows = array(
+              array( $idcol => array('label'=>'google')),//0
+              array( $idcol => array('label'=>'Q*(%&*("$&%*(&"$*")"))')),//2
+              array( $idcol => array('label'=>'tsk')),//1
+              );
+        $expectedtable->addRowsFromArray( $rows );
+        
+        $expectedtableReverse = new Piwik_DataTable;
+        $expectedtableReverse->addRowsFromArray(array_reverse($rows));
+        
+        $tableCopy = clone $table;
+        $this->assertTrue(Piwik_DataTable::isEqual($tableCopy, $table));
+        
+        // queue the filter and check the table didnt change
+        $table->queueFilter("Sort", array('label', 'asc'));
+        $this->assertTrue(Piwik_DataTable::isEqual($tableCopy, $table));
+        
+        // apply filter and check the table is sorted
+        $table->applyQueuedFilters();
+        $this->assertTrue(Piwik_DataTable::isEqual($expectedtable, $table));
+        
+        // apply one more filter check it hasnt changed
+        $table->queueFilter("Sort", array('label', 'desc'));
+        $this->assertTrue(Piwik_DataTable::isEqual($expectedtable, $table));
+        
+        // now apply the second sort and check it is correctly sorted
+        $table->applyQueuedFilters();
+        $this->assertTrue(Piwik_DataTable::isEqual($expectedtableReverse, $table));
+        
+        // do one more time to make sure it doesnt change
+        $table->applyQueuedFilters();
+        $this->assertTrue(Piwik_DataTable::isEqual($expectedtableReverse, $table));
+     }
+    
+    
+
+    /**
+     * General tests that tries to test the normal behaviour of DataTable
+     * 
+     * We create some tables, add rows, some of the rows link to sub tables
+     * 
+     * Then we serialize everything, and we check that the unserialize give the same object back
+     * 
+     * @group Core
+     * @group DataTable
+     */
+    function testGeneral()
+    {
+        /*
+         * create some fake tables to make sure that the serialized array of the first TABLE
+         * does not take in consideration those tables
+         */
+        $useless1 = new Piwik_DataTable;
+        $useless1->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array( 13,),));
+        /*
+         * end fake tables
+         */
+        
+        /*
+         * MAIN TABLE
+         */
+        $table = new Piwik_DataTable;
+        $subtable = new Piwik_DataTable;
+        $idtable = $table->getId();
+        $idsubtable = $subtable->getId();
+        
+        /*
+         * create some fake tables to make sure that the serialized array of the first TABLE
+         * does not take in consideration those tables
+         * -> we check that the DataTable_Manager is not impacting DataTable 
+         */
+        $useless2 = new Piwik_DataTable;
+        $useless1->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array( 8487,),));
+        $useless3 = new Piwik_DataTable;
+        $useless3->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array( 8487,),));
+        /*
+         * end fake tables
+         */
+        
+        $row = array(Piwik_DataTable_Row::COLUMNS => array( 0 => 1554,    1 => 42,    2 => 657,3 => 155744,),
+                      Piwik_DataTable_Row::METADATA => array('logo' => 'test.png'));
+        $row = new Piwik_DataTable_Row($row);
+        
+        $table->addRow($row);
+        $table->addRowFromArray(array( Piwik_DataTable_Row::COLUMNS => array( 0 => 1554,1 => 42,),
+                              Piwik_DataTable_Row::METADATA => array('url' => 'piwik.org')));
+        
+        $table->addRowFromArray(array( Piwik_DataTable_Row::COLUMNS => array( 0 => 787877888787,),
+                              Piwik_DataTable_Row::METADATA => array('url' => 'OUPLA ADDED'),
+                              Piwik_DataTable_Row::DATATABLE_ASSOCIATED => $subtable));
+              
+        /*
+         * SUB TABLE
+         */                
+        
+        
+        $row = array(         Piwik_DataTable_Row::COLUMNS => array( 0 => 1554,),
+                              Piwik_DataTable_Row::METADATA => array('searchengine' => 'google'),
+                          );
+        $subtable->addRowFromArray($row);
+        
+        $row = array(         Piwik_DataTable_Row::COLUMNS => array( 0 => 84894,),
+                              Piwik_DataTable_Row::METADATA => array('searchengine' => 'yahoo'),
+                          );
+        $subtable->addRowFromArray($row);
+        $row = array(         Piwik_DataTable_Row::COLUMNS => array( 0 => 4898978989,),
+                              Piwik_DataTable_Row::METADATA => array('searchengine' => 'ask'),
+                          );          
+        $subtable->addRowFromArray($row);
+        
+        
+        /*
+         * SUB SUB TABLE
+         */
+        $subsubtable = new Piwik_DataTable;
+        $subsubtable->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array( 245),
+                              Piwik_DataTable_Row::METADATA => array('yes' => 'subsubmetadata1'),)
+                              );      
+                              
+        $subsubtable->addRowFromArray(array(Piwik_DataTable_Row::COLUMNS => array( 13,),
+                              Piwik_DataTable_Row::METADATA => array('yes' => 'subsubmetadata2'),)
+                              );
+                              
+        $row = array(     Piwik_DataTable_Row::COLUMNS => array( 0 => 666666666666666,),
+                              Piwik_DataTable_Row::METADATA => array('url' => 'NEW ROW ADDED'),
+                              Piwik_DataTable_Row::DATATABLE_ASSOCIATED => $subsubtable);
+        
+        $subtable->addRowFromArray($row);
+        
+        $idsubsubtable = $subsubtable->getId();
+        
+        $serialized = ($table->getSerialized());
+        
+        $this->assertEquals(array_keys($serialized), array($idsubsubtable,$idsubtable,0));
+        $tableAfter = new Piwik_DataTable;
+        $tableAfter->addRowsFromSerializedArray($serialized[0]);
+        $this->assertEquals($table->getRows(),$tableAfter->getRows());
+
+        $subsubtableAfter = new Piwik_DataTable;
+        $subsubtableAfter->addRowsFromSerializedArray($serialized[$idsubsubtable]);
+        $this->assertEquals($subsubtable->getRows(),$subsubtableAfter->getRows());
+        
+        $this->assertEquals($table, Piwik_DataTable_Manager::getInstance()->getTable($idtable));
+        $this->assertEquals($subsubtable, Piwik_DataTable_Manager::getInstance()->getTable($idsubsubtable));
+    }
+    
+    /**
+     * for all datatable->addDatatable tests we check that
+     * - row uniqueness is based on the label + presence of the SUBTABLE id
+     *         => the label is the criteria used to match 2 rows in 2 datatable
+     * - no metadata are lost in the first datatable rows that have been changed
+     * - when a subtable
+     */
+     
+     
+    /**
+     * add an empty datatable to a normal datatable
+     * 
+     * @group Core
+     * @group DataTable
+     */
+    public function testAddSimpleNoRowTable2()
+    {
+        $table = $this->_getDataTable1ForTest();
+        $tableEmpty = new Piwik_DataTable;
+        $tableAfter = clone $table;
+        $tableAfter->addDataTable($tableEmpty);
+        $this->assertTrue( Piwik_DataTable::isEqual($table, $tableAfter) );
+    }
+    
+    /**
+     * add a normal datatable to an empty datatable
+     * 
+     * @group Core
+     * @group DataTable
+     */
+    public function testAddSimpleNoRowTable1()
+    {
+        $table = $this->_getDataTable1ForTest();
+        $tableEmpty = new Piwik_DataTable;
+        $tableEmpty->addDataTable($table);
+        $this->assertTrue( Piwik_DataTable::isEqual($tableEmpty, $table) );
+    }
+
+    /**
+     * add to the datatable another datatable// they don't have any row in common
+     * 
+     * @group Core
+     * @group DataTable
+     */
+    public function testAddSimpleNoCommonRow()
+    {
+        $table1 = $this->_getDataTable1ForTest();
+        $table2 = $this->_getDataTable2ForTest();
+        
+        $table1->addDataTable($table2);
+        
+        $rowsExpected = array_merge($this->_getRowsDataTable1ForTest(),$this->_getRowsDataTable2ForTest());
+        $tableExpected = new Piwik_DataTable;
+        $tableExpected->addRowsFromArray( $rowsExpected );
+        
+        $this->assertTrue( Piwik_DataTable::isEqual($table1, $tableExpected) );
+    }
+    
+    /**
+     * add 2 datatable with some common rows
+     * 
+     * @group Core
+     * @group DataTable
+     */
+    public function testAddSimpleSomeCommonRow()
+    {
+        
+        $idcol = Piwik_DataTable_Row::COLUMNS;
+        
+        $rows = array(
+              array( $idcol => array('label'=>'google', 'visits' => 1)),
+              array( $idcol => array('label'=>'ask', 'visits' => 2)),
+              array( $idcol => array('label'=>'123', 'visits' => 2)),
+              Piwik_DataTable::ID_SUMMARY_ROW => array( $idcol => array('label'=>Piwik_DataTable::LABEL_SUMMARY_ROW, 'visits' => 7))
+        );          
+        $table = new Piwik_DataTable;
+        $table->addRowsFromArray( $rows );
+        
+        $rows2 = array(
+              array( $idcol => array('label'=>'test', 'visits' => 1)),
+              array( $idcol => array('label'=>'ask', 'visits' => 111)),
+              array( $idcol => array('label'=>' google ', 'visits' => 5)),
+              array( $idcol => array('label'=>'123', 'visits' => 2)),
+          );          
+        $table2 = new Piwik_DataTable;
+        $table2->addRowsFromArray( $rows2 );
+        
+        $table->addDataTable($table2);
+        
+        $rowsExpected = array(
+              array( $idcol => array('label'=>'google', 'visits' => 1)),
+              array( $idcol => array('label'=>'ask', 'visits' => 113)),
+              array( $idcol => array('label'=>'123', 'visits' => 4)),
+              array( $idcol => array('label'=>'test', 'visits' => 1)),
+              array( $idcol => array('label'=>' google ', 'visits' => 5)),
+              Piwik_DataTable::ID_SUMMARY_ROW => array( $idcol  => array('label'=>Piwik_DataTable::LABEL_SUMMARY_ROW, 'visits' => 7))
+        );          
+        $tableExpected = new Piwik_DataTable;
+        $tableExpected->addRowsFromArray( $rowsExpected );
+        
+        $this->assertTrue( Piwik_DataTable::isEqual($table, $tableExpected) );
+    }
+    
+    /**
+     * add 2 datatable with only common rows
+     * 
+     * @group Core
+     * @group DataTable
+     */
+    public function testAddSimpleAllCommonRow()
+    {
+        $idcol = Piwik_DataTable_Row::COLUMNS;
+        
+        $rows = array(
+              array( $idcol => array('label'=>'google', 'visits' => 1)),
+              array( $idcol => array('label'=>'ask', 'visits' => 2)),
+              array( $idcol => array('label'=>'123', 'visits' => 2)),
+              Piwik_DataTable::ID_SUMMARY_ROW => array( $idcol  => array('label'=>Piwik_DataTable::LABEL_SUMMARY_ROW, 'visits' => 7))
+        );          
+        $table = new Piwik_DataTable;
+        $table->addRowsFromArray( $rows );
+        
+        $rows2 = array(
+              array( $idcol => array('label'=>'google', 'visits' => -1)),
+              array( $idcol => array('label'=>'ask', 'visits' => 0)),
+              array( $idcol => array('label'=>'123', 'visits' => 1.5)),
+              Piwik_DataTable::ID_SUMMARY_ROW => array( $idcol  => array('label'=>Piwik_DataTable::LABEL_SUMMARY_ROW, 'visits' => 8))
+        );          
+        $table2 = new Piwik_DataTable;
+        $table2->addRowsFromArray( $rows2 );
+        
+        $table->addDataTable($table2);
+        
+        $rowsExpected = array(
+              array( $idcol => array('label'=>'google', 'visits' => 0)),
+              array( $idcol => array('label'=>'ask', 'visits' => 2)),
+              array( $idcol => array('label'=>'123', 'visits' => 3.5)),
+              Piwik_DataTable::ID_SUMMARY_ROW => array( $idcol  => array('label'=>Piwik_DataTable::LABEL_SUMMARY_ROW, 'visits' => 15))
+        );          
+        $tableExpected = new Piwik_DataTable;
+        $tableExpected->addRowsFromArray( $rowsExpected );
+        
+        $this->assertTrue( Piwik_DataTable::isEqual($table, $tableExpected) );
+    }
+    
+    /**
+     * test add 2 different tables to the same table
+     * 
+     * @group Core
+     * @group DataTable
+     */
+    public function testAddDataTable2times()
+    {
+     
+        $idcol = Piwik_DataTable_Row::COLUMNS;
+        
+        $rows = array(
+              array( $idcol => array('label'=>'google', 'visits' => 1)),
+              array( $idcol => array('label'=>'ask', 'visits' => 0)),
+              array( $idcol => array('label'=>'123', 'visits' => 2)),
+              Piwik_DataTable::ID_SUMMARY_ROW => array( $idcol  => array('label'=>Piwik_DataTable::LABEL_SUMMARY_ROW, 'visits' => 1))
+        );          
+        $table = new Piwik_DataTable;
+        $table->addRowsFromArray( $rows );
+        
+        $rows2 = array(
+              array( $idcol => array('label'=>'google2', 'visits' => -1)),
+              array( $idcol => array('label'=>'ask', 'visits' => 100)),
+              array( $idcol => array('label'=>'123456', 'visits' => 1.5)),
+        );          
+        $table2 = new Piwik_DataTable;
+        $table2->addRowsFromArray( $rows2 );
+        
+        $rows3 = array(
+              array( $idcol => array('label'=>'google2', 'visits' => -1)),
+              array( $idcol => array('label'=>'ask', 'visits' => -10)),
+              array( $idcol => array('label'=>'123ab', 'visits' => 1.5)),
+              Piwik_DataTable::ID_SUMMARY_ROW => array( $idcol  => array('label'=>Piwik_DataTable::LABEL_SUMMARY_ROW, 'visits' => 3))
+        );          
+        $table3 = new Piwik_DataTable;
+        $table3->addRowsFromArray( $rows3 );
+        
+        // add the 2 tables
+        $table->addDataTable($table2);
+        $table->addDataTable($table3);
+        
+        $rowsExpected = array(
+              array( $idcol => array('label'=>'google', 'visits' => 1)),
+              array( $idcol => array('label'=>'ask', 'visits' => 90)),
+              array( $idcol => array('label'=>'123', 'visits' => 2)),
+              array( $idcol => array('label'=>'google2', 'visits' => -2)),
+              array( $idcol => array('label'=>'123456', 'visits' => 1.5)),
+              array( $idcol => array('label'=>'123ab', 'visits' => 1.5)),
+              Piwik_DataTable::ID_SUMMARY_ROW => array( $idcol  => array('label'=>Piwik_DataTable::LABEL_SUMMARY_ROW, 'visits' => 4))
+        );
+        $tableExpected = new Piwik_DataTable;
+        $tableExpected->addRowsFromArray( $rowsExpected );
+        
+        $this->assertTrue( Piwik_DataTable::isEqual($table, $tableExpected) );
+    }
+    
+    protected function _getDataTable1ForTest()
+    {
+        $rows = $this->_getRowsDataTable1ForTest();
+        $table = new Piwik_DataTable;
+        $table->addRowsFromArray( $rows );
+        return $table;
+    }
+
+    protected function _getDataTable2ForTest()
+    {
+        $rows = $this->_getRowsDataTable2ForTest();    
+        $table = new Piwik_DataTable;
+        $table->addRowsFromArray( $rows );
+        return $table;
+    }
+    
+    protected function _getRowsDataTable1ForTest()
+    {
+        $rows = array(
+              array( Piwik_DataTable_Row::COLUMNS => array('label'=>'google', 'visits' => 1)),
+              array( Piwik_DataTable_Row::COLUMNS => array('label'=>'ask', 'visits' => 2)),
+              array( Piwik_DataTable_Row::COLUMNS => array('label'=>'123', 'visits' => 2)),
+              Piwik_DataTable::ID_SUMMARY_ROW => array( Piwik_DataTable_Row::COLUMNS  => array('label'=>Piwik_DataTable::LABEL_SUMMARY_ROW, 'visits' => 4))
+              
+        );
+        return $rows;
+    }
+
+    protected function _getRowsDataTable2ForTest()
+    {
+        $rows = array(
+              array( Piwik_DataTable_Row::COLUMNS => array('label'=>'test', 'visits' => 1)),
+              array( Piwik_DataTable_Row::COLUMNS => array('label'=>' google ', 'visits' => 3)),
+              array( Piwik_DataTable_Row::COLUMNS => array('label'=>'123a', 'visits' => 2)),
+        );
+        return $rows;
+    }
+
+}
diff --git a/tests/PHPUnit/Core/TranslationWriterTest.php b/tests/PHPUnit/Core/TranslationWriterTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c00147f3cd99a20b14400873a442ba9e6998e7d7
--- /dev/null
+++ b/tests/PHPUnit/Core/TranslationWriterTest.php
@@ -0,0 +1,172 @@
+<?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 TranslationWriterTest extends PHPUnit_Framework_TestCase
+{
+    /**
+     * Dataprovider for testClean
+     */
+    public function getCleanTestData()
+    {
+        return array(
+            // empty string
+            array("", ''),
+            // newline
+            array("\n", ''),
+            // leading and trailing whitespace
+            array(" a \n", 'a'),
+            // single / double quotes
+            array(" &quot;it&#039;s&quot; ", '"it\'s"'),
+            // html special characters
+            array("&lt;tag&gt;", '<tag>'),
+            // other html entities
+            array("&hellip;", '…'),
+        );
+    }
+
+    /**
+     * @group Core
+     * @group TranslationWriter
+     * @dataProvider getCleanTestData
+     */
+    public function testClean($data, $expected)
+    {
+        $this->assertEquals($expected, Piwik_TranslationWriter::clean($data));
+    }
+
+    /**
+     * Dataprovider for testQuote
+     */
+    public function getQuoteTestData()
+    {
+        return array(
+            // alphanumeric
+            array('abc 123', "'abc 123'"),
+            // newline
+            array("\n", "'
+'"),
+            array('
+', "'
+'"),
+            // tab
+            array('	', "'	'"),
+            // single quote
+            array("it's", "'it\'s'"),
+        );
+    }
+
+    /**
+     * @group Core
+     * @group TranslationWriter
+     * @dataProvider getQuoteTestData
+     */
+    public function testQuote($data, $expected)
+    {
+        if(Piwik_Common::isWindows() && $data == "\n")
+        {
+            return;
+        }
+        $this->assertEquals($expected, Piwik_TranslationWriter::quote($data));
+    }
+
+    /**
+     * @group Core
+     * @group TranslationWriter
+     * @expectedException Exception
+     */
+    public function testGetTranslationPathInvalidLang()
+    {
+        $path = Piwik_TranslationWriter::getTranslationPath('../index');
+    }
+    
+    /**
+     * @group Core
+     * @group TranslationWriter
+     * @expectedException Exception
+     */
+    public function testGetTranslationPathInvalidBasePath()
+    {
+        $path = Piwik_TranslationWriter::getTranslationPath('en', 'core');
+    }
+    
+    /**
+     * @group Core
+     * @group TranslationWriter
+     */
+    public function testGetTranslationPath()
+    {
+        // implicit base path
+        $this->assertEquals(PIWIK_INCLUDE_PATH . '/lang/en.php', Piwik_TranslationWriter::getTranslationPath('en'));
+
+        // explicit base path
+        $this->assertEquals(PIWIK_INCLUDE_PATH . '/lang/en.php', Piwik_TranslationWriter::getTranslationPath('en', 'lang'));
+        $this->assertEquals(PIWIK_INCLUDE_PATH . '/tmp/en.php', Piwik_TranslationWriter::getTranslationPath('en', 'tmp'));
+    }
+
+    /**
+     * @group Core
+     * @group TranslationWriter
+     * @expectedException Exception
+     */
+    public function testLoadTranslationInvalidLang()
+    {
+        $translations = Piwik_TranslationWriter::loadTranslation('a');
+    }
+    
+    /**
+     * @group Core
+     * @group TranslationWriter
+     */
+    public function testLoadTranslation()
+    {
+        require PIWIK_INCLUDE_PATH . '/lang/en.php';
+        $this->assertTrue(is_array($translations));
+
+        $englishTranslations = Piwik_TranslationWriter::loadTranslation('en');
+
+        $this->assertEquals(count($translations), count($englishTranslations));
+        $this->assertEquals(0, count(array_diff($translations, $englishTranslations)));
+        $this->assertEquals(0, count(array_diff_assoc($translations, $englishTranslations)));
+    }
+
+    /**
+     * @group Core
+     * @group TranslationWriter
+     */
+    public function testSaveTranslation()
+    {
+        $path = Piwik_TranslationWriter::getTranslationPath('en', 'tmp');
+
+        $translations = array(
+            'General_Locale' => 'en_CA.UTF-8',
+            'General_Id' => 'Id',
+            'Goals_Goals' => 'Goals',
+            'Plugin_Body' => "Message\nBody",
+        );
+
+        @unlink($path);
+
+        $rc = Piwik_TranslationWriter::saveTranslation($translations, $path);
+        $this->assertNotEquals(false, $rc);
+
+        $contents = file_get_contents($path);
+        $expected = "<?php
+\$translations = array(
+\t'General_Locale' => 'en_CA.UTF-8',
+\t'General_Id' => 'Id',
+\t'Goals_Goals' => 'Goals',
+
+\t// FOR REVIEW
+\t'Plugin_Body' => 'Message
+Body',
+);
+";
+        if(Piwik_Common::isWindows()) $expected = str_replace("\r\n", "\n", $expected);
+        $this->assertEquals($expected, $contents);
+    }
+}