Index: src/wp-includes/canonical.php
===================================================================
--- src/wp-includes/canonical.php	(revision 36259)
+++ src/wp-includes/canonical.php	(working copy)
@@ -430,32 +430,39 @@ function redirect_canonical( $requested_
 	} elseif ( is_front_page() ) {
 		$redirect['path'] = trailingslashit($redirect['path']);
 	}
 
 	// Strip multiple slashes out of the URL
 	if ( strpos($redirect['path'], '//') > -1 )
 		$redirect['path'] = preg_replace('|/+|', '/', $redirect['path']);
 
 	// Always trailing slash the Front Page URL
 	if ( trailingslashit( $redirect['path'] ) == trailingslashit( $user_home['path'] ) )
 		$redirect['path'] = trailingslashit($redirect['path']);
 
 	// Ignore differences in host capitalization, as this can lead to infinite redirects
 	// Only redirect no-www <=> yes-www
 	if ( strtolower($original['host']) == strtolower($redirect['host']) ||
-		( strtolower($original['host']) != 'www.' . strtolower($redirect['host']) && 'www.' . strtolower($original['host']) != strtolower($redirect['host']) ) )
+		( strtolower($original['host']) != 'www.' . strtolower($redirect['host']) && 'www.' . strtolower($original['host']) != strtolower($redirect['host']) ) ) {
+		// Replace the hostname in $redirect_url in the event it's not rebuilt below.
+		$redirect_url = preg_replace(
+			'!^' . preg_quote( $redirect['scheme'] . '://' . $redirect['host'] . '/', '!' ) . '!',
+			$redirect['scheme'] . '://' . $original['host'] . '/',
+			$redirect_url
+		);
 		$redirect['host'] = $original['host'];
+	}
 
 	$compare_original = array( $original['host'], $original['path'] );
 
 	if ( !empty( $original['port'] ) )
 		$compare_original[] = $original['port'];
 
 	if ( !empty( $original['query'] ) )
 		$compare_original[] = $original['query'];
 
 	$compare_redirect = array( $redirect['host'], $redirect['path'] );
 
 	if ( !empty( $redirect['port'] ) )
 		$compare_redirect[] = $redirect['port'];
 
 	if ( !empty( $redirect['query'] ) )
Index: tests/phpunit/includes/testcase-canonical.php
===================================================================
--- tests/phpunit/includes/testcase-canonical.php	(revision 36259)
+++ tests/phpunit/includes/testcase-canonical.php	(working copy)
@@ -146,89 +146,103 @@ class WP_Canonical_UnitTestCase extends 
 
 		foreach ( self::$term_ids as $tid => $tax ) {
 			wp_delete_term( $tid, $tax );
 		}
 
 		self::$author_id = null;
 		self::$post_ids = array();
 		self::$comment_ids = array();
 		self::$term_ids = array();
 		self::$terms = array();
 	}
 
 	/**
 	 * Assert that a given URL is the same a the canonical URL generated by WP.
 	 *
+	 * This runs the canonical tests with the home_url hostname being forced to uppercase in addition to standard.
+	 *
 	 * @since 4.1.0
 	 *
 	 * @param string $test_url                Raw URL that will be run through redirect_canonical().
 	 * @param string $expected                Expected string.
 	 * @param int    $ticket                  Optional. Trac ticket number.
 	 * @param array  $expected_doing_it_wrong Array of class/function names expected to throw _doing_it_wrong() notices.
 	 */
 	public function assertCanonical( $test_url, $expected, $ticket = 0, $expected_doing_it_wrong = array() ) {
+		$test_url = home_url( $test_url );
+
+		$this->_assertCanonical( $test_url, $expected, $ticket, $expected_doing_it_wrong );
+
+		// Re-test with the home_url hostname forced to uppercase.
+		add_filter( 'home_url', array( $this, 'filter_uppercase_hostname' ) );
+		$this->_assertCanonical( $test_url, $expected, $ticket, $expected_doing_it_wrong );
+	}
+
+	/**
+	 * Internal helper for `::assertCanonical` for force an uppercase hostname in `home_url()`.
+	 *
+	 * @ignore
+	 */
+	public function filter_uppercase_hostname( $url ) {
+		return str_replace( '://' . WP_TESTS_DOMAIN, '://' . strtoupper( WP_TESTS_DOMAIN ), $url );
+	}
+
+	/**
+	 * Internal helper for `::assertCanonical`.
+	 *
+	 * @ignore
+	 */
+	public function _assertCanonical( $test_url, $expected, $ticket = 0, $expected_doing_it_wrong = array() ) {
 		$this->expected_doing_it_wrong = array_merge( $this->expected_doing_it_wrong, (array) $expected_doing_it_wrong );
 
 		if ( $ticket )
 			$this->knownWPBug( $ticket );
 
 		$ticket_ref = ($ticket > 0) ? 'Ticket #' . $ticket : null;
 
 		if ( is_string($expected) )
 			$expected = array('url' => $expected);
 		elseif ( is_array($expected) && !isset($expected['url']) && !isset($expected['qv']) )
 			$expected = array( 'qv' => $expected );
 
 		if ( !isset($expected['url']) && !isset($expected['qv']) )
 			$this->markTestSkipped('No valid expected output was provided');
 
-		$this->go_to( home_url( $test_url ) );
+		$this->go_to( $test_url );
 
 		// Does the redirect match what's expected?
-		$can_url = $this->get_canonical( $test_url );
+		$can_response = redirect_canonical( null, false );
+		$can_url = $can_response ? $can_response : $test_url;
 		$parsed_can_url = parse_url($can_url);
 
-		// Just test the Path and Query if present
-		if ( isset($expected['url']) ) {
-			$this->assertEquals( $expected['url'], $parsed_can_url['path'] . (!empty($parsed_can_url['query']) ? '?' . $parsed_can_url['query'] : ''), $ticket_ref );
+		if ( isset( $expected['url'] ) ) {
+			if ( $test_url == $expected['url'] ) {
+				$this->assertNull( $can_response, $ticket_ref );
+			} else {
+				$this->assertEquals( WP_TESTS_DOMAIN, $parsed_can_url['host'] );
+				$this->assertEquals( $expected['url'], $parsed_can_url['path'] . (!empty($parsed_can_url['query']) ? '?' . $parsed_can_url['query'] : ''), $ticket_ref );
+			}
 		}
 
 		if ( ! isset($expected['qv']) )
 			return;
 
 		// "make" that the request and check the query is correct
 		$this->go_to( $can_url );
 
 		// Are all query vars accounted for, And correct?
 		global $wp;
 
 		$query_vars = array_diff($wp->query_vars, $wp->extra_query_vars);
 		if ( !empty($parsed_can_url['query']) ) {
 			parse_str($parsed_can_url['query'], $_qv);
 
 			// $_qv should not contain any elements which are set in $query_vars already (ie. $_GET vars should not be present in the Rewrite)
 			$this->assertEquals( array(), array_intersect( $query_vars, $_qv ), 'Query vars are duplicated from the Rewrite into $_GET; ' . $ticket_ref );
 
 			$query_vars = array_merge($query_vars, $_qv);
 		}
 
 		$this->assertEquals( $expected['qv'], $query_vars );
 	}
 
-	/**
-	 * Get the canonical URL given a raw URL.
-	 *
-	 * @param string $test_url Should be relative to the site "front", ie /category/uncategorized/
-	 *                         as opposed to http://example.com/category/uncategorized/
-	 * @return $can_url Returns the original $test_url if no canonical can be generated, otherwise returns
-	 *                  the fully-qualified URL as generated by redirect_canonical().
-	 */
-	public function get_canonical( $test_url ) {
-		$test_url = home_url( $test_url );
-
-		$can_url = redirect_canonical( $test_url, false );
-		if ( ! $can_url )
-			return $test_url; // No redirect will take place for this request
-
-		return $can_url;
-	}
 }
Index: tests/phpunit/tests/canonical/pageOnFront.php
===================================================================
--- tests/phpunit/tests/canonical/pageOnFront.php	(revision 36259)
+++ tests/phpunit/tests/canonical/pageOnFront.php	(working copy)
@@ -32,24 +32,25 @@ class Tests_Canonical_PageOnFront extend
 	function data() {
 		/* Format:
 		 * [0]: $test_url,
 		 * [1]: expected results: Any of the following can be used
 		 *      array( 'url': expected redirection location, 'qv': expected query vars to be set via the rewrite AND $_GET );
 		 *      array( expected query vars to be set, same as 'qv' above )
 		 *      (string) expected redirect location
 		 * [3]: (optional) The ticket the test refers to, Can be skipped if unknown.
 		 */
 		 return array(
 			// Check against an odd redirect
 			array( '/page/2/', '/page/2/' ),
 			array( '/?page=2', '/page/2/' ),
 			array( '/page/1/', '/' ),
 			array( '/?page=1', '/' ),
+			array( '/', '/' /*, 21602 */ ),
 
 			// The page designated as the front page should redirect to the front of the site
 			array( '/front-page/', '/' ),
 			array( '/front-page/2/', '/page/2/' ),
 			array( '/front-page/?page=2', '/page/2/' ),
 			array( '/blog-page/?paged=2', '/blog-page/page/2/' ),
 		 );
 	}
 }
