From eece160168320bb41abd1e6c6f7d192f404e8c02 Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Mon, 7 May 2018 00:03:07 -0700
Subject: [PATCH 01/21] Basic & Sessions command testing

---
 .gitignore                                    |  2 +
 system/Commands/Sessions/CreateMigration.php  |  6 ++
 .../Commands/CommandsTestStreamFilter.php     | 17 +++++
 tests/system/Commands/CommandsTest.php        | 62 ++++++++++++++++
 .../system/Commands/SessionsCommandsTest.php  | 70 +++++++++++++++++++
 5 files changed, 157 insertions(+)
 create mode 100644 tests/_support/Commands/CommandsTestStreamFilter.php
 create mode 100644 tests/system/Commands/CommandsTest.php
 create mode 100644 tests/system/Commands/SessionsCommandsTest.php

diff --git a/.gitignore b/.gitignore
index 809d991402..de46280a0f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -124,3 +124,5 @@ nb-configuration.xml
 .vscode/
 
 /results/
+/phpunit.xml
+/application/Database/Migrations/
\ No newline at end of file
diff --git a/system/Commands/Sessions/CreateMigration.php b/system/Commands/Sessions/CreateMigration.php
index 18551acc5b..34cccc6238 100644
--- a/system/Commands/Sessions/CreateMigration.php
+++ b/system/Commands/Sessions/CreateMigration.php
@@ -39,6 +39,12 @@ use CodeIgniter\CLI\BaseCommand;
 use CodeIgniter\CLI\CLI;
 use Config\App;
 
+/**
+ * Creates a migration file for database sessions.
+ *
+ * @package CodeIgniter\Commands
+ */
+
 class CreateMigration extends BaseCommand
 {
 
diff --git a/tests/_support/Commands/CommandsTestStreamFilter.php b/tests/_support/Commands/CommandsTestStreamFilter.php
new file mode 100644
index 0000000000..9618507305
--- /dev/null
+++ b/tests/_support/Commands/CommandsTestStreamFilter.php
@@ -0,0 +1,17 @@
+<?php namespace CodeIgniter\Commands;
+
+class CommandsTestStreamFilter extends \php_user_filter
+{
+	public static $buffer = '';
+
+	public function filter($in, $out, &$consumed, $closing)
+	{
+		while ($bucket = stream_bucket_make_writeable($in)) {
+			self::$buffer .= $bucket->data;
+			$consumed += $bucket->datalen;
+		}
+		return PSFS_PASS_ON;
+	}
+}
+
+stream_filter_register('CommandsTestStreamFilter', 'CodeIgniter\Commands\CommandsTestStreamFilter');
diff --git a/tests/system/Commands/CommandsTest.php b/tests/system/Commands/CommandsTest.php
new file mode 100644
index 0000000000..c99bf43857
--- /dev/null
+++ b/tests/system/Commands/CommandsTest.php
@@ -0,0 +1,62 @@
+<?php namespace CodeIgniter\Commands;
+
+use Config\MockAppConfig;
+use CodeIgniter\HTTP\UserAgent;
+use CodeIgniter\CLI\CLI;
+use CodeIgniter\CLI\CommandRunner;
+
+class CommandsTest extends \CIUnitTestCase
+{
+
+	private $stream_filter;
+
+	public function setUp()
+	{
+		CommandsTestStreamFilter::$buffer = '';
+		$this->stream_filter = stream_filter_append(STDOUT, 'CommandsTestStreamFilter');
+
+		$this->env = new \CodeIgniter\Config\DotEnv(ROOTPATH);
+		$this->env->load();
+
+		// Set environment values that would otherwise stop the framework from functioning during tests.
+		if ( ! isset($_SERVER['app.baseURL']))
+		{
+			$_SERVER['app.baseURL'] = 'http://example.com';
+		}
+
+		$_SERVER['argv'] = ['spark', 'list'];
+		$_SERVER['argc'] = 2;
+		CLI::init();
+
+		$this->config = new MockAppConfig();
+		$this->request = new \CodeIgniter\HTTP\IncomingRequest($this->config, new \CodeIgniter\HTTP\URI('https://somwhere.com'), null, new UserAgent());
+		$this->response = new \CodeIgniter\HTTP\Response($this->config);
+		$this->runner = new CommandRunner($this->request, $this->response);
+	}
+
+	public function tearDown()
+	{
+		stream_filter_remove($this->stream_filter);
+	}
+
+	public function testHelpCommand()
+	{
+		$this->runner->index(['help']);
+		$result = CommandsTestStreamFilter::$buffer;
+
+		// make sure the result looks like a command list
+		$this->assertContains('Displays basic usage information.', $result);
+		$this->assertContains('command_name', $result);
+	}
+
+	public function testListCommands()
+	{
+		$this->runner->index(['list']);
+		$result = CommandsTestStreamFilter::$buffer;
+
+		// make sure the result looks like a command list
+		$this->assertContains('Lists the available commands.', $result);
+		$this->assertContains('Displays basic usage information.', $result);
+	}
+
+}
diff --git a/tests/system/Commands/SessionsCommandsTest.php b/tests/system/Commands/SessionsCommandsTest.php
new file mode 100644
index 0000000000..39e792889f
--- /dev/null
+++ b/tests/system/Commands/SessionsCommandsTest.php
@@ -0,0 +1,70 @@
+<?php namespace CodeIgniter\Commands;
+
+use Config\MockAppConfig;
+use CodeIgniter\HTTP\UserAgent;
+use CodeIgniter\CLI\CLI;
+use CodeIgniter\CLI\CommandRunner;
+
+class SessionsCommandsTest extends \CIUnitTestCase
+{
+	private $stream_filter;
+
+	public function setUp()
+	{
+		CommandsTestStreamFilter::$buffer = '';
+		$this->stream_filter = stream_filter_append(STDOUT, 'CommandsTestStreamFilter');
+
+		$this->env = new \CodeIgniter\Config\DotEnv(ROOTPATH);
+		$this->env->load();
+
+		// Set environment values that would otherwise stop the framework from functioning during tests.
+		if ( ! isset($_SERVER['app.baseURL']))
+		{
+			$_SERVER['app.baseURL'] = 'http://example.com';
+		}
+
+		$_SERVER['argv'] = ['spark', 'list'];
+		$_SERVER['argc'] = 2;
+		CLI::init();
+
+		$this->config = new MockAppConfig();
+		$this->request = new \CodeIgniter\HTTP\IncomingRequest($this->config, new \CodeIgniter\HTTP\URI('https://somwhere.com'), null, new UserAgent());
+		$this->response = new \CodeIgniter\HTTP\Response($this->config);
+		$this->runner = new CommandRunner($this->request, $this->response);
+	}
+
+	public function tearDown()
+	{
+		stream_filter_remove($this->stream_filter);
+	}
+
+	public function testCreateMigrationCommand()
+	{
+		$this->runner->index(['session:migration']);
+		$result = CommandsTestStreamFilter::$buffer;
+
+		// make sure we end up with a migration class in the right place
+		// or at least that we claim to have done so
+		// separate assertions avoid console color codes
+		$this->assertContains('Created file:', $result);
+		$this->assertContains('APPPATH/Database/Migrations/', $result);
+		$this->assertContains('_create_ci_sessions_table.php', $result);
+	}
+
+	public function testOverriddenCreateMigrationCommand()
+	{
+		$_SERVER['argv'] = ['spark','session:migration', '-t', 'mygoodies'];
+		$_SERVER['argc'] = 4;
+		CLI::init();
+		
+		$this->runner->index(['session:migration']);
+		$result = CommandsTestStreamFilter::$buffer;
+
+		// make sure we end up with a migration class in the right place
+		$this->assertContains('Created file:', $result);
+		$this->assertContains('APPPATH/Database/Migrations/', $result);
+		$this->assertContains('_create_mygoodies_table.php', $result);
+	}
+
+
+}

From 1ab2542d39c983296ac21efc8bacdd960eaac53c Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Tue, 8 May 2018 00:00:33 -0700
Subject: [PATCH 02/21] Basic command testing

---
 system/Commands/Server/Serve.php              | 62 ++++++++++++++++---
 system/Commands/Server/rewrite.php            |  4 +-
 .../{seeds => Seeds}/CITestSeeder.php         |  2 +-
 3 files changed, 57 insertions(+), 11 deletions(-)
 rename tests/_support/Database/{seeds => Seeds}/CITestSeeder.php (96%)

diff --git a/system/Commands/Server/Serve.php b/system/Commands/Server/Serve.php
index e101c9e5fb..849abef0fd 100644
--- a/system/Commands/Server/Serve.php
+++ b/system/Commands/Server/Serve.php
@@ -1,25 +1,68 @@
 <?php namespace CodeIgniter\Commands\Server;
 
+/**
+ * CodeIgniter
+ *
+ * An open source application development framework for PHP
+ *
+ * This content is released under the MIT License (MIT)
+ *
+ * Copyright (c) 2014-2018 British Columbia Institute of Technology
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ * @package	CodeIgniter
+ * @author	CodeIgniter Dev Team
+ * @copyright	2014-2018 British Columbia Institute of Technology (https://bcit.ca/)
+ * @license	https://opensource.org/licenses/MIT	MIT License
+ * @link	https://codeigniter.com
+ * @since	Version 3.0.0
+ * @filesource
+ */
+
 use CodeIgniter\CLI\BaseCommand;
 use CodeIgniter\CLI\CLI;
 
+/**
+ * Launch the PHP development server
+ * 
+ * Not testable, as it throws phpunit for a loop :-/
+ * @codeCoverageIgnore
+ */
 class Serve extends BaseCommand
 {
-	protected $group       = 'CodeIgniter';
-	protected $name        = 'serve';
+
+	protected $group = 'CodeIgniter';
+	protected $name = 'serve';
 	protected $description = 'Launchs the CodeIgniter PHP-Development Server.';
-	protected $usage       = 'serve';
-	protected $arguments   = [];
-	protected $options     = [
-		'-php'  => 'The PHP Binary [default: "PHP_BINARY"]',
-		'-host' => 'The HTTP Host [default: "localhost"]',
-		'-port' => 'The HTTP Host Port [default: "8080"]',
+	protected $usage = 'serve';
+	protected $arguments = [];
+	protected $options = [
+		'-php'	 => 'The PHP Binary [default: "PHP_BINARY"]',
+		'-host'	 => 'The HTTP Host [default: "localhost"]',
+		'-port'	 => 'The HTTP Host Port [default: "8080"]',
 	];
 
 	public function run(array $params)
 	{
 		// Collect any user-supplied options and apply them
-		$php  = CLI::getOption('php') ?? PHP_BINARY;
+		$php = CLI::getOption('php') ?? PHP_BINARY;
 		$host = CLI::getOption('host') ?? 'localhost';
 		$port = CLI::getOption('port') ?? '8080';
 
@@ -38,4 +81,5 @@ class Serve extends BaseCommand
 		// to ensure our environment is set and it simulates basic mod_rewrite.
 		passthru("{$php} -S {$host}:{$port} -t {$docroot} {$rewrite}");
 	}
+
 }
diff --git a/system/Commands/Server/rewrite.php b/system/Commands/Server/rewrite.php
index ca2a061f84..a7af4d4211 100644
--- a/system/Commands/Server/rewrite.php
+++ b/system/Commands/Server/rewrite.php
@@ -6,8 +6,9 @@
  * development server based around PHP's built-in development
  * server. This file simply tries to mimic Apache's mod_rewrite
  * functionality so the site will operate as normal.
+ * 
  */
-
+// @codeCoverageIgnoreStart
 // Avoid this file run when listing commands
 if (php_sapi_name() === 'cli')
 {
@@ -36,3 +37,4 @@ if ($uri !== '/' && (is_file($path) || is_dir($path)))
 // Otherwise, we'll load the index file and let
 // the framework handle the request from here.
 require_once $fcpath . 'index.php';
+// @codeCoverageIgnoreEnd
diff --git a/tests/_support/Database/seeds/CITestSeeder.php b/tests/_support/Database/Seeds/CITestSeeder.php
similarity index 96%
rename from tests/_support/Database/seeds/CITestSeeder.php
rename to tests/_support/Database/Seeds/CITestSeeder.php
index e7eaed444e..8088eeca21 100644
--- a/tests/_support/Database/seeds/CITestSeeder.php
+++ b/tests/_support/Database/Seeds/CITestSeeder.php
@@ -1,4 +1,4 @@
-<?php
+<?php namespace Tests\Support\Database\Seeds;
 
 class CITestSeeder extends \CodeIgniter\Database\Seeder
 {

From e7e6a6be90703d00ebe550834a96c140986bf8af Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Tue, 8 May 2018 07:53:50 -0700
Subject: [PATCH 03/21] part 1 of folder renaming

---
 tests/_support/Database/{Seeds => seedling}/CITestSeeder.php | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename tests/_support/Database/{Seeds => seedling}/CITestSeeder.php (100%)

diff --git a/tests/_support/Database/Seeds/CITestSeeder.php b/tests/_support/Database/seedling/CITestSeeder.php
similarity index 100%
rename from tests/_support/Database/Seeds/CITestSeeder.php
rename to tests/_support/Database/seedling/CITestSeeder.php

From 845f46babfd7389775ec1341801a5069f23d6aba Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Tue, 8 May 2018 07:54:43 -0700
Subject: [PATCH 04/21] Part 2 of folder renaming

---
 tests/_support/Database/{seedling => Seeds}/CITestSeeder.php | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 rename tests/_support/Database/{seedling => Seeds}/CITestSeeder.php (100%)

diff --git a/tests/_support/Database/seedling/CITestSeeder.php b/tests/_support/Database/Seeds/CITestSeeder.php
similarity index 100%
rename from tests/_support/Database/seedling/CITestSeeder.php
rename to tests/_support/Database/Seeds/CITestSeeder.php

From 2f511fc7f83563aba4d29e0afb10d36ef3cdb259 Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Tue, 8 May 2018 07:56:56 -0700
Subject: [PATCH 05/21] Fixed my gitignore

---
 .gitignore | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.gitignore b/.gitignore
index de46280a0f..c3aee13314 100644
--- a/.gitignore
+++ b/.gitignore
@@ -125,4 +125,3 @@ nb-configuration.xml
 
 /results/
 /phpunit.xml
-/application/Database/Migrations/
\ No newline at end of file

From c2e83e8a1b82f61691990341a449b6b5330f937f Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Fri, 25 May 2018 08:22:02 -0700
Subject: [PATCH 06/21] Flesh out View testing

---
 system/View/Plugins.php        |  17 ++----
 tests/system/View/ViewTest.php | 102 +++++++++++++++++++++++----------
 2 files changed, 79 insertions(+), 40 deletions(-)

diff --git a/system/View/Plugins.php b/system/View/Plugins.php
index 60e2059db8..c56408355f 100644
--- a/system/View/Plugins.php
+++ b/system/View/Plugins.php
@@ -53,7 +53,6 @@ class Plugins
 
 	//--------------------------------------------------------------------
 
-
 	/**
 	 * @param array $params
 	 *
@@ -69,7 +68,6 @@ class Plugins
 
 	//--------------------------------------------------------------------
 
-
 	/**
 	 * @param array $params
 	 *
@@ -89,7 +87,6 @@ class Plugins
 
 	//--------------------------------------------------------------------
 
-
 	/**
 	 * @param array $params
 	 *
@@ -109,7 +106,6 @@ class Plugins
 
 	//--------------------------------------------------------------------
 
-
 	/**
 	 * @param array $params
 	 *
@@ -121,7 +117,9 @@ class Plugins
 
 		return lang($line, $params);
 	}
-	
+
+	//--------------------------------------------------------------------
+
 	/**
 	 * @param array $params
 	 *
@@ -129,17 +127,14 @@ class Plugins
 	 */
 	public static function ValidationErrors(array $params = [])
 	{
-		
+
 		$validator = \config\services::validation();
-		if(empty($params))
+		if (empty($params))
 		{
 			return $validator->listErrors();
 		}
-		
-		return $validator->showError($params['field']);
-		
 
-		
+		return $validator->showError($params['field']);
 	}
 
 }
diff --git a/tests/system/View/ViewTest.php b/tests/system/View/ViewTest.php
index fdea5615d6..cdb27332a9 100644
--- a/tests/system/View/ViewTest.php
+++ b/tests/system/View/ViewTest.php
@@ -4,6 +4,7 @@ use CodeIgniter\View\View;
 
 class ViewTest extends \CIUnitTestCase
 {
+
 	protected $loader;
 	protected $viewsDir;
 	protected $config;
@@ -13,8 +14,8 @@ class ViewTest extends \CIUnitTestCase
 	public function setUp()
 	{
 		$this->loader = new \CodeIgniter\Autoloader\FileLocator(new \Config\Autoload());
-		$this->viewsDir = __DIR__.'/Views';
-		$this->config   = new Config\View();
+		$this->viewsDir = __DIR__ . '/Views';
+		$this->config = new Config\View();
 	}
 
 	//--------------------------------------------------------------------
@@ -28,8 +29,6 @@ class ViewTest extends \CIUnitTestCase
 		$this->assertEquals(['foo' => 'bar'], $view->getData());
 	}
 
-	//--------------------------------------------------------------------
-
 	public function testSetVarOverwrites()
 	{
 		$view = new View($this->config, $this->viewsDir, $this->loader);
@@ -47,8 +46,8 @@ class ViewTest extends \CIUnitTestCase
 		$view = new View($this->config, $this->viewsDir, $this->loader);
 
 		$expected = [
-			'foo' => 'bar',
-			'bar' => 'baz'
+			'foo'	 => 'bar',
+			'bar'	 => 'baz'
 		];
 
 		$view->setData($expected);
@@ -56,42 +55,38 @@ class ViewTest extends \CIUnitTestCase
 		$this->assertEquals($expected, $view->getData());
 	}
 
-	//--------------------------------------------------------------------
-
 	public function testSetDataMergesData()
 	{
 		$view = new View($this->config, $this->viewsDir, $this->loader);
 
 		$expected = [
-			'fee' => 'fi',
-			'foo' => 'bar',
-			'bar' => 'baz'
+			'fee'	 => 'fi',
+			'foo'	 => 'bar',
+			'bar'	 => 'baz'
 		];
 
 		$view->setVar('fee', 'fi');
 		$view->setData([
-			'foo' => 'bar',
-			'bar' => 'baz'
+			'foo'	 => 'bar',
+			'bar'	 => 'baz'
 		]);
 
 		$this->assertEquals($expected, $view->getData());
 	}
 
-	//--------------------------------------------------------------------
-
 	public function testSetDataOverwritesData()
 	{
 		$view = new View($this->config, $this->viewsDir, $this->loader);
 
 		$expected = [
-			'foo' => 'bar',
-			'bar' => 'baz'
+			'foo'	 => 'bar',
+			'bar'	 => 'baz'
 		];
 
 		$view->setVar('foo', 'fi');
 		$view->setData([
-			'foo' => 'bar',
-			'bar' => 'baz'
+			'foo'	 => 'bar',
+			'bar'	 => 'baz'
 		]);
 
 		$this->assertEquals($expected, $view->getData());
@@ -108,21 +103,19 @@ class ViewTest extends \CIUnitTestCase
 		$this->assertEquals(['foo' => 'bar&amp;'], $view->getData());
 	}
 
-	//--------------------------------------------------------------------
-
 	public function testSetDataWillEscapeAll()
 	{
 		$view = new View($this->config, $this->viewsDir, $this->loader);
 
 		$expected = [
-			'foo' => 'bar&amp;',
-			'bar' => 'baz&lt;'
+			'foo'	 => 'bar&amp;',
+			'bar'	 => 'baz&lt;'
 		];
 
 		$view->setData([
-			'foo' => 'bar&',
-			'bar' => 'baz<'
-		], 'html');
+			'foo'	 => 'bar&',
+			'bar'	 => 'baz<'
+				], 'html');
 
 		$this->assertEquals($expected, $view->getData());
 	}
@@ -170,7 +163,7 @@ class ViewTest extends \CIUnitTestCase
 		$view = new View($this->config, $this->viewsDir, $this->loader);
 
 		$view->setVar('testString', 'Hello World');
-		$view->render('simple', null,false);
+		$view->render('simple', null, false);
 
 		$this->assertEmpty($view->getData());
 	}
@@ -189,8 +182,6 @@ class ViewTest extends \CIUnitTestCase
 		$this->assertEquals($expected, $view->getData());
 	}
 
-	//--------------------------------------------------------------------
-
 	public function testRenderCanSaveDataThroughConfigSetting()
 	{
 		$this->config->saveData = true;
@@ -220,4 +211,57 @@ class ViewTest extends \CIUnitTestCase
 	}
 
 	//--------------------------------------------------------------------
+
+	public function testCachedRender()
+	{
+		$view = new View($this->config, $this->viewsDir, $this->loader);
+
+		$view->setVar('testString', 'Hello World');
+		$expected = '<h1>Hello World</h1>';
+
+		$this->assertContains($expected, $view->render('simple', ['cache' => 10]));
+		// this second renderings should go thru the cache
+		$this->assertContains($expected, $view->render('simple', ['cache' => 10]));
+	}
+
+	//--------------------------------------------------------------------
+
+	public function testRenderStringSavingData()
+	{
+		$view = new View($this->config, $this->viewsDir, $this->loader);
+
+		$view->setVar('testString', 'Hello World');
+		$expected = '<h1>Hello World</h1>';
+		$this->assertEquals($expected, $view->renderString('<h1><?= $testString ?></h1>', [], true));
+		$this->assertArrayHasKey('testString', $view->getData());
+		$this->assertEquals($expected, $view->renderString('<h1><?= $testString ?></h1>', [], false));
+		$this->assertArrayNotHasKey('testString', $view->getData());
+	}
+
+	//--------------------------------------------------------------------
+
+	public function testPerformanceLogging()
+	{
+		// Make sure debugging is on for our view
+		$view = new View($this->config, $this->viewsDir, $this->loader, true);
+		$this->assertEquals(0, count($view->getPerformanceData()));
+
+		$view->setVar('testString', 'Hello World');
+		$expected = '<h1>Hello World</h1>';
+		$this->assertEquals($expected, $view->renderString('<h1><?= $testString ?></h1>', [], true));
+		$this->assertEquals(1, count($view->getPerformanceData()));
+	}
+
+	public function testPerformanceNonLogging()
+	{
+		// Make sure debugging is on for our view
+		$view = new View($this->config, $this->viewsDir, $this->loader, false);
+		$this->assertEquals(0, count($view->getPerformanceData()));
+
+		$view->setVar('testString', 'Hello World');
+		$expected = '<h1>Hello World</h1>';
+		$this->assertEquals($expected, $view->renderString('<h1><?= $testString ?></h1>', [], true));
+		$this->assertEquals(0, count($view->getPerformanceData()));
+	}
+
 }

From a56adf9f509aa0b01de0933b8e68a3b663aeebe7 Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Fri, 25 May 2018 23:38:31 -0700
Subject: [PATCH 07/21] Parser testing

---
 system/Exceptions/FrameworkException.php |  12 +-
 system/View/Parser.php                   |  32 +-
 tests/_support/View/Views/simpler.php    |   1 +
 tests/system/View/ParserTest.php         | 401 +++++++++++++++--------
 tests/system/View/ViewTest.php           |   2 +-
 tests/system/View/Views/Simpler.php      |   1 +
 6 files changed, 284 insertions(+), 165 deletions(-)
 create mode 100644 tests/_support/View/Views/simpler.php
 create mode 100644 tests/system/View/Views/Simpler.php

diff --git a/system/Exceptions/FrameworkException.php b/system/Exceptions/FrameworkException.php
index 8dd95a4ab3..76646ee3eb 100644
--- a/system/Exceptions/FrameworkException.php
+++ b/system/Exceptions/FrameworkException.php
@@ -10,28 +10,30 @@
  */
 class FrameworkException extends \RuntimeException implements ExceptionInterface
 {
+
 	public static function forEmptyBaseURL(): self
 	{
-		return new self('You have an empty or invalid base URL. The baseURL value must be set in Config\App.php, or through the .env file.');
+		return new static('You have an empty or invalid base URL. The baseURL value must be set in Config\App.php, or through the .env file.');
 	}
 
 	public static function forInvalidFile(string $path)
 	{
-		return new self(lang('Core.invalidFile', [$path]));
+		return new static(lang('Core.invalidFile', [$path]));
 	}
 
 	public static function forCopyError()
 	{
-		return new self(lang('Core.copyError'));
+		return new static(lang('Core.copyError'));
 	}
 
 	public static function forMissingExtension(string $extension)
 	{
-		return new self(lang('Core.missingExtension', [$extension]));
+		return new static(lang('Core.missingExtension', [$extension]));
 	}
 
 	public static function forNoHandlers(string $class)
 	{
-		return new self(lang('Core.noHandlers', [$class]));
+		return new static(lang('Core.noHandlers', [$class]));
 	}
+
 }
diff --git a/system/View/Parser.php b/system/View/Parser.php
index b029e9d376..bb7036904a 100644
--- a/system/View/Parser.php
+++ b/system/View/Parser.php
@@ -298,24 +298,26 @@ class Parser extends View
 
 	//--------------------------------------------------------------------
 
-	protected function is_assoc($arr)
-	{
-		return array_keys($arr) !== range(0, count($arr) - 1);
-	}
+//FIXME the following method does not appear to be used anywhere, so commented out	
+//	protected function is_assoc($arr)
+//	{
+//		return array_keys($arr) !== range(0, count($arr) - 1);
+//	}
 
 	//--------------------------------------------------------------------
 
-	function strpos_all($haystack, $needle)
-	{
-		$offset = 0;
-		$allpos = [];
-		while (($pos = strpos($haystack, $needle, $offset)) !== FALSE)
-		{
-			$offset = $pos + 1;
-			$allpos[] = $pos;
-		}
-		return $allpos;
-	}
+//FIXME the following method does not appear to be used anywhere, so commented out	
+//	function strpos_all($haystack, $needle)
+//	{
+//		$offset = 0;
+//		$allpos = [];
+//		while (($pos = strpos($haystack, $needle, $offset)) !== FALSE)
+//		{
+//			$offset = $pos + 1;
+//			$allpos[] = $pos;
+//		}
+//		return $allpos;
+//	}
 
 	//--------------------------------------------------------------------
 
diff --git a/tests/_support/View/Views/simpler.php b/tests/_support/View/Views/simpler.php
new file mode 100644
index 0000000000..0588b62cd1
--- /dev/null
+++ b/tests/_support/View/Views/simpler.php
@@ -0,0 +1 @@
+<h1>{testString}</h1>
\ No newline at end of file
diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php
index 82968051e3..18b5d2214a 100644
--- a/tests/system/View/ParserTest.php
+++ b/tests/system/View/ParserTest.php
@@ -1,15 +1,16 @@
 <?php
 
 use CodeIgniter\View\Parser;
+use CodeIgniter\View\Exceptions\ViewException;
 
 class ParserTest extends \CIUnitTestCase
 {
 
 	public function setUp()
 	{
-		$this->loader   = new \CodeIgniter\Autoloader\FileLocator(new \Config\Autoload());
-		$this->viewsDir = __DIR__.'/Views';
-		$this->config   = new Config\View();
+		$this->loader = new \CodeIgniter\Autoloader\FileLocator(new \Config\Autoload());
+		$this->viewsDir = __DIR__ . '/Views';
+		$this->config = new Config\View();
 	}
 
 	// --------------------------------------------------------------------
@@ -53,9 +54,9 @@ class ParserTest extends \CIUnitTestCase
 	public function testParseString()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data   = [
-			'title' => 'Page Title',
-			'body'  => 'Lorem ipsum dolor sit amet.',
+		$data = [
+			'title'	 => 'Page Title',
+			'body'	 => 'Lorem ipsum dolor sit amet.',
 		];
 
 		$template = "{title}\n{body}";
@@ -71,14 +72,14 @@ class ParserTest extends \CIUnitTestCase
 	public function testParseStringMissingData()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data   = [
-			'title' => 'Page Title',
-			'body'  => 'Lorem ipsum dolor sit amet.',
+		$data = [
+			'title'	 => 'Page Title',
+			'body'	 => 'Lorem ipsum dolor sit amet.',
 		];
 
 		$template = "{title}\n{body}\n{name}";
 
-		$result = implode("\n", $data)."\n{name}";
+		$result = implode("\n", $data) . "\n{name}";
 
 		$parser->setData($data);
 		$this->assertEquals($result, $parser->renderString($template));
@@ -89,10 +90,10 @@ class ParserTest extends \CIUnitTestCase
 	public function testParseStringUnusedData()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data   = [
-			'title' => 'Page Title',
-			'body'  => 'Lorem ipsum dolor sit amet.',
-			'name'  => 'Someone',
+		$data = [
+			'title'	 => 'Page Title',
+			'body'	 => 'Lorem ipsum dolor sit amet.',
+			'name'	 => 'Someone',
 		];
 
 		$template = "{title}\n{body}";
@@ -113,26 +114,26 @@ class ParserTest extends \CIUnitTestCase
 
 	// --------------------------------------------------------------------
 
-    public function testParseArraySingle()
-    {
-        $parser = new Parser($this->config, $this->viewsDir, $this->loader);
-        $data   = [
-            'title'  => 'Super Heroes',
-            'powers' => [
-                ['invisibility' => 'yes', 'flying' => 'no'],
-            ],
-        ];
+	public function testParseArraySingle()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$data = [
+			'title'	 => 'Super Heroes',
+			'powers' => [
+				['invisibility' => 'yes', 'flying' => 'no'],
+			],
+		];
 
-        $template = "{ title }\n{ powers }{invisibility}\n{flying}{/powers}";
+		$template = "{ title }\n{ powers }{invisibility}\n{flying}{/powers}";
 
-        $parser->setData($data);
-        $this->assertEquals("Super Heroes\nyes\nno", $parser->renderString($template));
-    }
+		$parser->setData($data);
+		$this->assertEquals("Super Heroes\nyes\nno", $parser->renderString($template));
+	}
 
 	public function testParseArrayMulti()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data   = [
+		$data = [
 			'powers' => [
 				['invisibility' => 'yes', 'flying' => 'no'],
 			],
@@ -144,13 +145,52 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals("yes\nno\nsecond: yes no", $parser->renderString($template));
 	}
 
+	public function testParseArrayNested()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$data = [
+			'title'	 => 'Super Heroes',
+			'powers' => [
+				['invisibility'	 => 'yes', 'flying'		 => [
+						['by' => 'plane', 'with' => 'broomstick', 'scared' => 'yes']
+					]],
+			],
+		];
+
+		$template = "{ title }\n{ powers }{invisibility}\n{flying}{by} {with}{/flying}{/powers}";
+
+		$parser->setData($data);
+		$this->assertEquals("Super Heroes\nyes\nplane broomstick", $parser->renderString($template));
+	}
+
+	public function testParseArrayNestedObject()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$eagle = new stdClass();
+		$eagle->name = 'Baldy';
+		$eagle->home = 'Rockies';
+		$data = [
+			'birds' => [[
+				'pop' => $eagle,
+				'mom' => 'Owl',
+				'kids' => ['Tom', 'Dick', 'Harry'],
+				'home' => opendir('.'),
+			]],
+		];
+
+		$template = "{ birds }{mom} and {pop} work at {home}{/birds}";
+
+		$parser->setData($data);
+		$this->assertEquals("Owl and Class: stdClass work at Resource", $parser->renderString($template));
+	}
+
 	// --------------------------------------------------------------------
 
 	public function testParseLoop()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data   = [
-			'title'  => 'Super Heroes',
+		$data = [
+			'title'	 => 'Super Heroes',
 			'powers' => [
 				['name' => 'Tom'],
 				['name' => 'Dick'],
@@ -169,15 +209,15 @@ class ParserTest extends \CIUnitTestCase
 	public function testMismatchedVarPair()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data   = [
-			'title'  => 'Super Heroes',
+		$data = [
+			'title'	 => 'Super Heroes',
 			'powers' => [
 				['invisibility' => 'yes', 'flying' => 'no'],
 			],
 		];
 
 		$template = "{title}\n{powers}{invisibility}\n{flying}";
-		$result   = "Super Heroes\n{powers}{invisibility}\n{flying}";
+		$result = "Super Heroes\n{powers}{invisibility}\n{flying}";
 
 		$parser->setData($data);
 		$this->assertEquals($result, $parser->renderString($template));
@@ -188,15 +228,15 @@ class ParserTest extends \CIUnitTestCase
 	public function escValueTypes()
 	{
 		return [
-			'scalar'      => [42],
-			'string'      => ['George'],
-			'scalarlist'  => [[1, 2, 17, -4]],
-			'stringlist'  => [['George', 'Paul', 'John', 'Ringo']],
-			'associative' => [['name' => 'George', 'role' => 'guitar']],
-			'compound'    => [['name' => 'George', 'address' => ['line1' => '123 Some St', 'planet' => 'Naboo']]],
-			'pseudo'      => [
+			'scalar'		 => [42],
+			'string'		 => ['George'],
+			'scalarlist'	 => [[1, 2, 17, -4]],
+			'stringlist'	 => [['George', 'Paul', 'John', 'Ringo']],
+			'associative'	 => [['name' => 'George', 'role' => 'guitar']],
+			'compound'		 => [['name' => 'George', 'address' => ['line1' => '123 Some St', 'planet' => 'Naboo']]],
+			'pseudo'		 => [
 				[
-					'name'   => 'George',
+					'name'	 => 'George',
 					'emails' => [
 						['email' => 'me@here.com', 'type' => 'home'],
 						['email' => 'me@there.com', 'type' => 'work'],
@@ -243,6 +283,8 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals('http%3A%2F%2Ffoo.com', $parser->renderString($template));
 	}
 
+	//--------------------------------------------------------------------
+
 	public function testFilterWithNoArgument()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
@@ -257,8 +299,6 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals('&lt;script&gt;alert(&quot;ci4&quot;)&lt;/script&gt;', $parser->renderString($template));
 	}
 
-	//--------------------------------------------------------------------
-
 	public function testFilterWithArgument()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
@@ -282,7 +322,7 @@ class ParserTest extends \CIUnitTestCase
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
 
 		$data = [
-			'title' => '<script>Heroes</script>',
+			'title'	 => '<script>Heroes</script>',
 			'powers' => [
 				['link' => "<a href='test'>Link</a>"],
 			],
@@ -293,35 +333,33 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals('&lt;script&gt;Heroes&lt;/script&gt; &lt;a href=&#039;test&#039;&gt;Link&lt;/a&gt;', $parser->renderString($template));
 	}
 
-	//--------------------------------------------------------------------
+	public function testParserNoEscape()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
 
-    public function testParserNoEscape()
-    {
-        $parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$data = [
+			'title' => '<script>Heroes</script>',
+		];
 
-        $data = [
-            'title' => '<script>Heroes</script>',
-        ];
-
-        $template = "{! title!}";
-        $parser->setData($data);
-        $this->assertEquals('<script>Heroes</script>', $parser->renderString($template));
-    }
+		$template = "{! title!}";
+		$parser->setData($data);
+		$this->assertEquals('<script>Heroes</script>', $parser->renderString($template));
+	}
 
 	//--------------------------------------------------------------------
 
 	public function testIgnoresComments()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data   = [
-			'title'  => 'Super Heroes',
+		$data = [
+			'title'	 => 'Super Heroes',
 			'powers' => [
 				['invisibility' => 'yes', 'flying' => 'no'],
 			],
 		];
 
 		$template = "{# Comments #}{title}\n{powers}{invisibility}\n{flying}";
-		$result   = "Super Heroes\n{powers}{invisibility}\n{flying}";
+		$result = "Super Heroes\n{powers}{invisibility}\n{flying}";
 
 		$parser->setData($data);
 		$this->assertEquals($result, $parser->renderString($template));
@@ -332,15 +370,15 @@ class ParserTest extends \CIUnitTestCase
 	public function testNoParse()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data   = [
-			'title'  => 'Super Heroes',
+		$data = [
+			'title'	 => 'Super Heroes',
 			'powers' => [
 				['invisibility' => 'yes', 'flying' => 'no'],
 			],
 		];
 
 		$template = "{noparse}{title}\n{powers}{invisibility}\n{flying}{/noparse}";
-		$result   = "{title}\n{powers}{invisibility}\n{flying}";
+		$result = "{title}\n{powers}{invisibility}\n{flying}";
 
 		$parser->setData($data);
 		$this->assertEquals($result, $parser->renderString($template));
@@ -351,9 +389,9 @@ class ParserTest extends \CIUnitTestCase
 	public function testIfConditionalTrue()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data   = [
-			'doit' => true,
-			'dontdoit' => false
+		$data = [
+			'doit'		 => true,
+			'dontdoit'	 => false
 		];
 
 		$template = "{if doit}Howdy{endif}{ if dontdoit === false}Welcome{ endif }";
@@ -362,12 +400,10 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals('HowdyWelcome', $parser->renderString($template));
 	}
 
-	//--------------------------------------------------------------------
-
 	public function testElseConditionalFalse()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data   = [
+		$data = [
 			'doit' => true,
 		];
 
@@ -377,12 +413,10 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals('Howdy', $parser->renderString($template));
 	}
 
-	//--------------------------------------------------------------------
-
 	public function testElseConditionalTrue()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data   = [
+		$data = [
 			'doit' => false,
 		];
 
@@ -392,14 +426,12 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals('Welcome', $parser->renderString($template));
 	}
 
-	//--------------------------------------------------------------------
-
 	public function testElseifConditionalTrue()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data   = [
-			'doit' => false,
-			'dontdoit' => true
+		$data = [
+			'doit'		 => false,
+			'dontdoit'	 => true
 		];
 
 		$template = "{if doit}Howdy{elseif dontdoit}Welcome{ endif }";
@@ -408,6 +440,22 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals('Welcome', $parser->renderString($template));
 	}
 
+	public function testBadConditional()
+	{
+		$this->expectException(ViewException::class);
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$data = [
+			'doit'		 => true,
+			'dontdoit'	 => false
+		];
+
+		$template = "{if doit}achoo 'howdy'{endif}";
+		$parser->setData($data);
+
+		$result = $parser->renderString($template);
+		echo var_dump($result);
+	}
+
 	//--------------------------------------------------------------------
 
 	public function testWontParsePHP()
@@ -423,9 +471,9 @@ class ParserTest extends \CIUnitTestCase
 	public function testParseHandlesSpaces()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data   = [
-			'title' => 'Page Title',
-			'body'  => 'Lorem ipsum dolor sit amet.',
+		$data = [
+			'title'	 => 'Page Title',
+			'body'	 => 'Lorem ipsum dolor sit amet.',
 		];
 
 		$template = "{ title}\n{ body }";
@@ -438,75 +486,77 @@ class ParserTest extends \CIUnitTestCase
 
 	// --------------------------------------------------------------------
 
-    public function testParseRuns()
-    {
-        $parser = new Parser($this->config, $this->viewsDir, $this->loader);
-        $data   = [
-            'title' => 'Page Title',
-            'body'  => 'Lorem ipsum dolor sit amet.',
-        ];
+	public function testParseRuns()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$data = [
+			'title'	 => 'Page Title',
+			'body'	 => 'Lorem ipsum dolor sit amet.',
+		];
 
-        $template = "{ title}\n{ body }";
+		$template = "{ title}\n{ body }";
 
-        $result = implode("\n", $data);
+		$result = implode("\n", $data);
 
-        $parser->setData($data);
-        $this->assertEquals($result, $parser->renderString($template));
-    }
+		$parser->setData($data);
+		$this->assertEquals($result, $parser->renderString($template));
+	}
 
-    //--------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
-    /**
-     * @group parserplugins
-     */
-    public function testCanAddAndRemovePlugins()
-    {
-        $parser = new Parser($this->config, $this->viewsDir, $this->loader);
-        $parser->addPlugin('first', function($str){ return $str; });
+	/**
+	 * @group parserplugins
+	 */
+	public function testCanAddAndRemovePlugins()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$parser->addPlugin('first', function($str) {
+			return $str;
+		});
 
-        $setParsers = $this->getPrivateProperty($parser, 'plugins');
+		$setParsers = $this->getPrivateProperty($parser, 'plugins');
 
-        $this->assertArrayHasKey('first', $setParsers);
+		$this->assertArrayHasKey('first', $setParsers);
 
-        $parser->removePlugin('first');
+		$parser->removePlugin('first');
 
-        $setParsers = $this->getPrivateProperty($parser, 'plugins');
+		$setParsers = $this->getPrivateProperty($parser, 'plugins');
 
-        $this->assertArrayNotHasKey('first', $setParsers);
-    }
+		$this->assertArrayNotHasKey('first', $setParsers);
+	}
 
-    //--------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
-    /**
-     * @group parserplugins
-     */
-    public function testParserPluginNoMatches()
-    {
-        $parser = new Parser($this->config, $this->viewsDir, $this->loader);
+	/**
+	 * @group parserplugins
+	 */
+	public function testParserPluginNoMatches()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
 
-        $template = "hit:it";
+		$template = "hit:it";
 
-        $this->assertEquals("hit:it", $parser->renderString($template));
-    }
+		$this->assertEquals("hit:it", $parser->renderString($template));
+	}
 
-    //--------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
-    /**
-     * @group parserplugins
-     */
-    public function testParserPluginNoParams()
-    {
-        $parser = new Parser($this->config, $this->viewsDir, $this->loader);
-        $parser->addPlugin('hit:it', function($str){
-            return str_replace('here', "Hip to the Hop", $str);
-        }, true);
+	/**
+	 * @group parserplugins
+	 */
+	public function testParserPluginNoParams()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$parser->addPlugin('hit:it', function($str) {
+			return str_replace('here', "Hip to the Hop", $str);
+		}, true);
 
-        $template = "{+ hit:it +} stuff here {+ /hit:it +}";
+		$template = "{+ hit:it +} stuff here {+ /hit:it +}";
 
-        $this->assertEquals(" stuff Hip to the Hop ", $parser->renderString($template));
-    }
+		$this->assertEquals(" stuff Hip to the Hop ", $parser->renderString($template));
+	}
 
-    //--------------------------------------------------------------------
+	//--------------------------------------------------------------------
 
 	/**
 	 * @group parserplugins
@@ -514,15 +564,15 @@ class ParserTest extends \CIUnitTestCase
 	public function testParserPluginParams()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$parser->addPlugin('growth', function($str, array $params){
+		$parser->addPlugin('growth', function($str, array $params) {
 			$step = $params['step'] ?? 1;
 			$count = $params['count'] ?? 2;
 
 			$out = '';
 
-			for ($i=1; $i <= $count; $i++)
+			for ($i = 1; $i <= $count; $i ++ )
 			{
-				$out .= " ".$i * $step;
+				$out .= " " . $i * $step;
 			}
 
 			return $out;
@@ -541,7 +591,7 @@ class ParserTest extends \CIUnitTestCase
 	public function testParserSingleTag()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$parser->addPlugin('hit:it', function(){
+		$parser->addPlugin('hit:it', function() {
 			return "Hip to the Hop";
 		}, false);
 
@@ -550,15 +600,13 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals("Hip to the Hop", $parser->renderString($template));
 	}
 
-	//--------------------------------------------------------------------
-
 	/**
 	 * @group parserplugins
 	 */
 	public function testParserSingleTagWithParams()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$parser->addPlugin('hit:it', function(array $params=[]){
+		$parser->addPlugin('hit:it', function(array $params = []) {
 			return "{$params['first']} to the {$params['last']}";
 		}, false);
 
@@ -567,15 +615,13 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals("foo to the bar", $parser->renderString($template));
 	}
 
-	//--------------------------------------------------------------------
-
 	/**
 	 * @group parserplugins
 	 */
 	public function testParserSingleTagWithSingleParams()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$parser->addPlugin('hit:it', function(array $params=[]){
+		$parser->addPlugin('hit:it', function(array $params = []) {
 			return "{$params[0]} to the {$params[1]}";
 		}, false);
 
@@ -584,15 +630,13 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals("foo to the bar", $parser->renderString($template));
 	}
 
-	//--------------------------------------------------------------------
-
 	/**
 	 * @group parserplugins
 	 */
 	public function testParserSingleTagWithQuotedParams()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$parser->addPlugin('count', function(array $params=[]){
+		$parser->addPlugin('count', function(array $params = []) {
 			$out = '';
 
 			foreach ($params as $index => $param)
@@ -616,7 +660,7 @@ class ParserTest extends \CIUnitTestCase
 	public function testParseLoopWithDollarSign()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data   = [
+		$data = [
 			'books' => [
 				['price' => '12.50'],
 			],
@@ -628,5 +672,74 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals("<p>Price $: 12.50</p>", $parser->renderString($template));
 	}
 
-	// --------------------------------------------------------------------
+	//--------------------------------------------------------------------
+
+	public function testCachedRender()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$parser->setVar('teststring', 'Hello World');
+
+		$expected = '<h1>Hello World</h1>';
+		$this->assertEquals($expected, $parser->render('template1', ['cache' => 10]));
+		// this second renderings should go thru the cache
+		$this->assertEquals($expected, $parser->render('template1', ['cache' => 10]));
+	}
+
+	//--------------------------------------------------------------------
+
+	public function testRenderFindsView()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$parser->setData(['testString' => 'Hello World']);
+
+		$expected = '<h1>Hello World</h1>';
+		$this->assertEquals($expected, $parser->render('Simpler'));
+	}
+
+	public function testRenderSearchesForView()
+	{
+		$_SERVER['HTTP_HOST'] = 'example.com';
+		$_GET = [];
+		$this->config = new \Config\Pager();
+		$this->pager = new \CodeIgniter\Pager\Pager($this->config, \Config\Services::parser());
+		$this->assertTrue(strpos($this->pager->links(), '<ul class="pagination">') > 0);
+	}
+
+	public function testRenderCantFindView()
+	{
+		$this->expectException(ViewException::class);
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$parser->setData(['testString' => 'Hello World']);
+
+		$expected = '<h1>Hello World</h1>';
+		$result = $parser->render('Simplest');
+	}
+
+	//--------------------------------------------------------------------
+
+	public function testRenderSavingData()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$parser->setData(['testString' => 'Hello World']);
+
+		$expected = '<h1>Hello World</h1>';
+		$this->assertEquals($expected, $parser->render('Simpler', [], true));
+		$this->assertArrayHasKey('testString', $parser->getData());
+		$this->assertEquals($expected, $parser->render('Simpler', [], false));
+		$this->assertArrayNotHasKey('testString', $parser->getData());
+	}
+
+	public function testRenderStringSavingData()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$parser->setData(['testString' => 'Hello World']);
+
+		$expected = '<h1>Hello World</h1>';
+		$pattern = '<h1>{testString}</h1>';
+		$this->assertEquals($expected, $parser->renderString($pattern, [], true));
+		$this->assertArrayHasKey('testString', $parser->getData());
+		$this->assertEquals($expected, $parser->renderString($pattern, [], false));
+		$this->assertArrayNotHasKey('testString', $parser->getData());
+	}
+
 }
diff --git a/tests/system/View/ViewTest.php b/tests/system/View/ViewTest.php
index cdb27332a9..68b33373e0 100644
--- a/tests/system/View/ViewTest.php
+++ b/tests/system/View/ViewTest.php
@@ -150,7 +150,7 @@ class ViewTest extends \CIUnitTestCase
 	{
 		$view = new View($this->config, $this->viewsDir, $this->loader);
 
-		$this->expectException(\CodeIgniter\Exceptions\FrameworkException::class);
+		$this->expectException(\CodeIgniter\View\Exceptions\ViewException::class);
 		$view->setVar('testString', 'Hello World');
 
 		$view->render('missing');
diff --git a/tests/system/View/Views/Simpler.php b/tests/system/View/Views/Simpler.php
new file mode 100644
index 0000000000..0588b62cd1
--- /dev/null
+++ b/tests/system/View/Views/Simpler.php
@@ -0,0 +1 @@
+<h1>{testString}</h1>
\ No newline at end of file

From 0ab579ee8827c7b16b3efd12fbe5231c1d5ca6bb Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Sun, 27 May 2018 00:12:39 -0700
Subject: [PATCH 08/21] Parser tests

---
 system/View/Parser.php           |  1 +
 tests/system/View/ParserTest.php | 16 ----------------
 2 files changed, 1 insertion(+), 16 deletions(-)

diff --git a/system/View/Parser.php b/system/View/Parser.php
index bb7036904a..89f7dd7bd0 100644
--- a/system/View/Parser.php
+++ b/system/View/Parser.php
@@ -528,6 +528,7 @@ class Parser extends View
 		extract($this->data);
 		$result = eval('?>' . $template . '<?php ');
 
+		//TODO under what circumstances would we ever get a FALSE? Could not induce one
 		if ($result === false)
 		{
 			throw ViewException::forTagSyntaxError(str_replace(['?>', '<?php '], '', $template));
diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php
index 18b5d2214a..fd4dcaff79 100644
--- a/tests/system/View/ParserTest.php
+++ b/tests/system/View/ParserTest.php
@@ -440,22 +440,6 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals('Welcome', $parser->renderString($template));
 	}
 
-	public function testBadConditional()
-	{
-		$this->expectException(ViewException::class);
-		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
-		$data = [
-			'doit'		 => true,
-			'dontdoit'	 => false
-		];
-
-		$template = "{if doit}achoo 'howdy'{endif}";
-		$parser->setData($data);
-
-		$result = $parser->renderString($template);
-		echo var_dump($result);
-	}
-
 	//--------------------------------------------------------------------
 
 	public function testWontParsePHP()

From ac6ec0035442c7d36c6f2f8459bf468bbc8615ad Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Mon, 4 Jun 2018 01:21:21 -0700
Subject: [PATCH 09/21] Flesh out Parser tests; handle ParseError

---
 system/View/Parser.php           | 16 +++----
 tests/system/View/ParserTest.php | 73 +++++++++++++++++++++++---------
 2 files changed, 60 insertions(+), 29 deletions(-)

diff --git a/system/View/Parser.php b/system/View/Parser.php
index 89f7dd7bd0..069ca1733d 100644
--- a/system/View/Parser.php
+++ b/system/View/Parser.php
@@ -297,15 +297,12 @@ class Parser extends View
 	}
 
 	//--------------------------------------------------------------------
-
 //FIXME the following method does not appear to be used anywhere, so commented out	
 //	protected function is_assoc($arr)
 //	{
 //		return array_keys($arr) !== range(0, count($arr) - 1);
 //	}
-
 	//--------------------------------------------------------------------
-
 //FIXME the following method does not appear to be used anywhere, so commented out	
 //	function strpos_all($haystack, $needle)
 //	{
@@ -318,7 +315,6 @@ class Parser extends View
 //		}
 //		return $allpos;
 //	}
-
 	//--------------------------------------------------------------------
 
 	/**
@@ -526,14 +522,14 @@ class Parser extends View
 		// Parse the PHP itself, or insert an error so they can debug
 		ob_start();
 		extract($this->data);
-		$result = eval('?>' . $template . '<?php ');
-
-		//TODO under what circumstances would we ever get a FALSE? Could not induce one
-		if ($result === false)
+		try
 		{
+			$result = eval('?>' . $template . '<?php ');
+		} catch (\ParseError $e)
+		{
+			ob_end_clean();
 			throw ViewException::forTagSyntaxError(str_replace(['?>', '<?php '], '', $template));
 		}
-
 		return ob_get_clean();
 	}
 
@@ -653,7 +649,7 @@ class Parser extends View
 			$escape = false;
 		}
 		// If no `esc` filter is found, then we'll need to add one.
-		elseif ( ! preg_match('/^|\s+esc/', $key))
+		elseif ( ! preg_match('/\s+esc/', $key))
 		{
 			$escape = 'html';
 		}
diff --git a/tests/system/View/ParserTest.php b/tests/system/View/ParserTest.php
index 3b5faf0567..7e0dadb12b 100644
--- a/tests/system/View/ParserTest.php
+++ b/tests/system/View/ParserTest.php
@@ -10,9 +10,9 @@ class ParserTest extends \CIUnitTestCase
 	{
 		parent::setUp();
 
-		$this->loader   = new \CodeIgniter\Autoloader\FileLocator(new \Config\Autoload());
-		$this->viewsDir = __DIR__.'/Views';
-		$this->config   = new Config\View();
+		$this->loader = new \CodeIgniter\Autoloader\FileLocator(new \Config\Autoload());
+		$this->viewsDir = __DIR__ . '/Views';
+		$this->config = new Config\View();
 	}
 
 	// --------------------------------------------------------------------
@@ -173,11 +173,11 @@ class ParserTest extends \CIUnitTestCase
 		$eagle->home = 'Rockies';
 		$data = [
 			'birds' => [[
-				'pop' => $eagle,
-				'mom' => 'Owl',
-				'kids' => ['Tom', 'Dick', 'Harry'],
-				'home' => opendir('.'),
-			]],
+			'pop'	 => $eagle,
+			'mom'	 => 'Owl',
+			'kids'	 => ['Tom', 'Dick', 'Harry'],
+			'home'	 => opendir('.'),
+				]],
 		];
 
 		$template = "{ birds }{mom} and {pop} work at {home}{/birds}";
@@ -285,6 +285,32 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals('http%3A%2F%2Ffoo.com', $parser->renderString($template));
 	}
 
+	public function testNoEscapingSetData()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+
+		$template = '{ foo | noescape}';
+
+		$parser->setData(['foo' => 'http://foo.com'], 'unknown');
+		$this->assertEquals('http://foo.com', $parser->renderString($template));
+	}
+
+	public function testAutoEscaping()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$parser->setData(['foo' => 'http://foo.com'], 'unknown');
+
+		$this->assertEquals('html', $parser->shouldAddEscaping('{ foo | this | that }'));
+	}
+
+	public function testAutoEscapingNot()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$parser->setData(['foo' => 'http://foo.com'], 'unknown');
+
+		$this->assertEquals(false, $parser->shouldAddEscaping('{ foo | noescape }'));
+	}
+
 	//--------------------------------------------------------------------
 
 	public function testFilterWithNoArgument()
@@ -444,6 +470,24 @@ class ParserTest extends \CIUnitTestCase
 
 	//--------------------------------------------------------------------
 
+	public function testConditionalBadSyntax()
+	{
+		$this->expectException(ViewException::class);
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+		$data = [
+			'doit'		 => true,
+			'dontdoit'	 => false
+		];
+
+		// the template is purposefully malformed
+		$template = "{if doit}Howdy{elseif doit}Welcome{ endif )}";
+
+		$parser->setData($data);
+		$this->assertEquals('HowdyWelcome', $parser->renderString($template));
+	}
+
+	//--------------------------------------------------------------------
+
 	public function testWontParsePHP()
 	{
 		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
@@ -666,9 +710,9 @@ class ParserTest extends \CIUnitTestCase
 		$parser->setVar('teststring', 'Hello World');
 
 		$expected = '<h1>Hello World</h1>';
-		$this->assertEquals($expected, $parser->render('template1', ['cache' => 10]));
+		$this->assertEquals($expected, $parser->render('template1', ['cache' => 10, 'cache_name' => 'HelloWorld']));
 		// this second renderings should go thru the cache
-		$this->assertEquals($expected, $parser->render('template1', ['cache' => 10]));
+		$this->assertEquals($expected, $parser->render('template1', ['cache' => 10, 'cache_name' => 'HelloWorld']));
 	}
 
 	//--------------------------------------------------------------------
@@ -682,15 +726,6 @@ class ParserTest extends \CIUnitTestCase
 		$this->assertEquals($expected, $parser->render('Simpler'));
 	}
 
-	public function testRenderSearchesForView()
-	{
-		$_SERVER['HTTP_HOST'] = 'example.com';
-		$_GET = [];
-		$this->config = new \Config\Pager();
-		$this->pager = new \CodeIgniter\Pager\Pager($this->config, \Config\Services::parser());
-		$this->assertTrue(strpos($this->pager->links(), '<ul class="pagination">') > 0);
-	}
-
 	public function testRenderCantFindView()
 	{
 		$this->expectException(ViewException::class);

From a7e3c559b2ceaa25432463d32e3c11f08feff8ac Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Mon, 4 Jun 2018 07:53:18 -0700
Subject: [PATCH 10/21] Flesh out Filter testing

---
 tests/system/View/ParserFilterTest.php | 37 ++++++++++++++++++++++++--
 1 file changed, 35 insertions(+), 2 deletions(-)

diff --git a/tests/system/View/ParserFilterTest.php b/tests/system/View/ParserFilterTest.php
index dc34d4c348..b37168567c 100644
--- a/tests/system/View/ParserFilterTest.php
+++ b/tests/system/View/ParserFilterTest.php
@@ -159,6 +159,39 @@ class ParserFilterTest extends \CIUnitTestCase
 		$this->assertEquals("The quick red fox <mark>jumped over</mark> the lazy brown dog", $parser->renderString($template));
 	}
 
+	public function testHighlightCode()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+
+		$data = [
+			'value1' => 'Sincerely'
+		];
+		$parser->setData($data);
+
+		$template = '{ value1|highlight_code }';
+		$expected = <<<EOF
+<code><span style="color: #000000">
+<span style="color: #0000BB">Sincerely&nbsp;</span>
+</span>
+</code>
+EOF;
+		$this->assertEquals($expected, $parser->renderString($template));
+	}
+
+	public function testProse()
+	{
+		$parser = new Parser($this->config, $this->viewsDir, $this->loader);
+
+		$data = [
+			'value1' => 'Sincerely\nMe'
+		];
+		$parser->setData($data);
+
+		$template = '{ value1|prose }';
+		$expected = '<p>Sincerely\nMe</p>';
+		$this->assertEquals($expected, $parser->renderString($template));
+	}
+
 	//--------------------------------------------------------------------
 
 	public function testLimitChars()
@@ -249,10 +282,10 @@ class ParserFilterTest extends \CIUnitTestCase
 			'value1' => 5.55,
 		];
 
-		$template = '{ value1|round(1) } { value1|round(1, common) } { value1|round(ceil) } { value1|round(floor) }';
+		$template = '{ value1|round(1) } { value1|round(1, common) } { value1|round(ceil) } { value1|round(floor) } { value1|round(unknown) }';
 
 		$parser->setData($data);
-		$this->assertEquals('5.6 5.6 6 5', $parser->renderString($template));
+		$this->assertEquals('5.6 5.6 6 5 5.55', $parser->renderString($template));
 	}
 
 	//--------------------------------------------------------------------

From f5a7fbf72bccd92b0bc8a3324efbe9dfdf21957e Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Tue, 5 Jun 2018 12:48:18 -0700
Subject: [PATCH 11/21] Flesh out Cell & Plugins testing

---
 system/View/Cell.php                   |  16 ++--
 system/View/Plugins.php                |  12 +++
 tests/system/View/CellTest.php         | 117 +++++++++++++++++++++----
 tests/system/View/ParserPluginTest.php |   1 +
 tests/system/View/SampleClass.php      |   9 +-
 5 files changed, 129 insertions(+), 26 deletions(-)

diff --git a/system/View/Cell.php b/system/View/Cell.php
index b3571eea43..420dc9967b 100644
--- a/system/View/Cell.php
+++ b/system/View/Cell.php
@@ -77,7 +77,6 @@ class Cell
 
 	//--------------------------------------------------------------------
 
-
 	/**
 	 * Cell constructor.
 	 *
@@ -127,7 +126,7 @@ class Cell
 
 		if ($paramCount === 0)
 		{
-			if (! empty($paramArray))
+			if ( ! empty($paramArray))
 			{
 				throw ViewException::forMissingCellParameters($class, $method);
 			}
@@ -135,7 +134,9 @@ class Cell
 			$output = $instance->{$method}();
 		}
 		elseif (
-				($paramCount === 1) && ( ( ! array_key_exists($refParams[0]->name, $paramArray)) || (array_key_exists($refParams[0]->name, $paramArray) && count($paramArray) !== 1) )
+				($paramCount === 1) && (
+				( ! array_key_exists($refParams[0]->name, $paramArray)) ||
+				(array_key_exists($refParams[0]->name, $paramArray) && count($paramArray) !== 1) )
 		)
 		{
 			$output = $instance->{$method}($paramArray);
@@ -162,7 +163,7 @@ class Cell
 				}
 			}
 
-			$output = $instance->$method(...$fireArgs);
+			$output = $instance->$method(...array_values($fireArgs));
 		}
 		// Can we cache it?
 		if ( ! empty($this->cache) && $ttl !== 0)
@@ -205,8 +206,11 @@ class Cell
 
 			foreach ($params as $p)
 			{
-				list($key, $val) = explode('=', $p);
-				$new_params[trim($key)] = trim($val, ', ');
+				if ( ! empty($p))
+				{
+					list($key, $val) = explode('=', $p);
+					$new_params[trim($key)] = trim($val, ', ');
+				}
 			}
 
 			$params = $new_params;
diff --git a/system/View/Plugins.php b/system/View/Plugins.php
index c56408355f..97f9209221 100644
--- a/system/View/Plugins.php
+++ b/system/View/Plugins.php
@@ -46,7 +46,10 @@ class Plugins
 	public static function currentURL(array $params = [])
 	{
 		if ( ! function_exists('current_url'))
+			// can't unit test this since it is loaded in CIUnitTestCase setup
+			// @codeCoverageIgnoreStart
 			helper('url');
+			// @codeCoverageIgnoreEnd
 
 		return current_url();
 	}
@@ -61,7 +64,10 @@ class Plugins
 	public static function previousURL(array $params = [])
 	{
 		if ( ! function_exists('previous_url'))
+			// can't unit test this since it is loaded in CIUnitTestCase setup
+			// @codeCoverageIgnoreStart
 			helper('url');
+			// @codeCoverageIgnoreEnd
 
 		return previous_url();
 	}
@@ -76,7 +82,10 @@ class Plugins
 	public static function mailto(array $params = [])
 	{
 		if ( ! function_exists('mailto'))
+			// can't unit test this since it is loaded in CIUnitTestCase setup
+			// @codeCoverageIgnoreStart
 			helper('url');
+			// @codeCoverageIgnoreEnd
 
 		$email = $params['email'] ?? '';
 		$title = $params['title'] ?? '';
@@ -95,7 +104,10 @@ class Plugins
 	public static function safeMailto(array $params = [])
 	{
 		if ( ! function_exists('safe_mailto'))
+			// can't unit test this since it is loaded in CIUnitTestCase setup
+			// @codeCoverageIgnoreStart
 			helper('url');
+			// @codeCoverageIgnoreEnd
 
 		$email = $params['email'] ?? '';
 		$title = $params['title'] ?? '';
diff --git a/tests/system/View/CellTest.php b/tests/system/View/CellTest.php
index 75b9742de8..e9a5ff09ad 100644
--- a/tests/system/View/CellTest.php
+++ b/tests/system/View/CellTest.php
@@ -1,12 +1,14 @@
 <?php
 
 use CodeIgniter\View\Cell;
+use CodeIgniter\View\Exceptions\ViewException;
 use Tests\Support\Cache\Handlers\MockHandler;
 
-include_once __DIR__ .'/SampleClass.php';
+include_once __DIR__ . '/SampleClass.php';
 
 class CellTest extends \CIUnitTestCase
 {
+
 	protected $cache;
 
 	/**
@@ -21,28 +23,28 @@ class CellTest extends \CIUnitTestCase
 		parent::setUp();
 
 		$this->cache = new MockHandler();
-	    $this->cell = new Cell($this->cache);
+		$this->cell = new Cell($this->cache);
 	}
 
 	//--------------------------------------------------------------------
 
 	public function testPrepareParamsReturnsEmptyArrayWithInvalidParam()
 	{
-	    $this->assertEquals([], $this->cell->prepareParams(1.023));
+		$this->assertEquals([], $this->cell->prepareParams(1.023));
 	}
 
 	//--------------------------------------------------------------------
 
 	public function testPrepareParamsReturnsNullWithEmptyString()
 	{
-	    $this->assertEquals([], $this->cell->prepareParams(''));
+		$this->assertEquals([], $this->cell->prepareParams(''));
 	}
 
 	//--------------------------------------------------------------------
 
 	public function testPrepareParamsReturnsSelfWhenArray()
 	{
-	    $object = ['one' => 'two', 'three' => 'four'];
+		$object = ['one' => 'two', 'three' => 'four'];
 
 		$this->assertEquals($object, $this->cell->prepareParams($object));
 	}
@@ -51,14 +53,14 @@ class CellTest extends \CIUnitTestCase
 
 	public function testPrepareParamsReturnsEmptyArrayWithEmptyArray()
 	{
-	    $this->assertEquals([], $this->cell->prepareParams([]));
+		$this->assertEquals([], $this->cell->prepareParams([]));
 	}
 
 	//--------------------------------------------------------------------
 
 	public function testPrepareParamsReturnsArrayWithString()
 	{
-	    $params = 'one=two three=four';
+		$params = 'one=two three=four';
 		$expected = ['one' => 'two', 'three' => 'four'];
 
 		$this->assertEquals($expected, $this->cell->prepareParams($params));
@@ -95,7 +97,6 @@ class CellTest extends \CIUnitTestCase
 	}
 
 	//--------------------------------------------------------------------
-
 	//--------------------------------------------------------------------
 	// Render
 	//--------------------------------------------------------------------
@@ -129,18 +130,96 @@ class CellTest extends \CIUnitTestCase
 
 	//--------------------------------------------------------------------
 
-    public function testOptionsEmptyArray()
-    {
-        $params = [];
-        $expected = [];
+	public function testOptionsEmptyArray()
+	{
+		$params = [];
+		$expected = [];
 
-        $this->assertEquals(implode(',', $expected), $this->cell->render('\CodeIgniter\View\SampleClass::staticEcho', $params));
-    }
+		$this->assertEquals(implode(',', $expected), $this->cell->render('\CodeIgniter\View\SampleClass::staticEcho', $params));
+	}
 
-    public function testOptionsNoParams()
-    {
-        $expected = [];
+	public function testOptionsNoParams()
+	{
+		$expected = [];
+
+		$this->assertEquals(implode(',', $expected), $this->cell->render('\CodeIgniter\View\SampleClass::staticEcho'));
+	}
+
+	public function testCellEmptyParams()
+	{
+		$params = ',';
+		$expected = 'Hello World';
+
+		$this->assertEquals($expected, $this->cell->render('\CodeIgniter\View\SampleClass::index', $params));
+	}
+
+	//--------------------------------------------------------------------
+
+	public function testCellClassMissing()
+	{
+		$this->expectException(ViewException::class);
+		$params = 'one=two,three=four';
+		$expected = ['one' => 'two', 'three' => 'four'];
+
+		$this->assertEquals(implode(',', $expected), $this->cell->render('::echobox', $params));
+	}
+
+	public function testCellMethodMissing()
+	{
+		$this->expectException(ViewException::class);
+		$params = 'one=two,three=four';
+		$expected = ['one' => 'two', 'three' => 'four'];
+
+		$this->assertEquals(implode(',', $expected), $this->cell->render('\CodeIgniter\View\SampleClass::', $params));
+	}
+
+	public function testCellBadClass()
+	{
+		$this->expectException(ViewException::class);
+		$params = 'one=two,three=four';
+		$expected = 'Hello World';
+
+		$this->assertEquals($expected, $this->cell->render('\CodeIgniter\View\GoodQuestion::', $params));
+	}
+
+	public function testCellBadMethod()
+	{
+		$this->expectException(ViewException::class);
+		$params = 'one=two,three=four';
+		$expected = 'Hello World';
+
+		$this->assertEquals($expected, $this->cell->render('\CodeIgniter\View\SampleClass::notThere', $params));
+	}
+
+	//--------------------------------------------------------------------
+
+	public function testRenderCached()
+	{
+		$params = 'one=two,three=four';
+		$expected = ['one' => 'two', 'three' => 'four'];
+
+		$this->assertEquals(implode(',', $expected), $this->cell->render('\CodeIgniter\View\SampleClass::echobox', $params, 60, 'rememberme'));
+		$params = 'one=six,three=five';
+		$this->assertEquals(implode(',', $expected), $this->cell->render('\CodeIgniter\View\SampleClass::echobox', $params, 1, 'rememberme'));
+	}
+
+	//--------------------------------------------------------------------
+
+	public function testParametersMatch()
+	{
+		$params = ['p1' => 'one', 'p2' => 'two', 'p4' => 'three'];
+		$expected = 'Right on';
+
+		$this->assertEquals($expected, $this->cell->render('\CodeIgniter\View\SampleClass::work', $params));
+	}
+
+	public function testParametersDontMatch()
+	{
+		$this->expectException(ViewException::class);
+		$params = 'p1=one,p2=two,p3=three';
+		$expected = 'Right on';
+
+		$this->assertEquals($expected, $this->cell->render('\CodeIgniter\View\SampleClass::work', $params));
+	}
 
-        $this->assertEquals(implode(',', $expected), $this->cell->render('\CodeIgniter\View\SampleClass::staticEcho'));
-    }
 }
diff --git a/tests/system/View/ParserPluginTest.php b/tests/system/View/ParserPluginTest.php
index 009b0e5c18..5f8e03d977 100644
--- a/tests/system/View/ParserPluginTest.php
+++ b/tests/system/View/ParserPluginTest.php
@@ -1,6 +1,7 @@
 <?php
 
 use CodeIgniter\View\Parser;
+use CodeIgniter\View\Plugins;
 
 class ParserPluginTest extends \CIUnitTestCase
 {
diff --git a/tests/system/View/SampleClass.php b/tests/system/View/SampleClass.php
index 88dd413b6e..28de16fe2c 100644
--- a/tests/system/View/SampleClass.php
+++ b/tests/system/View/SampleClass.php
@@ -8,6 +8,11 @@
  */
 class SampleClass {
 
+	public function index()
+	{
+		return 'Hello World';
+	}
+
 	public function hello()
 	{
 		return 'Hello';
@@ -39,5 +44,7 @@ class SampleClass {
 
 	//--------------------------------------------------------------------
 
-
+	public function work($p1, $p2, $p4) {
+		return 'Right on';
+	}
 }
\ No newline at end of file

From 2ac3d164ad28f3ec410fe2019e08df166eae1e7a Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Tue, 5 Jun 2018 22:17:44 -0700
Subject: [PATCH 12/21] Modify gitignore to drop generated migrations

---
 .gitignore | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.gitignore b/.gitignore
index c3aee13314..32c14d01b5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -63,6 +63,8 @@ writable/uploads/*
 
 writable/debugbar/*
 
+application/Database/Migrations/2*
+
 php_errors.log
 
 #-------------------------

From aa23b0eb739e92c694a0609f269d11b97a47d5e6 Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Wed, 6 Jun 2018 07:32:26 -0700
Subject: [PATCH 13/21] Use the proper test stream filter

---
 tests/system/Commands/CommandsTest.php         | 11 ++++++-----
 tests/system/Commands/SessionsCommandsTest.php | 11 ++++++-----
 2 files changed, 12 insertions(+), 10 deletions(-)

diff --git a/tests/system/Commands/CommandsTest.php b/tests/system/Commands/CommandsTest.php
index c99bf43857..00dd6439d6 100644
--- a/tests/system/Commands/CommandsTest.php
+++ b/tests/system/Commands/CommandsTest.php
@@ -1,9 +1,10 @@
 <?php namespace CodeIgniter\Commands;
 
-use Config\MockAppConfig;
+use Tests\Support\Config\MockAppConfig;
 use CodeIgniter\HTTP\UserAgent;
 use CodeIgniter\CLI\CLI;
 use CodeIgniter\CLI\CommandRunner;
+use CodeIgniter\Test\Filters\CITestStreamFilter;
 
 class CommandsTest extends \CIUnitTestCase
 {
@@ -12,8 +13,8 @@ class CommandsTest extends \CIUnitTestCase
 
 	public function setUp()
 	{
-		CommandsTestStreamFilter::$buffer = '';
-		$this->stream_filter = stream_filter_append(STDOUT, 'CommandsTestStreamFilter');
+		CITestStreamFilter::$buffer = '';
+		$this->stream_filter = stream_filter_append(STDOUT, 'CITestStreamFilter');
 
 		$this->env = new \CodeIgniter\Config\DotEnv(ROOTPATH);
 		$this->env->load();
@@ -42,7 +43,7 @@ class CommandsTest extends \CIUnitTestCase
 	public function testHelpCommand()
 	{
 		$this->runner->index(['help']);
-		$result = CommandsTestStreamFilter::$buffer;
+		$result = CITestStreamFilter::$buffer;
 
 		// make sure the result looks like a command list
 		$this->assertContains('Displays basic usage information.', $result);
@@ -52,7 +53,7 @@ class CommandsTest extends \CIUnitTestCase
 	public function testListCommands()
 	{
 		$this->runner->index(['list']);
-		$result = CommandsTestStreamFilter::$buffer;
+		$result = CITestStreamFilter::$buffer;
 
 		// make sure the result looks like a command list
 		$this->assertContains('Lists the available commands.', $result);
diff --git a/tests/system/Commands/SessionsCommandsTest.php b/tests/system/Commands/SessionsCommandsTest.php
index 39e792889f..7171f8e8ad 100644
--- a/tests/system/Commands/SessionsCommandsTest.php
+++ b/tests/system/Commands/SessionsCommandsTest.php
@@ -1,9 +1,10 @@
 <?php namespace CodeIgniter\Commands;
 
-use Config\MockAppConfig;
+use Tests\Support\Config\MockAppConfig;
 use CodeIgniter\HTTP\UserAgent;
 use CodeIgniter\CLI\CLI;
 use CodeIgniter\CLI\CommandRunner;
+use CodeIgniter\Test\Filters\CITestStreamFilter;
 
 class SessionsCommandsTest extends \CIUnitTestCase
 {
@@ -11,8 +12,8 @@ class SessionsCommandsTest extends \CIUnitTestCase
 
 	public function setUp()
 	{
-		CommandsTestStreamFilter::$buffer = '';
-		$this->stream_filter = stream_filter_append(STDOUT, 'CommandsTestStreamFilter');
+		CITestStreamFilter::$buffer = '';
+		$this->stream_filter = stream_filter_append(STDOUT, 'CITestStreamFilter');
 
 		$this->env = new \CodeIgniter\Config\DotEnv(ROOTPATH);
 		$this->env->load();
@@ -41,7 +42,7 @@ class SessionsCommandsTest extends \CIUnitTestCase
 	public function testCreateMigrationCommand()
 	{
 		$this->runner->index(['session:migration']);
-		$result = CommandsTestStreamFilter::$buffer;
+		$result = CITestStreamFilter::$buffer;
 
 		// make sure we end up with a migration class in the right place
 		// or at least that we claim to have done so
@@ -58,7 +59,7 @@ class SessionsCommandsTest extends \CIUnitTestCase
 		CLI::init();
 		
 		$this->runner->index(['session:migration']);
-		$result = CommandsTestStreamFilter::$buffer;
+		$result = CITestStreamFilter::$buffer;
 
 		// make sure we end up with a migration class in the right place
 		$this->assertContains('Created file:', $result);

From 30fba9ccdd7023a109fd145d4c8e19b392bb4ae9 Mon Sep 17 00:00:00 2001
From: Lonnie Ezell <lonnieje@gmail.com>
Date: Wed, 6 Jun 2018 22:09:15 -0500
Subject: [PATCH 14/21] Don't allow failures for php 7.2 on Travis

---
 .travis.yml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.travis.yml b/.travis.yml
index 4c01894e82..b724db7167 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -8,7 +8,6 @@ php:
 matrix:
   fast_finish: true
   allow_failures:
-    - php: 7.2
     - php: nightly
 
 global:

From b29efcb12c17133f4b917ce13748c29a97656928 Mon Sep 17 00:00:00 2001
From: Lonnie Ezell <lonnieje@gmail.com>
Date: Wed, 6 Jun 2018 22:17:15 -0500
Subject: [PATCH 15/21] Rearranging phpunit whitelist to try and appease
 Travis.

---
 phpunit.xml.dist | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/phpunit.xml.dist b/phpunit.xml.dist
index d720c67e40..608abb0fbf 100644
--- a/phpunit.xml.dist
+++ b/phpunit.xml.dist
@@ -20,14 +20,14 @@
         <whitelist processUncoveredFilesFromWhitelist="true">
             <directory suffix=".php">./system</directory>
             <exclude>
-                <file>./system/bootstrap.php</file>
-                <file>./system/Commands/Sessions/Views/migration.tpl.php</file>
-                <file>./system/ComposerScripts.php</file>
-                <file>./system/Config/Routes.php</file>
                 <directory>./system/Debug/Toolbar/Views</directory>
                 <directory>./system/Pager/Views</directory>
                 <directory>./system/ThirdParty</directory>
                 <directory>./system/Validation/Views</directory>
+                <file>./system/bootstrap.php</file>
+                <file>./system/Commands/Sessions/Views/migration.tpl.php</file>
+                <file>./system/ComposerScripts.php</file>
+                <file>./system/Config/Routes.php</file>
             </exclude>
         </whitelist>
     </filter>

From fc9add0305d9749beb58feac466860918f6e96c6 Mon Sep 17 00:00:00 2001
From: Lonnie Ezell <lonnieje@gmail.com>
Date: Wed, 6 Jun 2018 22:56:26 -0500
Subject: [PATCH 16/21] Provide a simpler default value for session table name.

---
 system/Commands/Sessions/CreateMigration.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/system/Commands/Sessions/CreateMigration.php b/system/Commands/Sessions/CreateMigration.php
index 34cccc6238..5c9427e067 100644
--- a/system/Commands/Sessions/CreateMigration.php
+++ b/system/Commands/Sessions/CreateMigration.php
@@ -104,7 +104,7 @@ class CreateMigration extends BaseCommand
 	{
 		$config = new App();
 
-		$tableName = CLI::getOption('t') ?? $config->sessionSavePath ?? 'ci_sessions';
+		$tableName = CLI::getOption('t') ?? 'ci_sessions';
 
 		$path = APPPATH . 'Database/Migrations/' . date('YmdHis_') . 'create_' . $tableName . '_table' . '.php';
 

From bfc73e43104f91bad311e047f504119c1853effc Mon Sep 17 00:00:00 2001
From: Lonnie Ezell <lonnieje@gmail.com>
Date: Wed, 6 Jun 2018 22:56:52 -0500
Subject: [PATCH 17/21] Fix an issue with non-namespaced seeder.

---
 tests/system/Database/Live/AliasTest.php         | 2 +-
 tests/system/Database/Live/CIDbTestCaseTest.php  | 2 +-
 tests/system/Database/Live/CountTest.php         | 2 +-
 tests/system/Database/Live/DeleteTest.php        | 2 +-
 tests/system/Database/Live/EmptyTest.php         | 2 +-
 tests/system/Database/Live/ForgeTest.php         | 2 +-
 tests/system/Database/Live/FromTest.php          | 2 +-
 tests/system/Database/Live/GetTest.php           | 2 +-
 tests/system/Database/Live/GroupTest.php         | 2 +-
 tests/system/Database/Live/IncrementTest.php     | 2 +-
 tests/system/Database/Live/InsertTest.php        | 2 +-
 tests/system/Database/Live/JoinTest.php          | 2 +-
 tests/system/Database/Live/LikeTest.php          | 2 +-
 tests/system/Database/Live/LimitTest.php         | 2 +-
 tests/system/Database/Live/ModelTest.php         | 2 +-
 tests/system/Database/Live/OrderTest.php         | 2 +-
 tests/system/Database/Live/PreparedQueryTest.php | 2 +-
 tests/system/Database/Live/SelectTest.php        | 2 +-
 tests/system/Database/Live/UpdateTest.php        | 2 +-
 tests/system/Database/Live/WhereTest.php         | 2 +-
 20 files changed, 20 insertions(+), 20 deletions(-)

diff --git a/tests/system/Database/Live/AliasTest.php b/tests/system/Database/Live/AliasTest.php
index b5b933ca7b..f4d1f588c8 100644
--- a/tests/system/Database/Live/AliasTest.php
+++ b/tests/system/Database/Live/AliasTest.php
@@ -6,7 +6,7 @@ class AliasTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testAlias()
 	{
diff --git a/tests/system/Database/Live/CIDbTestCaseTest.php b/tests/system/Database/Live/CIDbTestCaseTest.php
index a025ad33a4..3cd083edd1 100644
--- a/tests/system/Database/Live/CIDbTestCaseTest.php
+++ b/tests/system/Database/Live/CIDbTestCaseTest.php
@@ -9,7 +9,7 @@ class CIDbTestCaseTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testHasInDatabase()
 	{
diff --git a/tests/system/Database/Live/CountTest.php b/tests/system/Database/Live/CountTest.php
index 804e860879..510061d7b9 100644
--- a/tests/system/Database/Live/CountTest.php
+++ b/tests/system/Database/Live/CountTest.php
@@ -9,7 +9,7 @@ class CountTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testCountReturnsZeroWithNoResults()
 	{
diff --git a/tests/system/Database/Live/DeleteTest.php b/tests/system/Database/Live/DeleteTest.php
index 048852ba21..6aa3578e04 100644
--- a/tests/system/Database/Live/DeleteTest.php
+++ b/tests/system/Database/Live/DeleteTest.php
@@ -10,7 +10,7 @@ class DeleteTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testDeleteThrowExceptionWithNoCriteria()
 	{
diff --git a/tests/system/Database/Live/EmptyTest.php b/tests/system/Database/Live/EmptyTest.php
index 33f53e8ef1..32575a99a9 100644
--- a/tests/system/Database/Live/EmptyTest.php
+++ b/tests/system/Database/Live/EmptyTest.php
@@ -9,7 +9,7 @@ class EmptyTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testEmpty()
 	{
diff --git a/tests/system/Database/Live/ForgeTest.php b/tests/system/Database/Live/ForgeTest.php
index b570467250..2492376542 100644
--- a/tests/system/Database/Live/ForgeTest.php
+++ b/tests/system/Database/Live/ForgeTest.php
@@ -9,7 +9,7 @@ class ForgeTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function setUp()
 	{
diff --git a/tests/system/Database/Live/FromTest.php b/tests/system/Database/Live/FromTest.php
index 9d3740f61f..1df87f02b6 100644
--- a/tests/system/Database/Live/FromTest.php
+++ b/tests/system/Database/Live/FromTest.php
@@ -9,7 +9,7 @@ class FromTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testFromCanAddTables()
 	{
diff --git a/tests/system/Database/Live/GetTest.php b/tests/system/Database/Live/GetTest.php
index 85349437ae..785a4753a8 100644
--- a/tests/system/Database/Live/GetTest.php
+++ b/tests/system/Database/Live/GetTest.php
@@ -9,7 +9,7 @@ class GetTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testGet()
 	{
diff --git a/tests/system/Database/Live/GroupTest.php b/tests/system/Database/Live/GroupTest.php
index 65dce2c6a5..5be00607d5 100644
--- a/tests/system/Database/Live/GroupTest.php
+++ b/tests/system/Database/Live/GroupTest.php
@@ -9,7 +9,7 @@ class GroupTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testGroupBy()
 	{
diff --git a/tests/system/Database/Live/IncrementTest.php b/tests/system/Database/Live/IncrementTest.php
index c5d7209cf0..69d1f6f599 100644
--- a/tests/system/Database/Live/IncrementTest.php
+++ b/tests/system/Database/Live/IncrementTest.php
@@ -9,7 +9,7 @@ class IncrementTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testIncrement()
 	{
diff --git a/tests/system/Database/Live/InsertTest.php b/tests/system/Database/Live/InsertTest.php
index 207c64627d..f29377e982 100644
--- a/tests/system/Database/Live/InsertTest.php
+++ b/tests/system/Database/Live/InsertTest.php
@@ -9,7 +9,7 @@ class InsertTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testInsert()
 	{
diff --git a/tests/system/Database/Live/JoinTest.php b/tests/system/Database/Live/JoinTest.php
index 5f3fb6c770..da3cd07026 100644
--- a/tests/system/Database/Live/JoinTest.php
+++ b/tests/system/Database/Live/JoinTest.php
@@ -9,7 +9,7 @@ class JoinTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testSimpleJoin()
 	{
diff --git a/tests/system/Database/Live/LikeTest.php b/tests/system/Database/Live/LikeTest.php
index 0922d4cc80..2e9d539a9b 100644
--- a/tests/system/Database/Live/LikeTest.php
+++ b/tests/system/Database/Live/LikeTest.php
@@ -9,7 +9,7 @@ class LikeTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testLikeDefault()
 	{
diff --git a/tests/system/Database/Live/LimitTest.php b/tests/system/Database/Live/LimitTest.php
index e7669ebdd5..ad5359b41e 100644
--- a/tests/system/Database/Live/LimitTest.php
+++ b/tests/system/Database/Live/LimitTest.php
@@ -9,7 +9,7 @@ class LimitTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testLimit()
 	{
diff --git a/tests/system/Database/Live/ModelTest.php b/tests/system/Database/Live/ModelTest.php
index 38807592f0..a42cd49781 100644
--- a/tests/system/Database/Live/ModelTest.php
+++ b/tests/system/Database/Live/ModelTest.php
@@ -20,7 +20,7 @@ class ModelTest extends CIDatabaseTestCase
 
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function setUp()
 	{
diff --git a/tests/system/Database/Live/OrderTest.php b/tests/system/Database/Live/OrderTest.php
index 512b7edb85..c239a10e87 100644
--- a/tests/system/Database/Live/OrderTest.php
+++ b/tests/system/Database/Live/OrderTest.php
@@ -9,7 +9,7 @@ class OrderTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testOrderAscending()
 	{
diff --git a/tests/system/Database/Live/PreparedQueryTest.php b/tests/system/Database/Live/PreparedQueryTest.php
index 7dc07b35a8..f283f3cc65 100644
--- a/tests/system/Database/Live/PreparedQueryTest.php
+++ b/tests/system/Database/Live/PreparedQueryTest.php
@@ -10,7 +10,7 @@ class PreparedQueryTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	//--------------------------------------------------------------------
 
diff --git a/tests/system/Database/Live/SelectTest.php b/tests/system/Database/Live/SelectTest.php
index b2829baca3..39c1d47abb 100644
--- a/tests/system/Database/Live/SelectTest.php
+++ b/tests/system/Database/Live/SelectTest.php
@@ -10,7 +10,7 @@ class SelectTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	//--------------------------------------------------------------------
 
diff --git a/tests/system/Database/Live/UpdateTest.php b/tests/system/Database/Live/UpdateTest.php
index b7e4f0735d..729f1ba96a 100644
--- a/tests/system/Database/Live/UpdateTest.php
+++ b/tests/system/Database/Live/UpdateTest.php
@@ -10,7 +10,7 @@ class UpdateTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testUpdateSetsAllWithoutWhere()
 	{
diff --git a/tests/system/Database/Live/WhereTest.php b/tests/system/Database/Live/WhereTest.php
index 84a3e778eb..aeea97b39b 100644
--- a/tests/system/Database/Live/WhereTest.php
+++ b/tests/system/Database/Live/WhereTest.php
@@ -9,7 +9,7 @@ class WhereTest extends CIDatabaseTestCase
 {
 	protected $refresh = true;
 
-	protected $seed = 'CITestSeeder';
+	protected $seed = 'Tests\Support\Database\Seeds\CITestSeeder';
 
 	public function testWhereSimpleKeyValue()
 	{

From 4f13dff1f0e75d64ec1e6cffac35a74bda1cd8de Mon Sep 17 00:00:00 2001
From: Lonnie Ezell <lonnieje@gmail.com>
Date: Wed, 6 Jun 2018 22:57:27 -0500
Subject: [PATCH 18/21] Safety so seeders don't get loaded multiple times.

---
 system/Database/Seeder.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/system/Database/Seeder.php b/system/Database/Seeder.php
index 441808a88b..1b412e5957 100644
--- a/system/Database/Seeder.php
+++ b/system/Database/Seeder.php
@@ -149,7 +149,7 @@ class Seeder
 
 			if ( ! class_exists($class, false))
 			{
-				require $path;
+				require_once $path;
 			}
 
 			$seeder = new $class($this->config);

From 2d5be738200159fc843f5494ffda23e9762ac568 Mon Sep 17 00:00:00 2001
From: Lonnie Ezell <lonnieje@gmail.com>
Date: Wed, 6 Jun 2018 22:58:29 -0500
Subject: [PATCH 19/21] Cannot unpack arrays with string keys. Fixes #1057

---
 system/HTTP/Response.php         | 3 +++
 system/Helpers/cookie_helper.php | 5 +----
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/system/HTTP/Response.php b/system/HTTP/Response.php
index 8abfda7a6d..44791bbc45 100644
--- a/system/HTTP/Response.php
+++ b/system/HTTP/Response.php
@@ -922,6 +922,9 @@ class Response extends Message implements ResponseInterface
 
 		foreach ($this->cookies as $params)
 		{
+			// PHP cannot unpack array with string keys
+			$params = array_values($params);
+
 			setcookie(...$params);
 		}
 	}
diff --git a/system/Helpers/cookie_helper.php b/system/Helpers/cookie_helper.php
index d4c09f7aa8..346404b613 100755
--- a/system/Helpers/cookie_helper.php
+++ b/system/Helpers/cookie_helper.php
@@ -73,10 +73,7 @@ if ( ! function_exists('set_cookie'))
 		// The following line shows as a syntax error in NetBeans IDE
 		//(\Config\Services::response())->setcookie
 		$response = \Config\Services::response();
-		$response->setcookie
-				(
-				$name, $value, $expire, $domain, $path, $prefix, $secure, $httpOnly
-		);
+		$response->setcookie($name, $value, $expire, $domain, $path, $prefix, $secure, $httpOnly);
 	}
 
 }

From fa64b01f56e8f6b88c9061d05cc02f692cd3c8e4 Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Thu, 7 Jun 2018 02:56:46 -0700
Subject: [PATCH 20/21] Flesh out Common tests - part 1

---
 tests/_support/View/Views/simple.php |   1 +
 tests/system/CommonFunctionsTest.php | 168 ++++++++++++++++++++++++---
 2 files changed, 151 insertions(+), 18 deletions(-)
 create mode 100644 tests/_support/View/Views/simple.php

diff --git a/tests/_support/View/Views/simple.php b/tests/_support/View/Views/simple.php
new file mode 100644
index 0000000000..afd6c25fe2
--- /dev/null
+++ b/tests/_support/View/Views/simple.php
@@ -0,0 +1 @@
+<h1><?= $testString ?></h1>
\ No newline at end of file
diff --git a/tests/system/CommonFunctionsTest.php b/tests/system/CommonFunctionsTest.php
index e5ac5b327c..7c334120fa 100644
--- a/tests/system/CommonFunctionsTest.php
+++ b/tests/system/CommonFunctionsTest.php
@@ -1,5 +1,8 @@
 <?php
 
+use Tests\Support\Autoloader\MockFileLocator;
+use CodeIgniter\Router\RouteCollection;
+
 /**
  * @backupGlobals enabled
  */
@@ -12,7 +15,7 @@ class CommomFunctionsTest extends \CIUnitTestCase
 	{
 		parent::setUp();
 
-	    unset($_ENV['foo'], $_SERVER['foo']);
+		unset($_ENV['foo'], $_SERVER['foo']);
 	}
 
 	//--------------------------------------------------------------------
@@ -48,40 +51,169 @@ class CommomFunctionsTest extends \CIUnitTestCase
 
 	// ------------------------------------------------------------------------
 
-    public function testEnvReturnsDefault()
-    {
-        $this->assertEquals('baz', env('foo', 'baz'));
-    }
+	public function testEnvReturnsDefault()
+	{
+		$this->assertEquals('baz', env('foo', 'baz'));
+	}
 
-    public function testEnvGetsFromSERVER()
-    {
-        $_SERVER['foo'] = 'bar';
+	public function testEnvGetsFromSERVER()
+	{
+		$_SERVER['foo'] = 'bar';
 
-        $this->assertEquals('bar', env('foo', 'baz'));
-    }
+		$this->assertEquals('bar', env('foo', 'baz'));
+	}
 
-    public function testEnvGetsFromENV()
-    {
-        $_ENV['foo'] = 'bar';
+	public function testEnvGetsFromENV()
+	{
+		$_ENV['foo'] = 'bar';
 
-        $this->assertEquals('bar', env('foo', 'baz'));
-    }
+		$this->assertEquals('bar', env('foo', 'baz'));
+	}
+
+	public function testEnvBooleans()
+	{
+		$_ENV['p1'] = 'true';
+		$_ENV['p2'] = 'false';
+		$_ENV['p3'] = 'empty';
+		$_ENV['p4'] = 'null';
+
+		$this->assertTrue(env('p1'));
+		$this->assertFalse(env('p2'));
+		$this->assertEmpty(env('p3'));
+		$this->assertNull(env('p4'));
+	}
+
+	// ------------------------------------------------------------------------
 
 	public function testRedirectReturnsRedirectResponse()
 	{
 		$_SERVER['REQUEST_METHOD'] = 'GET';
 
 		$response = $this->createMock(\CodeIgniter\HTTP\Response::class);
-		$routes   = new \CodeIgniter\Router\RouteCollection(new \Tests\Support\Autoloader\MockFileLocator(new \Config\Autoload()));
+		$routes = new \CodeIgniter\Router\RouteCollection(new \Tests\Support\Autoloader\MockFileLocator(new \Config\Autoload()));
 		\CodeIgniter\Services::injectMock('response', $response);
 		\CodeIgniter\Services::injectMock('routes', $routes);
 
 		$routes->add('home/base', 'Controller::index', ['as' => 'base']);
 
 		$response->method('redirect')
-			->will($this->returnArgument(0));
+				->will($this->returnArgument(0));
 
 		$this->assertInstanceOf(\CodeIgniter\HTTP\RedirectResponse::class, redirect('base'));
-    }
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function testView()
+	{
+		$data = [
+			'testString' => 'bar',
+			'bar'		 => 'baz',
+		];
+		$expected = '<h1>bar</h1>';
+		$this->assertContains($expected, view('\Tests\Support\View\Views\simple', $data, []));
+	}
+
+	public function testViewSavedData()
+	{
+		$data = [
+			'testString' => 'bar',
+			'bar'		 => 'baz',
+		];
+		$expected = '<h1>bar</h1>';
+		$this->assertContains($expected, view('\Tests\Support\View\Views\simple', $data, ['saveData' => true]));
+		$this->assertContains($expected, view('\Tests\Support\View\Views\simple'));
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function testViewCell()
+	{
+		$expected = 'Hello';
+		$this->assertEquals($expected, view_cell('\CodeIgniter\View\SampleClass::hello'));
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function testEscapeBadContext()
+	{
+		$this->expectException(InvalidArgumentException::class);
+		esc(['width' => '800', 'height' => '600'], 'bogus');
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function testSessionInstance()
+	{
+		$this->assertInstanceOf(CodeIgniter\Session\Session::class, session());
+	}
+
+	public function testSessionVariable()
+	{
+		$_SESSION['notbogus'] = 'Hi there';
+		$this->assertEquals('Hi there', session('notbogus'));
+	}
+
+	public function testSessionVariableNotThere()
+	{
+		$_SESSION['bogus'] = 'Hi there';
+		$this->assertEquals(null, session('notbogus'));
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function testSingleService()
+	{
+		$timer1 = single_service('timer');
+		$timer2 = single_service('timer');
+		$this->assertFalse($timer1 === $timer2);
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function testRouteTo()
+	{
+		// prime the pump
+		$routes = service('routes');
+		$routes->add('path/(:any)/to/(:num)', 'myController::goto/$1/$2');
+
+		$this->assertEquals('/path/string/to/13', route_to('myController::goto', 'string', 13));
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function testInvisible()
+	{
+		$this->assertEquals('Javascript', remove_invisible_characters("Java\0script"));
+	}
+
+	public function testInvisibleEncoded()
+	{
+		$this->assertEquals('Javascript', remove_invisible_characters("Java%0cscript", true));
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function testAppTimezone()
+	{
+		$this->assertEquals('America/Chicago', app_timezone());
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function testCSRFToken()
+	{
+		$this->assertEquals('csrf_test_name', csrf_token());
+	}
+
+	public function testHash()
+	{
+		$this->assertEquals(32, strlen(csrf_hash()));
+	}
+
+	public function testCSRFField()
+	{
+		$this->assertContains('<input type="hidden" ', csrf_field());
+	}
 
 }

From 94bce7c873e4930c6e2f8c52a8fff60278cb8d6d Mon Sep 17 00:00:00 2001
From: Master Yoda <jim_parry@bcit.ca>
Date: Thu, 7 Jun 2018 20:50:03 -0700
Subject: [PATCH 21/21] Part 2 of expanded common function testing

---
 system/Common.php                    | 11 +++++
 tests/system/CommonFunctionsTest.php | 61 +++++++++++++++++++++++++++-
 2 files changed, 71 insertions(+), 1 deletion(-)

diff --git a/system/Common.php b/system/Common.php
index 48bb5bddbe..61380b9ff7 100644
--- a/system/Common.php
+++ b/system/Common.php
@@ -440,8 +440,10 @@ if ( ! function_exists('log_message'))
 			return $logger->log($level, $message, $context);
 		}
 
+		// @codeCoverageIgnoreStart
 		return Services::logger(true)
 						->log($level, $message, $context);
+		// @codeCoverageIgnoreEnd
 	}
 
 }
@@ -668,6 +670,9 @@ if ( ! function_exists('force_https'))
 	 *                                    Defaults to 1 year.
 	 * @param RequestInterface  $request
 	 * @param ResponseInterface $response
+	 * 
+	 * Not testable, as it will exit!
+	 * @codeCoverageIgnore
 	 */
 	function force_https(int $duration = 31536000, RequestInterface $request = null, ResponseInterface $response = null)
 	{
@@ -837,6 +842,8 @@ if ( ! function_exists('is_really_writable'))
 	 * @param   string $file
 	 *
 	 * @return  bool
+	 * 
+	 * @codeCoverageIgnore	Not practical to test, as travis runs on linux
 	 */
 	function is_really_writable($file)
 	{
@@ -931,6 +938,8 @@ if ( ! function_exists('function_usable'))
 	 * @param	string	$function_name	Function to check for
 	 * @return	bool	TRUE if the function exists and is safe to call,
 	 * 			FALSE otherwise.
+	 * 
+	 * @codeCoverageIgnore	This is too exotic
 	 */
 	function function_usable($function_name)
 	{
@@ -959,6 +968,8 @@ if (! function_exists('dd'))
 	 * Prints a Kint debug report and exits.
 	 *
 	 * @param array ...$vars
+	 * 
+	 * @codeCoverageIgnore	Can't be tested ... exits
 	 */
 	function dd(...$vars)
 	{
diff --git a/tests/system/CommonFunctionsTest.php b/tests/system/CommonFunctionsTest.php
index 7c334120fa..0c88d29c25 100644
--- a/tests/system/CommonFunctionsTest.php
+++ b/tests/system/CommonFunctionsTest.php
@@ -1,7 +1,15 @@
 <?php
 
-use Tests\Support\Autoloader\MockFileLocator;
+use Config\App;
+use Config\Autoload;
+use CodeIgniter\Config\Services;
 use CodeIgniter\Router\RouteCollection;
+use CodeIgniter\HTTP\RequestResponse;
+use CodeIgniter\HTTP\RedirectResponse;
+use CodeIgniter\HTTP\URI;
+use CodeIgniter\HTTP\UserAgent;
+use Tests\Support\Autoloader\MockFileLocator;
+use Tests\Support\HTTP\MockIncomingRequest;
 
 /**
  * @backupGlobals enabled
@@ -102,6 +110,11 @@ class CommomFunctionsTest extends \CIUnitTestCase
 		$this->assertInstanceOf(\CodeIgniter\HTTP\RedirectResponse::class, redirect('base'));
 	}
 
+	public function testRedirectDefault()
+	{
+		$this->assertInstanceOf(\CodeIgniter\HTTP\RedirectResponse::class, redirect());
+	}
+
 	// ------------------------------------------------------------------------
 
 	public function testView()
@@ -216,4 +229,50 @@ class CommomFunctionsTest extends \CIUnitTestCase
 		$this->assertContains('<input type="hidden" ', csrf_field());
 	}
 
+	// ------------------------------------------------------------------------
+
+	public function testOldInput()
+	{
+		// setup from RedirectResponseTest...
+		$_SERVER['REQUEST_METHOD'] = 'GET';
+
+		$this->config = new App();
+		$this->config->baseURL = 'http://example.com';
+
+		$this->routes = new RouteCollection(new MockFileLocator(new Autoload()));
+		Services::injectMock('routes', $this->routes);
+
+		$this->request = new MockIncomingRequest($this->config, new URI('http://example.com'), null, new UserAgent());
+		Services::injectMock('request', $this->request);
+
+		// setup & ask for a redirect...
+		$_SESSION = [];
+		$_GET = ['foo' => 'bar'];
+		$_POST = ['bar' => 'baz', 'zibble' => serialize('fritz')];
+
+		$response = new RedirectResponse(new App());
+		$returned = $response->withInput();
+
+		$this->assertEquals('bar', old('foo')); // regular parameter
+		$this->assertEquals('doo', old('yabba dabba', 'doo')); // non-existing parameter
+		$this->assertEquals('fritz', old('zibble')); // serialized parameter
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function testReallyWritable()
+	{
+		// cannot test fully on *nix
+		$this->assertTrue(is_really_writable(WRITEPATH));
+	}
+
+	// ------------------------------------------------------------------------
+
+	public function testSlashItem()
+	{
+		$this->assertEquals('/', slash_item('cookiePath')); // slash already there
+		$this->assertEquals('', slash_item('cookieDomain')); // empty, so untouched
+		$this->assertEquals('en/', slash_item('defaultLocale')); // slash appended
+	}
+
 }