Improved VFS usage in Loader and Config units, added Loader driver test, and moved config load testing to Config unit

Signed-off-by: dchill42 <dchill42@gmail.com>
diff --git a/tests/codeigniter/core/Config_test.php b/tests/codeigniter/core/Config_test.php
index 30cb90a..7782a78 100644
--- a/tests/codeigniter/core/Config_test.php
+++ b/tests/codeigniter/core/Config_test.php
@@ -90,4 +90,54 @@
 		$this->assertEquals('http://example.com/system/', $this->config->system_url());
 	}
 
-}
\ No newline at end of file
+	// --------------------------------------------------------------------
+
+	public function test_load()
+	{
+		// Create VFS tree of application config files
+		$file1 = 'test.php';
+		$file2 = 'secttest';
+		$key1 = 'testconfig';
+		$val1 = 'my_value';
+		$cfg1 = array(
+			$key1 => $val1
+		);
+		$cfg2 = array(
+			'one' => 'prime',
+			'two' => 2,
+			'three' => true
+		);
+		$tree = array(
+			'application' => array(
+				'config' => array(
+					$file1 => '<?php $config = '.var_export($cfg1, TRUE).';',
+					$file2.'.php' => '<?php $config = '.var_export($cfg2, TRUE).';'
+				)
+			)
+		);
+		$root = vfsStream::setup('root', NULL, $tree);
+
+		// Set config path with VFS URL
+		$this->config->_config_paths = array(vfsStream::url('application').'/');
+
+		// Test regular load
+		$this->assertTrue($this->config->load($file1));
+		$this->assertEquals($val1, $this->config->item($key1));
+
+		// Test section load
+		$this->assertTrue($this->config->load($file2, TRUE));
+		$this->assertEquals($cfg2, $this->config->item($file2));
+
+		// Test graceful fail
+		$this->assertFalse($this->config->load('not_config_file', FALSE, TRUE));
+
+		// Test regular fail
+		$file3 = 'absentia';
+		$this->setExpectedException(
+			'RuntimeException',
+			'CI Error: The configuration file '.$file3.'.php does not exist.'
+		);
+		$this->assertNull($this->config->load($file3));
+	}
+
+}
diff --git a/tests/codeigniter/core/Loader_test.php b/tests/codeigniter/core/Loader_test.php
index fdea962..bb8bfd7 100644
--- a/tests/codeigniter/core/Loader_test.php
+++ b/tests/codeigniter/core/Loader_test.php
@@ -18,54 +18,123 @@
 
 	// --------------------------------------------------------------------
 
+	/**
+	 * @covers CI_Loader::library
+	 */
 	public function test_library()
 	{
 		$this->_setup_config_mock();
 
+		// Create libraries directory with test library
+		$lib = 'unit_test_lib';
+		$class = 'CI_'.ucfirst($lib);
+		$content = '<?php class '.$class.' { } ';
+		$this->_create_content('libraries', $lib, $content, NULL, TRUE);
+
 		// Test loading as an array.
-		$this->assertNull($this->load->library(array('table')));
-		$this->assertTrue(class_exists('CI_Table'), 'Table class exists');
-		$this->assertAttributeInstanceOf('CI_Table', 'table', $this->ci_obj);
+		$this->assertNull($this->load->library(array($lib)));
+		$this->assertTrue(class_exists($class), $class.' does not exist');
+		$this->assertAttributeInstanceOf($class, $lib, $this->ci_obj);
 
 		// Test no lib given
-		$this->assertEquals(FALSE, $this->load->library());
+		$this->assertFalse($this->load->library());
 
 		// Test a string given to params
-		$this->assertEquals(NULL, $this->load->library('table', ' '));
+		$this->assertNull($this->load->library($lib, ' '));
 	}
 
 	// --------------------------------------------------------------------
 
+	/**
+	 * @covers CI_Loader::library
+	 */
+	public function test_library_config()
+	{
+		$this->_setup_config_mock();
+
+		// Create libraries directory with test library
+		$lib = 'unit_test_config_lib';
+		$class = 'CI_'.ucfirst($lib);
+		$content = '<?php class '.$class.' { public function __construct($params) { $this->config = $params; } } ';
+		$this->_create_content('libraries', $lib, $content, NULL, TRUE);
+
+		// Create config file
+		$cfg = array(
+			'foo' => 'bar',
+			'bar' => 'baz',
+			'baz' => false
+		);
+		$this->_create_content('config', $lib, '<?php $config = '.var_export($cfg, TRUE).';');
+
+		// Test object name and config
+		$obj = 'testy';
+		$this->assertNull($this->load->library($lib, NULL, $obj));
+		$this->assertTrue(class_exists($class), $class.' does not exist');
+		$this->assertAttributeInstanceOf($class, $obj, $this->ci_obj);
+		$this->assertEquals($cfg, $this->ci_obj->$obj->config);
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * @covers CI_Loader::library
+	 */
 	public function test_load_library_in_application_dir()
 	{
 		$this->_setup_config_mock();
 
-		$content = '<?php class Super_test_library {} ';
+		// Create libraries directory in app path with test library
+		$lib = 'super_test_library';
+		$class = ucfirst($lib);
+		$content = '<?php class '.$class.' {} ';
+		$this->_create_content('libraries', $lib, $content);
 
-		$model = vfsStream::newFile('Super_test_library.php')->withContent($content)->at($this->load->libs_dir);
-		$this->assertNull($this->load->library('super_test_library'));
+		// Load library
+		$this->assertNull($this->load->library($lib));
 
 		// Was the model class instantiated.
-		$this->assertTrue(class_exists('Super_test_library'));
+		$this->assertTrue(class_exists($class), $class.' does not exist');
+		$this->assertAttributeInstanceOf($class, $lib, $this->ci_obj);
 	}
 
 	// --------------------------------------------------------------------
 
-	private function _setup_config_mock()
+	/**
+	 * @covers CI_Loader::driver
+	 */
+	public function test_driver()
 	{
-		// Mock up a config object until we
-		// figure out how to test the library configs
-		$config = $this->getMock('CI_Config', NULL, array(), '', FALSE);
-		$config->expects($this->any())
-			   ->method('load')
-			   ->will($this->returnValue(TRUE));
+		$this->_setup_config_mock();
 
-		// Add the mock to our stdClass
-		$this->ci_instance_var('config', $config);
+		// Create libraries directory with test driver
+		$driver = 'unit_test_driver';
+		$dir = ucfirst($driver);
+		$class = 'CI_'.$dir;
+		$content = '<?php class '.$class.' { } ';
+		$this->_create_content('libraries', $driver, $content, $dir, TRUE);
+
+		// Test loading as an array.
+		$this->assertNull($this->load->driver(array($driver)));
+		$this->assertTrue(class_exists($class), $class.' does not exist');
+		$this->assertAttributeInstanceOf($class, $driver, $this->ci_obj);
+
+		// Test loading as a library with a name
+		$obj = 'testdrive';
+		$this->assertNull($this->load->library($driver, NULL, $obj));
+		$this->assertAttributeInstanceOf($class, $obj, $this->ci_obj);
+
+		// Test no driver given
+		$this->assertFalse($this->load->driver());
+
+		// Test a string given to params
+		$this->assertNull($this->load->driver($driver, ' '));
 	}
 
 	// --------------------------------------------------------------------
 
+	/**
+	 * @covers CI_Loader::model
+	 */
 	public function test_non_existent_model()
 	{
 		$this->setExpectedException(
@@ -79,20 +148,23 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * @coverts CI_Loader::model
+	 * @covers CI_Loader::model
 	 */
 	public function test_models()
 	{
 		$this->ci_set_core_class('model', 'CI_Model');
 
-		$content = '<?php class Unit_test_model extends CI_Model {} ';
+		// Create models directory with test model
+		$model = 'unit_test_model';
+		$class = ucfirst($model);
+		$content = '<?php class '.$class.' extends CI_Model {} ';
+		$this->_create_content('models', $model, $content);
 
-		$model = vfsStream::newFile('unit_test_model.php')->withContent($content)->at($this->load->models_dir);
-
-		$this->assertNull($this->load->model('unit_test_model'));
+		// Load model
+		$this->assertNull($this->load->model($model));
 
 		// Was the model class instantiated.
-		$this->assertTrue(class_exists('Unit_test_model'));
+		$this->assertTrue(class_exists($class));
 
 		// Test no model given
 		$this->assertNull($this->load->model(''));
@@ -109,26 +181,27 @@
 	// --------------------------------------------------------------------
 
 	/**
-	 * @coverts CI_Loader::view
+	 * @covers CI_Loader::view
 	 */
 	public function test_load_view()
 	{
 		$this->ci_set_core_class('output', 'CI_Output');
 
+		// Create views directory with test view
+		$view = 'unit_test_view';
 		$content = 'This is my test page.  <?php echo $hello; ?>';
-		$view = vfsStream::newFile('unit_test_view.php')->withContent($content)->at($this->load->views_dir);
+		$this->_create_content('views', $view, $content);
 
 		// Use the optional return parameter in this test, so the view is not
 		// run through the output class.
-		$this->assertEquals('This is my test page.  World!',
-		$this->load->view('unit_test_view', array('hello' => "World!"), TRUE));
-
+		$out = $this->load->view($view, array('hello' => "World!"), TRUE);
+		$this->assertEquals('This is my test page.  World!', $out);
 	}
 
 	// --------------------------------------------------------------------
 
 	/**
-	 * @coverts CI_Loader::view
+	 * @covers CI_Loader::view
 	 */
 	public function test_non_existent_view()
 	{
@@ -142,16 +215,22 @@
 
 	// --------------------------------------------------------------------
 
+	/**
+	 * @covers CI_Loader::file
+	 */
 	public function test_file()
 	{
+		// Create views directory with test file
+		$dir = 'views';
+		$file = 'ci_test_mock_file';
 		$content = 'Here is a test file, which we will load now.';
-		$file = vfsStream::newFile('ci_test_mock_file.php')->withContent($content)->at($this->load->views_dir);
+		$this->_create_content($dir, $file, $content);
 
 		// Just like load->view(), take the output class out of the mix here.
-		$load = $this->load->file(vfsStream::url('application').'/views/ci_test_mock_file.php', TRUE);
+		$out = $this->load->file($this->load->app_path.$dir.'/'.$file.'.php', TRUE);
+		$this->assertEquals($content, $out);
 
-		$this->assertEquals($content, $load);
-
+		// Test non-existent file
 		$this->setExpectedException(
 			'RuntimeException',
 			'CI Error: Unable to load the requested file: ci_test_file_not_exists'
@@ -162,6 +241,9 @@
 
 	// --------------------------------------------------------------------
 
+	/**
+	 * @covers CI_Loader::vars
+	 */
 	public function test_vars()
 	{
 		$this->assertNull($this->load->vars(array('foo' => 'bar')));
@@ -170,10 +252,22 @@
 
 	// --------------------------------------------------------------------
 
+	/**
+	 * @covers CI_Loader::helper
+	 */
 	public function test_helper()
 	{
-		$this->assertEquals(NULL, $this->load->helper('array'));
+		// Create helper directory in app path with test helper
+		$helper = 'test';
+		$func = '_my_helper_test_func';
+		$content = '<?php function '.$func.'() { return true; } ';
+		$this->_create_content('helpers', $helper.'_helper', $content);
 
+		// Load helper
+		$this->assertEquals(NULL, $this->load->helper($helper));
+		$this->assertTrue(function_exists($func), $func.' does not exist');
+
+		// Test non-existent helper
 		$this->setExpectedException(
 			'RuntimeException',
 			'CI Error: Unable to load the requested file: helpers/bad_helper.php'
@@ -184,9 +278,31 @@
 
 	// --------------------------------------------------------------------
 
+	/**
+	 * @covers CI_Loader::helper
+	 */
 	public function test_loading_multiple_helpers()
 	{
-		$this->assertEquals(NULL, $this->load->helpers(array('file', 'array', 'string')));
+		// Create helper directory in base path with test helpers
+		$helpers = array();
+		$funcs = array();
+		$files = array();
+		for ($i = 1; $i <= 3; ++$i) {
+			$helper = 'test'.$i;
+			$helpers[] = $helper;
+			$func = '_my_helper_test_func'.$i;
+			$funcs[] = $func;
+			$files[$helper.'_helper'] = '<?php function '.$func.'() { return true; } ';
+		}
+		$this->_create_content('helpers', $files, NULL, NULL, TRUE);
+
+		// Load helpers
+		$this->assertEquals(NULL, $this->load->helpers($helpers));
+
+		// Verify helper existence
+		foreach ($funcs as $func) {
+			$this->assertTrue(function_exists($func), $func.' does not exist');
+		}
 	}
 
 	// --------------------------------------------------------------------
@@ -198,6 +314,52 @@
 
 	// --------------------------------------------------------------------
 
+	/**
+	 * @covers CI_Loader::add_package_path
+	 * @covers CI_Loader::get_package_paths
+	 * @covers CI_Loader::remove_package_path
+	 */
+	public function test_packages()
+	{
+		$this->_setup_config_mock();
+
+		// Create third-party directory in app path with model
+		$dir = 'third-party';
+		$lib = 'unit_test_package';
+		$class = 'CI_'.ucfirst($lib);
+		$content = '<?php class '.$class.' { } ';
+		$this->_create_content($dir, $lib, $content);
+
+		// Test failed load without path
+		$this->setExpectedException(
+			'RuntimeException',
+			'CI Error: Unable to load the requested class: '.$lib
+		);
+		$this->load->library($lib);
+
+		// Clear exception and get paths
+		$this->setExpectedException(NULL);
+		$paths = $this->load->get_package_paths(TRUE);
+
+		// Add path and verify
+		$path = $this->load->app_path.$dir;
+		$this->assertNull($this->load->add_package_path($path));
+		$this->assertContains($path, $this->load->get_package_paths(TRUE));
+
+		// Test successful load
+		$this->assertNull($this->load->library($lib));
+		$this->assertTrue(class_exists($class), $class.' does not exist');
+
+		// Remove path and verify restored paths
+		$this->assertNull($this->load->remove_package_path($path));
+		$this->assertEquals($paths, $this->load->get_package_paths(TRUE));
+	}
+
+	// --------------------------------------------------------------------
+
+	/**
+	 * @covers CI_Loader::config
+	 */
 	public function test_load_config()
 	{
 		$this->_setup_config_mock();
@@ -206,16 +368,53 @@
 
 	// --------------------------------------------------------------------
 
-	public function test_load_bad_config()
+	private function _setup_config_mock()
 	{
-		$this->_setup_config_mock();
+		// Mock up a config object so config() has something to call
+		$config = $this->getMock('CI_Config', array('load'), array(), '', FALSE);
+		$config->expects($this->any())
+			   ->method('load')
+			   ->will($this->returnValue(TRUE));
 
-		$this->setExpectedException(
-			'RuntimeException',
-			'CI Error: The configuration file foobar.php does not exist.'
-		);
+		// Reinitialize config paths to use VFS
+		$config->_config_paths = array($this->load->app_path);
 
-		$this->load->config('foobar', FALSE);
+		// Add the mock to our stdClass
+		$this->ci_instance_var('config', $config);
 	}
 
-}
\ No newline at end of file
+	// --------------------------------------------------------------------
+
+	private function _create_content($dir, $file, $content, $sub = NULL, $base = FALSE)
+	{
+		// Create structure containing directory
+		$tree = array($dir => array());
+
+		// Check for subdirectory
+		if ($sub) {
+			// Add subdirectory to tree and get reference
+			$tree[$dir][$sub] = array();
+			$leaf =& $tree[$dir][$sub];
+		}
+		else {
+			// Get reference to main directory
+			$leaf =& $tree[$dir];
+		}
+
+		// Check for multiple files
+		if (is_array($file)) {
+			// Add multiple files to directory
+			foreach ($file as $name => $data) {
+				$leaf[$name.'.php'] = $data;
+			}
+		}
+		else {
+			// Add single file with content
+			$leaf[$file.'.php'] = $content;
+		}
+
+		// Create structure under app or base path
+		vfsStream::create($tree, $base ? $this->load->base_root : $this->load->app_root);
+	}
+
+}
diff --git a/tests/mocks/core/loader.php b/tests/mocks/core/loader.php
index 53d88d5..e28650d 100644
--- a/tests/mocks/core/loader.php
+++ b/tests/mocks/core/loader.php
@@ -5,27 +5,31 @@
 	/**
 	 * Since we use paths to load up models, views, etc, we need the ability to
 	 * mock up the file system so when core tests are run, we aren't mucking
-	 * in the application directory.  this will give finer grained control over
-	 * these tests.  So yeah, while this looks odd, I need to overwrite protected
-	 * class vars in the loader.  So here we go...
+	 * in the application directory. This will give finer grained control over
+	 * these tests. Also, by mocking the system directory, we eliminate dependency
+	 * on any other classes so errors in libraries, helpers, etc. don't give false
+	 * negatives for the actual loading process. So yeah, while this looks odd,
+	 * I need to overwrite protected class vars in the loader. So here we go...
 	 *
 	 * @covers CI_Loader::__construct()
 	 */
 	public function __construct()
 	{
-		vfsStreamWrapper::register();
-		vfsStreamWrapper::setRoot(new vfsStreamDirectory('application'));
+		// Create VFS tree of loader locations
+		$this->root = vfsStream::setup();
+		$this->app_root = vfsStream::newDirectory('application')->at($this->root);
+		$this->base_root = vfsStream::newDirectory('system')->at($this->root);
 
-		$this->models_dir 	= vfsStream::newDirectory('models')->at(vfsStreamWrapper::getRoot());
-		$this->libs_dir 	= vfsStream::newDirectory('libraries')->at(vfsStreamWrapper::getRoot());
-		$this->helpers_dir 	= vfsStream::newDirectory('helpers')->at(vfsStreamWrapper::getRoot());
-		$this->views_dir 	= vfsStream::newDirectory('views')->at(vfsStreamWrapper::getRoot());
+		// Get VFS app and base path URLs
+		$this->app_path = vfsStream::url('application').'/';
+		$this->base_path = vfsStream::url('system').'/';
 
+		// Set loader paths with VFS URLs
 		$this->_ci_ob_level  		= ob_get_level();
-		$this->_ci_library_paths	= array(vfsStream::url('application').'/', BASEPATH);
-		$this->_ci_helper_paths 	= array(vfsStream::url('application').'/', BASEPATH);
-		$this->_ci_model_paths 		= array(vfsStream::url('application').'/');
-		$this->_ci_view_paths 		= array(vfsStream::url('application').'/views/' => TRUE);
+		$this->_ci_library_paths	= array($this->app_path, $this->base_path);
+		$this->_ci_helper_paths 	= array($this->app_path, $this->base_path);
+		$this->_ci_model_paths 		= array($this->app_path);
+		$this->_ci_view_paths 		= array($this->app_path.'views/' => TRUE);
 	}
 
-}
\ No newline at end of file
+}