Merge pull request #2026 from johnathancroom/keep_flash_data_array

keep_flashdata accepts array
diff --git a/.travis.yml b/.travis.yml
index a1635ea..070b23c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,6 +6,7 @@
   
 env:
   - DB=mysql
+  - DB=mysqli
   - DB=pgsql
   - DB=sqlite
   - DB=pdo/mysql
@@ -17,7 +18,7 @@
   - php composer.phar install
   - sh -c "if [ '$DB' = 'pgsql' ] || [ '$DB' = 'pdo/pgsql' ]; then psql -c 'DROP DATABASE IF EXISTS ci_test;' -U postgres; fi"
   - sh -c "if [ '$DB' = 'pgsql' ] || [ '$DB' = 'pdo/pgsql' ]; then psql -c 'create database ci_test;' -U postgres; fi"
-  - sh -c "if [ '$DB' = 'mysql' ] || [ '$DB' = 'pdo/mysql' ]; then mysql -e 'create database IF NOT EXISTS ci_test;'; fi"
+  - sh -c "if [ '$DB' = 'mysql' ] || [ '$DB' = 'mysqli' ] || [ '$DB' = 'pdo/mysql' ]; then mysql -e 'create database IF NOT EXISTS ci_test;'; fi"
 
 script: phpunit --coverage-text --configuration tests/travis/$DB.phpunit.xml
 
diff --git a/system/core/Loader.php b/system/core/Loader.php
index 1e6eafe..6515074 100644
--- a/system/core/Loader.php
+++ b/system/core/Loader.php
@@ -669,6 +669,12 @@
 			return FALSE;
 		}
 
+		if ( ! class_exists('CI_Driver_Library'))
+		{
+			// We aren't instantiating an object here, just making the base class available
+			require BASEPATH.'libraries/Driver.php';
+		}
+
 		// We can save the loader some time since Drivers will *always* be in a subfolder,
 		// and typically identically named to the library
 		if ( ! strpos($library, '/'))
@@ -949,13 +955,6 @@
 
 			// Get the filename from the path
 			$class = substr($class, $last_slash);
-
-			// Check for match and driver base class
-			if (strtolower(trim($subdir, '/')) == strtolower($class) && ! class_exists('CI_Driver_Library'))
-			{
-				// We aren't instantiating an object here, just making the base class available
-				require BASEPATH.'libraries/Driver.php';
-			}
 		}
 
 		// We'll test for both lowercase and capitalized versions of the file name
diff --git a/system/database/DB_forge.php b/system/database/DB_forge.php
index bb1ee48..59c3baf 100644
--- a/system/database/DB_forge.php
+++ b/system/database/DB_forge.php
@@ -402,7 +402,7 @@
 		// Are indexes created from within the CREATE TABLE statement? (e.g. in MySQL)
 		if ($this->_create_table_keys === TRUE)
 		{
-			$columns .= $this->_process_indexes();
+			$columns .= $this->_process_indexes($table);
 		}
 
 		// _create_table will usually have the following format: "%s %s (%s\n)"
@@ -962,14 +962,25 @@
 	 * @param	string	$table
 	 * @return	string
 	 */
-	protected function _process_indexes($table = NULL)
+	protected function _process_indexes($table)
 	{
 		$table = $this->db->escape_identifiers($table);
 		$sqls = array();
 
 		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
 		{
-			if ( ! isset($this->fields[$this->keys[$i]]))
+			if (is_array($this->keys[$i]))
+			{
+				for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+				{
+					if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+					{
+						unset($this->keys[$i][$i2]);
+						continue;
+					}
+				}
+			}
+			elseif ( ! isset($this->fields[$this->keys[$i]]))
 			{
 				unset($this->keys[$i]);
 				continue;
diff --git a/system/database/DB_utility.php b/system/database/DB_utility.php
index a32fd44..c4140ae 100644
--- a/system/database/DB_utility.php
+++ b/system/database/DB_utility.php
@@ -146,7 +146,7 @@
 		if ($query !== FALSE)
 		{
 			$query = $query->result_array();
-			return current($res);
+			return current($query);
 		}
 
 		return FALSE;
diff --git a/system/database/drivers/cubrid/cubrid_forge.php b/system/database/drivers/cubrid/cubrid_forge.php
index 9a7cdb8..2a737c5 100644
--- a/system/database/drivers/cubrid/cubrid_forge.php
+++ b/system/database/drivers/cubrid/cubrid_forge.php
@@ -179,13 +179,24 @@
 	 * @param	string	$table	(ignored)
 	 * @return	string
 	 */
-	protected function _process_indexes($table = NULL)
+	protected function _process_indexes($table)
 	{
 		$sql = '';
 
 		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
 		{
-			if ( ! isset($this->fields[$this->keys[$i]]))
+			if (is_array($this->keys[$i]))
+			{
+				for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+				{
+					if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+					{
+						unset($this->keys[$i][$i2]);
+						continue;
+					}
+				}
+			}
+			elseif ( ! isset($this->fields[$this->keys[$i]]))
 			{
 				unset($this->keys[$i]);
 				continue;
diff --git a/system/database/drivers/mysql/mysql_forge.php b/system/database/drivers/mysql/mysql_forge.php
index 1e9145e..f4394c6 100644
--- a/system/database/drivers/mysql/mysql_forge.php
+++ b/system/database/drivers/mysql/mysql_forge.php
@@ -175,13 +175,24 @@
 	 * @param	string	$table	(ignored)
 	 * @return	string
 	 */
-	protected function _process_indexes($table = NULL)
+	protected function _process_indexes($table)
 	{
 		$sql = '';
 
 		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
 		{
-			if ( ! isset($this->fields[$this->keys[$i]]))
+			if (is_array($this->keys[$i]))
+			{
+				for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+				{
+					if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+					{
+						unset($this->keys[$i][$i2]);
+						continue;
+					}
+				}
+			}
+			elseif ( ! isset($this->fields[$this->keys[$i]]))
 			{
 				unset($this->keys[$i]);
 				continue;
diff --git a/system/database/drivers/mysqli/mysqli_forge.php b/system/database/drivers/mysqli/mysqli_forge.php
index 1a6e284..f29ea5a 100644
--- a/system/database/drivers/mysqli/mysqli_forge.php
+++ b/system/database/drivers/mysqli/mysqli_forge.php
@@ -175,13 +175,24 @@
 	 * @param	string	$table	(ignored)
 	 * @return	string
 	 */
-	protected function _process_indexes($table = NULL)
+	protected function _process_indexes($table)
 	{
 		$sql = '';
 
 		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
 		{
-			if ( ! isset($this->fields[$this->keys[$i]]))
+			if (is_array($this->keys[$i]))
+			{
+				for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+				{
+					if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+					{
+						unset($this->keys[$i][$i2]);
+						continue;
+					}
+				}
+			}
+			elseif ( ! isset($this->fields[$this->keys[$i]]))
 			{
 				unset($this->keys[$i]);
 				continue;
diff --git a/system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php b/system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php
index cb7d9e6..ebf4b15 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_cubrid_forge.php
@@ -179,13 +179,24 @@
 	 * @param	string	$table	(ignored)
 	 * @return	string
 	 */
-	protected function _process_indexes($table = NULL)
+	protected function _process_indexes($table)
 	{
 		$sql = '';
 
 		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
 		{
-			if ( ! isset($this->fields[$this->keys[$i]]))
+			if (is_array($this->keys[$i]))
+			{
+				for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+				{
+					if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+					{
+						unset($this->keys[$i][$i2]);
+						continue;
+					}
+				}
+			}
+			elseif ( ! isset($this->fields[$this->keys[$i]]))
 			{
 				unset($this->keys[$i]);
 				continue;
diff --git a/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php b/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php
index 85d9445..5a6de6b 100644
--- a/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php
+++ b/system/database/drivers/pdo/subdrivers/pdo_mysql_forge.php
@@ -189,13 +189,24 @@
 	 * @param	string	$table	(ignored)
 	 * @return	string
 	 */
-	protected function _process_indexes($table = NULL)
+	protected function _process_indexes($table)
 	{
 		$sql = '';
 
 		for ($i = 0, $c = count($this->keys); $i < $c; $i++)
 		{
-			if ( ! isset($this->fields[$this->keys[$i]]))
+			if (is_array($this->keys[$i]))
+			{
+				for ($i2 = 0, $c2 = count($this->keys[$i]); $i2 < $c2; $i2++)
+				{
+					if ( ! isset($this->fields[$this->keys[$i][$i2]]))
+					{
+						unset($this->keys[$i][$i2]);
+						continue;
+					}
+				}
+			}
+			elseif ( ! isset($this->fields[$this->keys[$i]]))
 			{
 				unset($this->keys[$i]);
 				continue;
diff --git a/system/libraries/Cache/Cache.php b/system/libraries/Cache/Cache.php
index e76fdc5..48bd958 100644
--- a/system/libraries/Cache/Cache.php
+++ b/system/libraries/Cache/Cache.php
@@ -43,12 +43,12 @@
 	 * @var array
 	 */
 	protected $valid_drivers = array(
-		'cache_apc',
-		'cache_dummy',
-		'cache_file',
-		'cache_memcached',
-		'cache_redis',
-		'cache_wincache'
+		'apc',
+		'dummy',
+		'file',
+		'memcached',
+		'redis',
+		'wincache'
 	);
 
 	/**
diff --git a/system/libraries/Driver.php b/system/libraries/Driver.php
index 621d226..72f3764 100644
--- a/system/libraries/Driver.php
+++ b/system/libraries/Driver.php
@@ -60,8 +60,8 @@
 	 * The first time a child is used it won't exist, so we instantiate it
 	 * subsequents calls will go straight to the proper child.
 	 *
-	 * @param   string  Child class name
-	 * @return  object  Child class
+	 * @param	string	Child class name
+	 * @return	object	Child class
 	 */
 	public function __get($child)
 	{
@@ -74,61 +74,102 @@
 	 *
 	 * Separate load_driver call to support explicit driver load by library or user
 	 *
-	 * @param   string  Child class name
-	 * @return  object  Child class
+	 * @param	string	Driver name (w/o parent prefix)
+	 * @return	object	Child class
 	 */
 	public function load_driver($child)
 	{
+		// Get CodeIgniter instance and subclass prefix
+		$CI = get_instance();
+		$prefix = (string) $CI->config->item('subclass_prefix');
+
 		if ( ! isset($this->lib_name))
 		{
-			$this->lib_name = get_class($this);
+			// Get library name without any prefix
+			$this->lib_name = str_replace(array('CI_', $prefix), '', get_class($this));
 		}
 
-		// The class will be prefixed with the parent lib
-		$child_class = $this->lib_name.'_'.$child;
+		// The child will be prefixed with the parent lib
+		$child_name = $this->lib_name.'_'.$child;
 
-		// Remove the CI_ prefix and lowercase
-		$lib_name = ucfirst(strtolower(str_replace('CI_', '', $this->lib_name)));
-		$driver_name = strtolower(str_replace('CI_', '', $child_class));
-
-		if (in_array($driver_name, array_map('strtolower', $this->valid_drivers)))
+		// See if requested child is a valid driver
+		if ( ! in_array($child, $this->valid_drivers))
 		{
-			// check and see if the driver is in a separate file
-			if ( ! class_exists($child_class))
+			// The requested driver isn't valid!
+			$msg = 'Invalid driver requested: '.$child_name;
+			log_message('error', $msg);
+			show_error($msg);
+		}
+
+		// Get package paths and filename case variations to search
+		$paths = $CI->load->get_package_paths(TRUE);
+
+		// Is there an extension?
+		$class_name = $prefix.$child_name;
+		$found = class_exists($class_name);
+		if ( ! $found)
+		{
+			// Check for subclass file
+			foreach ($paths as $path)
 			{
-				// check application path first
-				foreach (get_instance()->load->get_package_paths(TRUE) as $path)
+				// Does the file exist?
+				$file = $path.'libraries/'.$this->lib_name.'/drivers/'.$prefix.$child_name.'.php';
+				if (file_exists($file))
 				{
-					// loves me some nesting!
-					foreach (array(ucfirst($driver_name), $driver_name) as $class)
+					// Yes - require base class from BASEPATH
+					$basepath = BASEPATH.'libraries/'.$this->lib_name.'/drivers/'.$child_name.'.php';
+					if ( ! file_exists($basepath))
 					{
-						$filepath = $path.'libraries/'.$lib_name.'/drivers/'.$class.'.php';
-
-						if (file_exists($filepath))
-						{
-							include_once $filepath;
-							break 2;
-						}
+						$msg = 'Unable to load the requested class: CI_'.$child_name;
+						log_message('error', $msg);
+						show_error($msg);
 					}
-				}
 
-				// it's a valid driver, but the file simply can't be found
-				if ( ! class_exists($child_class))
-				{
-					log_message('error', 'Unable to load the requested driver: '.$child_class);
-					show_error('Unable to load the requested driver: '.$child_class);
+					// Include both sources and mark found
+					include($basepath);
+					include($file);
+					$found = TRUE;
+					break;
 				}
 			}
-
-			$obj = new $child_class;
-			$obj->decorate($this);
-			$this->$child = $obj;
-			return $this->$child;
 		}
 
-		// The requested driver isn't valid!
-		log_message('error', 'Invalid driver requested: '.$child_class);
-		show_error('Invalid driver requested: '.$child_class);
+		// Do we need to search for the class?
+		if ( ! $found)
+		{
+			// Use standard class name
+			$class_name = 'CI_'.$child_name;
+			$found = class_exists($class_name);
+			if ( ! $found)
+			{
+				// Check package paths
+				foreach ($paths as $path)
+				{
+					// Does the file exist?
+					$file = $path.'libraries/'.$this->lib_name.'/drivers/'.$child_name.'.php';
+					if (file_exists($file))
+					{
+						// Include source
+						include($file);
+						break;
+					}
+				}
+			}
+		}
+
+		// Did we finally find the class?
+		if ( ! class_exists($class_name))
+		{
+			$msg = 'Unable to load the requested driver: '.$class_name;
+			log_message('error', $msg);
+			show_error($msg);
+		}
+
+		// Instantiate, decorate and add child
+		$obj = new $class_name();
+		$obj->decorate($this);
+		$this->$child = $obj;
+		return $this->$child;
 	}
 
 }
diff --git a/system/libraries/Session/Session.php b/system/libraries/Session/Session.php
index 9b011de..85a4835 100644
--- a/system/libraries/Session/Session.php
+++ b/system/libraries/Session/Session.php
@@ -107,17 +107,15 @@
 
 		// Get valid drivers list
 		$this->valid_drivers = array(
-			'Session_native',
-			'Session_cookie'
+			'native',
+			'cookie'
 		);
 		$key = 'sess_valid_drivers';
 		$drivers = isset($params[$key]) ? $params[$key] : $CI->config->item($key);
 		if ($drivers)
 		{
-			is_array($drivers) OR $drivers = array($drivers);
-
 			// Add driver names to valid list
-			foreach ($drivers as $driver)
+			foreach ((array) $drivers as $driver)
 			{
 				if ( ! in_array(strtolower($driver), array_map('strtolower', $this->valid_drivers)))
 				{
@@ -134,9 +132,9 @@
 			$driver = 'cookie';
 		}
 
-		if ( ! in_array('session_'.strtolower($driver), array_map('strtolower', $this->valid_drivers)))
+		if ( ! in_array(strtolower($driver), array_map('strtolower', $this->valid_drivers)))
 		{
-			$this->valid_drivers[] = 'Session_'.$driver;
+			$this->valid_drivers[] = $driver;
 		}
 
 		// Save a copy of parameters in case drivers need access
@@ -178,17 +176,17 @@
 	/**
 	 * Select default session storage driver
 	 *
-	 * @param	string	Driver classname
+	 * @param	string	Driver name
 	 * @return	void
 	 */
 	public function select_driver($driver)
 	{
 		// Validate driver name
-		$lowername = strtolower(str_replace('CI_', '', $driver));
-		if (in_array($lowername, array_map('strtolower', $this->valid_drivers)))
+		$prefix = (string) get_instance()->config->item('subclass_prefix');
+		$child = strtolower(str_replace(array('CI_', $prefix, $this->lib_name.'_'), '', $driver));
+		if (in_array($child, array_map('strtolower', $this->valid_drivers)))
 		{
 			// See if driver is loaded
-			$child = str_replace($this->lib_name.'_', '', $driver);
 			if (isset($this->$child))
 			{
 				// See if driver is already current
diff --git a/tests/codeigniter/database/DB_driver_test.php b/tests/codeigniter/database/DB_driver_test.php
index 1f48ca9..c04c42b 100644
--- a/tests/codeigniter/database/DB_driver_test.php
+++ b/tests/codeigniter/database/DB_driver_test.php
@@ -21,6 +21,11 @@
 		return new Mock_Database_Drivers_Mysql($config);
 	}
 
+	protected function mysqli($config)
+	{
+		return new Mock_Database_Drivers_Mysqli($config);
+	}
+
 	protected function sqlite($config)
 	{
 		return new Mock_Database_Drivers_Sqlite($config);
diff --git a/tests/codeigniter/helpers/language_helper_test.php b/tests/codeigniter/helpers/language_helper_test.php
index 06932b9..1fe0ef1 100644
--- a/tests/codeigniter/helpers/language_helper_test.php
+++ b/tests/codeigniter/helpers/language_helper_test.php
@@ -5,7 +5,9 @@
 	public function test_lang()
 	{
 		$this->helper('language');
-		$this->ci_instance_var('lang', new Mock_Core_Lang());
+		$lang = $this->getMock('CI_Lang', array('line'));
+		$lang->expects($this->any())->method('line')->will($this->returnValue(FALSE));
+		$this->ci_instance_var('lang', $lang);
 
 		$this->assertFalse(lang(1));
 		$this->assertEquals('<label for="foo"></label>', lang(1, 'foo'));
diff --git a/tests/codeigniter/libraries/Calendar_test.php b/tests/codeigniter/libraries/Calendar_test.php
index 95668d7..952e8a8 100644
--- a/tests/codeigniter/libraries/Calendar_test.php
+++ b/tests/codeigniter/libraries/Calendar_test.php
@@ -2,12 +2,12 @@
 
 class Calendar_test extends CI_TestCase {
 
-	function __construct()
+	function set_up()
 	{
-		$obj = new stdClass;
-		$obj->calendar = new Mock_Libraries_Calendar();
-
-		$this->calendar = $obj->calendar;
+		$lang = $this->getMock('CI_Lang', array('load', 'line'));
+		$lang->expects($this->any())->method('line')->will($this->returnValue(FALSE));
+		$this->ci_instance_var('lang', $lang);
+		$this->calendar = new CI_Calendar();
 	}
 
 	function test_initialize()
@@ -20,9 +20,6 @@
 		$this->assertEquals('monday', $this->calendar->start_day);
 	}
 
-	/**
-	 * @covers Mock_Libraries_Calendar::parse_template
-	 */
 	function test_generate()
 	{
 		$no_events = '<table border="0" cellpadding="4" cellspacing="0">
diff --git a/tests/codeigniter/libraries/Driver_test.php b/tests/codeigniter/libraries/Driver_test.php
new file mode 100644
index 0000000..fb5f3f0
--- /dev/null
+++ b/tests/codeigniter/libraries/Driver_test.php
@@ -0,0 +1,176 @@
+<?php
+
+/**
+ * Driver library base class unit test
+ */
+class Driver_test extends CI_TestCase {
+	/**
+	 * Set up test framework
+	 */
+	public function set_up()
+	{
+		// Set our subclass prefix
+		$this->subclass = 'Mock_Libraries_';
+		$this->ci_set_config('subclass_prefix', $this->subclass);
+
+        // Mock Loader->get_package_paths
+        $paths = 'get_package_paths';
+        $ldr = $this->getMock('CI_Loader', array($paths));
+        $ldr->expects($this->any())->method($paths)->will($this->returnValue(array(APPPATH, BASEPATH)));
+        $this->ci_instance_var('load', $ldr);
+
+		// Create mock driver library
+		$this->name = 'Driver';
+		$this->lib = new Mock_Libraries_Driver();
+	}
+
+	/**
+	 * Test driver child loading
+	 */
+	public function test_load_driver()
+	{
+		// Create driver file
+		$driver = 'basic';
+		$file = $this->name.'_'.$driver;
+		$class = 'CI_'.$file;
+		$prop = 'called';
+		$content = '<?php class '.$class.' extends CI_Driver { public $'.$prop.' = FALSE; '.
+			'public function decorate($parent) { $this->'.$prop.' = TRUE; } }';
+		$this->ci_vfs_create($file, $content, $this->ci_base_root, array('libraries', $this->name, 'drivers'));
+
+		// Make driver valid
+		$this->lib->driver_list($driver);
+
+		// Load driver
+		$this->assertNotNull($this->lib->load_driver($driver));
+
+		// Did lib name get set?
+		$this->assertEquals($this->name, $this->lib->get_name());
+
+		// Was driver loaded?
+		$this->assertObjectHasAttribute($driver, $this->lib);
+		$this->assertAttributeInstanceOf($class, $driver, $this->lib);
+		$this->assertAttributeInstanceOf('CI_Driver', $driver, $this->lib);
+
+		// Was decorate called?
+		$this->assertObjectHasAttribute($prop, $this->lib->$driver);
+		$this->assertTrue($this->lib->$driver->$prop);
+
+		// Do we get an error for an invalid driver?
+		$driver = 'unlisted';
+		$this->setExpectedException('RuntimeException', 'CI Error: Invalid driver requested: '.$this->name.'_'.$driver);
+		$this->lib->load_driver($driver);
+	}
+
+	/**
+	 * Test loading lowercase from app path
+	 */
+	public function test_load_app_driver()
+	{
+		// Create driver file
+		$driver = 'lowpack';
+		$file = $this->name.'_'.$driver;
+		$class = 'CI_'.$file;
+		$content = '<?php class '.$class.' extends CI_Driver {  }';
+		$this->ci_vfs_create($file, $content, $this->ci_app_root,
+			array('libraries', $this->name, 'drivers'));
+
+		// Make valid list
+		$nodriver = 'absent';
+		$this->lib->driver_list(array($driver, $nodriver));
+
+		// Load driver
+		$this->assertNotNull($this->lib->load_driver($driver));
+
+		// Was driver loaded?
+		$this->assertObjectHasAttribute($driver, $this->lib);
+		$this->assertAttributeInstanceOf($class, $driver, $this->lib);
+		$this->assertAttributeInstanceOf('CI_Driver', $driver, $this->lib);
+
+		// Do we get an error for a non-existent driver?
+		$this->setExpectedException('RuntimeException', 'CI Error: Unable to load the requested driver: CI_'.
+			$this->name.'_'.$nodriver);
+		$this->lib->load_driver($nodriver);
+	}
+
+	/**
+	 * Test loading driver extension
+	 */
+	public function test_load_driver_ext()
+	{
+		// Create base file
+		$driver = 'extend';
+		$base = $this->name.'_'.$driver;
+		$baseclass = 'CI_'.$base;
+		$content = '<?php class '.$baseclass.' extends CI_Driver {  }';
+		$this->ci_vfs_create($base, $content, $this->ci_base_root, array('libraries', $this->name, 'drivers'));
+
+		// Create driver file
+		$class = $this->subclass.$base;
+		$content = '<?php class '.$class.' extends '.$baseclass.' {  }';
+		$this->ci_vfs_create($class, $content, $this->ci_app_root, array('libraries', $this->name, 'drivers'));
+
+		// Make valid list
+		$this->lib->driver_list($driver);
+
+		// Load driver
+		$this->assertNotNull($this->lib->load_driver($driver));
+
+		// Was driver loaded?
+		$this->assertObjectHasAttribute($driver, $this->lib);
+		$this->assertAttributeInstanceOf($class, $driver, $this->lib);
+		$this->assertAttributeInstanceOf($baseclass, $driver, $this->lib);
+		$this->assertAttributeInstanceOf('CI_Driver', $driver, $this->lib);
+
+		// Create driver extension without base
+		$driver = 'baseless';
+		$base = $this->name.'_'.$driver;
+		$class = $this->subclass.$base;
+		$content = '<?php class '.$class.' extends CI_Driver {  }';
+		$this->ci_vfs_create($class, $content, $this->ci_app_root, array('libraries', $this->name, 'drivers'));
+		$this->lib->driver_list($driver);
+
+		// Do we get an error when base class isn't found?
+		$this->setExpectedException('RuntimeException', 'CI Error: Unable to load the requested class: CI_'.$base);
+		$this->lib->load_driver($driver);
+	}
+
+	/**
+	 * Test decorating driver with parent attributes
+	 */
+	public function test_decorate()
+	{
+		// Create parent with a method and property to access
+		$pclass = 'Test_Parent';
+		$prop = 'parent_prop';
+		$value = 'initial';
+		$method = 'parent_func';
+		$return = 'func return';
+		$code = 'class '.$pclass.' { public $'.$prop.' = \''.$value.'\'; '.
+			'public function '.$method.'() { return \''.$return.'\'; } }';
+		eval($code);
+		$parent = new $pclass();
+
+		// Create child driver to decorate
+		$class = 'Test_Driver';
+		eval('class '.$class.' extends CI_Driver {  }');
+		$child = new $class();
+
+		// Decorate child
+		$child->decorate($parent);
+
+		// Do we get the initial parent property value?
+		$this->assertEquals($value, $child->$prop);
+
+		// Can we change the parent property?
+		$newval = 'changed';
+		$child->$prop = $newval;
+		$this->assertEquals($newval, $parent->$prop);
+
+		// Do we get back the updated value?
+		$this->assertEquals($newval, $child->$prop);
+
+		// Can we call the parent method?
+		$this->assertEquals($return, $child->$method());
+	}
+}
diff --git a/tests/codeigniter/libraries/Session_test.php b/tests/codeigniter/libraries/Session_test.php
index 14469f7..e675b4e 100644
--- a/tests/codeigniter/libraries/Session_test.php
+++ b/tests/codeigniter/libraries/Session_test.php
@@ -6,8 +6,8 @@
 class Session_test extends CI_TestCase {
 	protected $settings = array(
 		'use_cookies' => 0,
-	   	'use_only_cookies' => 0,
-	   	'cache_limiter' => false
+		'use_only_cookies' => 0,
+		'cache_limiter' => false
 	);
 	protected $setting_vals = array();
 	protected $cookie_vals;
@@ -28,11 +28,12 @@
 		$this->cookie_vals = $_COOKIE;
 		$_COOKIE = array();
 
+		// Set subclass prefix to match our mock
+		$this->ci_set_config('subclass_prefix', 'Mock_Libraries_');
+
 		// Establish necessary support classes
-		$cfg = $this->ci_core_class('cfg');
-		$ldr = $this->ci_core_class('load');
 		$ci = $this->ci_instance();
-		$ci->config = new $cfg();
+		$ldr = $this->ci_core_class('load');
 		$ci->load = new $ldr();
 		$ci->input = new Mock_Core_Input(NULL, NULL);
 
@@ -56,11 +57,7 @@
 			'sess_time_to_update' => 300,
 			'time_reference' => 'local',
 			'cookie_prefix' => '',
-			'encryption_key' => 'foobar',
-			'sess_valid_drivers' => array(
-				'Mock_Libraries_Session_native',
-			   	'Mock_Libraries_Session_cookie'
-			)
+			'encryption_key' => 'foobar'
 		);
 		$this->session = new Mock_Libraries_Session($config);
 	}
@@ -83,9 +80,6 @@
 
 	/**
 	 * Test set_userdata() function
-	 *
-	 * @covers  CI_Session::set_userdata
-	 * @covers  CI_Session::userdata
 	 */
 	public function test_set_userdata()
 	{
@@ -117,8 +111,6 @@
 
 	/**
 	 * Test the has_userdata() function
-	 *
-	 * @covers	CI_Session::has_userdata
 	 */
 	public function test_has_userdata()
 	{
@@ -141,8 +133,6 @@
 
 	/**
 	 * Test the all_userdata() function
-	 *
-	 * @covers	CI_Session::all_userdata
 	 */
 	public function test_all_userdata()
 	{
@@ -150,16 +140,16 @@
 		$cdata = array(
 			'one' => 'first',
 			'two' => 'second',
-		   	'three' => 'third',
-		   	'foo' => 'bar',
-		   	'bar' => 'baz'
+			'three' => 'third',
+			'foo' => 'bar',
+			'bar' => 'baz'
 		);
 		$ndata = array(
 			'one' => 'gold',
-		   	'two' => 'silver',
-		   	'three' => 'bronze',
-		   	'foo' => 'baz',
-		   	'bar' => 'foo'
+			'two' => 'silver',
+			'three' => 'bronze',
+			'foo' => 'baz',
+			'bar' => 'foo'
 		);
 		$this->session->cookie->set_userdata($cdata);
 		$this->session->native->set_userdata($ndata);
@@ -177,8 +167,6 @@
 
 	/**
 	 * Test the unset_userdata() function
-	 *
-	 * @covers	CI_Session::unset_userdata
 	 */
 	public function test_unset_userdata()
 	{
@@ -202,9 +190,6 @@
 
 	/**
 	 * Test the flashdata() functions
-	 *
-	 * @covers	CI_Session::set_flashdata
-	 * @covers	CI_Session::flashdata
 	 */
 	public function test_flashdata()
 	{
@@ -234,8 +219,6 @@
 
 	/**
 	 * Test the keep_flashdata() function
-	 *
-	 * @covers	CI_Session::keep_flashdata
 	 */
 	public function test_keep_flashdata()
 	{
@@ -271,25 +254,23 @@
 
 	/**
 	 * Test the all_flashdata() function
-	 *
-	 * @covers	CI_Session::all_flashdata
 	 */
 	public function test_all_flashdata()
 	{
 		// Set a specific series of data for each driver
 		$cdata = array(
 			'one' => 'first',
-		   	'two' => 'second',
-		   	'three' => 'third',
-		   	'foo' => 'bar',
-		   	'bar' => 'baz'
+			'two' => 'second',
+			'three' => 'third',
+			'foo' => 'bar',
+			'bar' => 'baz'
 		);
 		$ndata = array(
 			'one' => 'gold',
-		   	'two' => 'silver',
-		   	'three' => 'bronze',
-		   	'foo' => 'baz',
-		   	'bar' => 'foo'
+			'two' => 'silver',
+			'three' => 'bronze',
+			'foo' => 'baz',
+			'bar' => 'foo'
 		);
 		$this->session->cookie->set_flashdata($cdata);
 		$this->session->native->set_flashdata($ndata);
@@ -303,9 +284,6 @@
 
 	/**
 	 * Test the tempdata() functions
-	 *
-	 * @covers	CI_Session::set_tempdata
-	 * @covers	CI_Session::tempdata
 	 */
 	public function test_set_tempdata()
 	{
@@ -332,8 +310,6 @@
 
 	/**
 	 * Test the unset_tempdata() function
-	 *
-	 * @covers	CI_Session::unset_tempdata
 	 */
 	public function test_unset_tempdata()
 	{
@@ -357,8 +333,6 @@
 
 	/**
 	 * Test the sess_regenerate() function
-	 *
-	 * @covers	CI_Session::sess_regenerate
 	 */
 	public function test_sess_regenerate()
 	{
@@ -378,8 +352,6 @@
 
 	/**
 	 * Test the sess_destroy() function
-	 *
-	 * @covers	CI_Session::sess_destroy
 	 */
 	public function test_sess_destroy()
 	{
@@ -399,4 +371,4 @@
 		$this->session->native->sess_destroy();
 		$this->assertNull($this->session->native->userdata($key));
 	}
-}
\ No newline at end of file
+}
diff --git a/tests/codeigniter/libraries/Upload_test.php b/tests/codeigniter/libraries/Upload_test.php
index 546cebc..1bd8f14 100644
--- a/tests/codeigniter/libraries/Upload_test.php
+++ b/tests/codeigniter/libraries/Upload_test.php
@@ -7,7 +7,8 @@
 		$ci = $this->ci_instance();
 		$ci->upload = new Mock_Libraries_Upload();
 		$ci->security = new Mock_Core_Security();
-		$ci->lang = new Mock_Core_Lang();
+		$ci->lang = $this->getMock('CI_Lang', array('load', 'line'));
+		$ci->lang->expects($this->any())->method('line')->will($this->returnValue(FALSE));
 		$this->upload = $ci->upload;
 	}
 
diff --git a/tests/mocks/core/lang.php b/tests/mocks/core/lang.php
deleted file mode 100644
index 27ea3fa..0000000
--- a/tests/mocks/core/lang.php
+++ /dev/null
@@ -1,15 +0,0 @@
-<?php
-
-class Mock_Core_Lang extends CI_Lang {
-
-	public function line($line = '')
-	{
-		return FALSE;
-	}
-
-	public function load($langfile, $idiom = '', $return = FALSE, $add_suffix = TRUE, $alt_path = '')
-	{
-		return;
-	}
-
-}
\ No newline at end of file
diff --git a/tests/mocks/database/config/mysqli.php b/tests/mocks/database/config/mysqli.php
new file mode 100644
index 0000000..5dd08ab
--- /dev/null
+++ b/tests/mocks/database/config/mysqli.php
@@ -0,0 +1,34 @@
+<?php
+
+return array(
+
+	// Typical Database configuration
+	'mysqli' => array(
+		'dsn' => '',
+		'hostname' => 'localhost',
+		'username' => 'travis',
+		'password' => '',
+		'database' => 'ci_test',
+		'dbdriver' => 'mysqli'
+	),
+
+	// Database configuration with failover
+	'mysqli_failover' => array(
+		'dsn' => '',
+		'hostname' => 'localhost',
+		'username' => 'not_travis',
+		'password' => 'wrong password',
+		'database' => 'not_ci_test',
+		'dbdriver' => 'mysqli',
+		'failover' => array(
+			array(
+				'dsn' => '',
+				'hostname' => 'localhost',
+				'username' => 'travis',
+				'password' => '',
+				'database' => 'ci_test',
+				'dbdriver' => 'mysqli',
+			)
+		)
+	)
+);
\ No newline at end of file
diff --git a/tests/mocks/database/drivers/mysqli.php b/tests/mocks/database/drivers/mysqli.php
new file mode 100644
index 0000000..73c35b6
--- /dev/null
+++ b/tests/mocks/database/drivers/mysqli.php
@@ -0,0 +1,17 @@
+<?php
+
+class Mock_Database_Drivers_Mysqli extends Mock_Database_DB_Driver {
+
+	/**
+	 * Instantiate the database driver
+	 *
+	 * @param	string	DB Driver class name
+	 * @param	array	DB configuration to set
+	 * @return	void
+	 */
+	public function __construct($config = array())
+	{
+		parent::__construct('CI_DB_mysqli_driver', $config);
+	}
+
+}
\ No newline at end of file
diff --git a/tests/mocks/libraries/calendar.php b/tests/mocks/libraries/calendar.php
deleted file mode 100644
index 8fee536..0000000
--- a/tests/mocks/libraries/calendar.php
+++ /dev/null
@@ -1,25 +0,0 @@
-<?php
-
-class Mock_Libraries_Calendar extends CI_Calendar {
-
-	public function __construct($config = array())
-	{
-		$this->CI = new stdClass;
-		$this->CI->lang = new Mock_Core_Lang();
-
-		if ( ! in_array('calendar_lang.php', $this->CI->lang->is_loaded, TRUE))
-		{
-			$this->CI->lang->load('calendar');
-		}
-
-		$this->local_time = time();
-
-		if (count($config) > 0)
-		{
-			$this->initialize($config);
-		}
-
-		log_message('debug', 'Calendar Class Initialized');
-	}
-
-}
\ No newline at end of file
diff --git a/tests/mocks/libraries/driver.php b/tests/mocks/libraries/driver.php
new file mode 100644
index 0000000..91bb015
--- /dev/null
+++ b/tests/mocks/libraries/driver.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * Mock library to subclass Driver for testing
+ */
+class Mock_Libraries_Driver extends CI_Driver_Library {
+	/**
+	 * Set valid drivers list
+	 */
+	public function driver_list($drivers = NULL)
+	{
+		if (empty($drivers))
+		{
+			return $this->valid_drivers;
+		}
+
+		$this->valid_drivers = (array) $drivers;
+	}
+
+	/**
+	 * Get library name
+	 */
+	public function get_name()
+	{
+		return $this->lib_name;
+	}
+}
diff --git a/tests/mocks/libraries/session.php b/tests/mocks/libraries/session.php
index c6e194f..11b27cf 100644
--- a/tests/mocks/libraries/session.php
+++ b/tests/mocks/libraries/session.php
@@ -4,7 +4,6 @@
  * Mock library to add testing features to Session driver library
  */
 class Mock_Libraries_Session extends CI_Session {
-
 	/**
 	 * Simulate new page load
 	 */
@@ -20,7 +19,6 @@
  * Mock cookie driver to overload cookie setting
  */
 class Mock_Libraries_Session_cookie extends CI_Session_cookie {
-
 	/**
 	 * Overload _setcookie to manage $_COOKIE values, since actual cookies can't be set in unit testing
 	 */
@@ -36,8 +34,3 @@
 		}
 	}
 }
-
-/**
- * Mock native driver (just for consistency in loading)
- */
-class Mock_Libraries_Session_native extends CI_Session_native { }
\ No newline at end of file
diff --git a/tests/travis/mysqli.phpunit.xml b/tests/travis/mysqli.phpunit.xml
new file mode 100644
index 0000000..1364f8b
--- /dev/null
+++ b/tests/travis/mysqli.phpunit.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit 
+	bootstrap="../Bootstrap.php"
+	colors="true"
+	convertNoticesToExceptions="true"
+	convertWarningsToExceptions="true"
+	stopOnError="false"
+	stopOnFailure="false"
+	stopOnIncomplete="false"
+	stopOnSkipped="false">
+	<php>
+        <const name="DB_DRIVER" value="mysqli"/>
+    </php>
+	<testsuites>
+		<testsuite name="CodeIgniter Core Test Suite">
+			<directory suffix="test.php">../codeigniter</directory>
+		</testsuite>
+	</testsuites>
+	<filter>
+        <whitelist addUncoveredFilesFromWhitelist="false">
+            <directory suffix=".php">../../system</directory>
+        </whitelist>
+	</filter>
+</phpunit>
\ No newline at end of file
diff --git a/user_guide_src/source/changelog.rst b/user_guide_src/source/changelog.rst
index 5d4757a..92989af 100644
--- a/user_guide_src/source/changelog.rst
+++ b/user_guide_src/source/changelog.rst
@@ -275,7 +275,6 @@
 	 -  Added method ``get_vars()`` to the Loader to retrieve all variables loaded with ``$this->load->vars()``.
 	 -  ``_ci_autoloader()`` is now a protected method.
 	 -  Added autoloading of drivers with ``$autoload['drivers']``.
-	 -  ``library()`` method will now load drivers as well, for backward compatibility of converted libraries (like :doc:`Session <libraries/sessions>`).
 	 -  ``$config['rewrite_short_tags']`` now has no effect when using PHP 5.4 as ``<?=`` will always be available.
 	 -  Changed method ``config()`` to return whatever ``CI_Config::load()`` returns instead of always being void.
    -  :doc:`Input Library <libraries/input>` changes include:
diff --git a/user_guide_src/source/installation/upgrade_300.rst b/user_guide_src/source/installation/upgrade_300.rst
index 0af21b1..ef5fbdf 100644
--- a/user_guide_src/source/installation/upgrade_300.rst
+++ b/user_guide_src/source/installation/upgrade_300.rst
@@ -42,9 +42,13 @@
 	application/libraries/Log.php -> application/core/Log.php
 	application/libraries/MY_Log.php -> application/core/MY_log.php
 
-**************************************************************
-Step 5: Add new session driver items to your config/config.php
-**************************************************************
+*********************************************************
+Step 5: Convert your Session usage from library to driver
+*********************************************************
+
+When you load (or autoload) the Session library, you must now load it as a driver instead of a library. This means
+calling ``$this->load->driver('session')`` instead of ``$this->load->library('session')`` and/or listing 'session'
+in ``$autoload['drivers']`` instead of ``$autoload['libraries']``.
 
 With the change from a single Session Library to the new Session Driver, two new config items have been added:
 
@@ -58,6 +62,10 @@
 available as valid drivers, neither of these configuration items are required. However, it is recommended that you
 add them for clarity and ease of configuration in the future.
 
+If you have written a Session extension, you must move it into a 'Session' sub-directory of 'libraries', following the
+standard for Drivers. Also beware that some functions which are not part of the external Session API have moved into
+the drivers, so your extension may have to be broken down into separate library and driver class extensions.
+
 ***************************************
 Step 6: Update your config/database.php
 ***************************************
@@ -314,4 +322,4 @@
 	sooner rather than later.
 
 .. note:: This is for MySQL and CUBRID databases only! Other drivers don't support this
-	clause and will silently ignore it.
+	clause and will silently ignore it.
\ No newline at end of file
diff --git a/user_guide_src/source/libraries/sessions.rst b/user_guide_src/source/libraries/sessions.rst
index aecad31..36c7c1d 100644
--- a/user_guide_src/source/libraries/sessions.rst
+++ b/user_guide_src/source/libraries/sessions.rst
@@ -28,10 +28,6 @@
 Once loaded, the Sessions library object will be available using:
 $this->session
 
-.. note:: For backward compatibility, the Session class may stil be loaded
-	using the $this->load->library function, but converting your applications
-	to use $this->load->driver is strongly recommended.
-
 How do Sessions work?
 =====================
 
@@ -487,4 +483,3 @@
 your config.php file to an array including your driver name::
 
 	$config['sess_valid_drivers'] = array('sess_driver');
-