common.test

Tests for common.inc functionality.

File

drupal/modules/simpletest/tests/common.test
View source
<?php

/**
 * @file
 * Tests for common.inc functionality.
 */

/**
 * Tests for URL generation functions.
 */
class DrupalAlterTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'drupal_alter() tests',
      'description' => 'Confirm that alteration of arguments passed to drupal_alter() works correctly.',
      'group' => 'System',
    );
  }
  function setUp() {
    parent::setUp('common_test');
  }
  function testDrupalAlter() {

    // This test depends on Bartik, so make sure that it is always the current
    // active theme.
    global $theme, $base_theme_info;
    $theme = 'bartik';
    $base_theme_info = array();
    $array = array(
      'foo' => 'bar',
    );
    $entity = new stdClass();
    $entity->foo = 'bar';

    // Verify alteration of a single argument.
    $array_copy = $array;
    $array_expected = array(
      'foo' => 'Drupal theme',
    );
    drupal_alter('drupal_alter', $array_copy);
    $this
      ->assertEqual($array_copy, $array_expected, 'Single array was altered.');
    $entity_copy = clone $entity;
    $entity_expected = clone $entity;
    $entity_expected->foo = 'Drupal theme';
    drupal_alter('drupal_alter', $entity_copy);
    $this
      ->assertEqual($entity_copy, $entity_expected, 'Single object was altered.');

    // Verify alteration of multiple arguments.
    $array_copy = $array;
    $array_expected = array(
      'foo' => 'Drupal theme',
    );
    $entity_copy = clone $entity;
    $entity_expected = clone $entity;
    $entity_expected->foo = 'Drupal theme';
    $array2_copy = $array;
    $array2_expected = array(
      'foo' => 'Drupal theme',
    );
    drupal_alter('drupal_alter', $array_copy, $entity_copy, $array2_copy);
    $this
      ->assertEqual($array_copy, $array_expected, 'First argument to drupal_alter() was altered.');
    $this
      ->assertEqual($entity_copy, $entity_expected, 'Second argument to drupal_alter() was altered.');
    $this
      ->assertEqual($array2_copy, $array2_expected, 'Third argument to drupal_alter() was altered.');

    // Verify alteration order when passing an array of types to drupal_alter().
    // common_test_module_implements_alter() places 'block' implementation after
    // other modules.
    $array_copy = $array;
    $array_expected = array(
      'foo' => 'Drupal block theme',
    );
    drupal_alter(array(
      'drupal_alter',
      'drupal_alter_foo',
    ), $array_copy);
    $this
      ->assertEqual($array_copy, $array_expected, 'hook_TYPE_alter() implementations ran in correct order.');
  }

}

/**
 * Tests for URL generation functions.
 *
 * url() calls module_implements(), which may issue a db query, which requires
 * inheriting from a web test case rather than a unit test case.
 */
class CommonURLUnitTest extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'URL generation tests',
      'description' => 'Confirm that url(), drupal_get_query_parameters(), drupal_http_build_query(), and l() work correctly with various input.',
      'group' => 'System',
    );
  }

  /**
   * Confirm that invalid text given as $path is filtered.
   */
  function testLXSS() {
    $text = $this
      ->randomName();
    $path = "<SCRIPT>alert('XSS')</SCRIPT>";
    $link = l($text, $path);
    $sanitized_path = check_url(url($path));
    $this
      ->assertTrue(strpos($link, $sanitized_path) !== FALSE, format_string('XSS attack @path was filtered', array(
      '@path' => $path,
    )));
  }

  /*
   * Tests for active class in l() function.
   */
  function testLActiveClass() {
    $link = l($this
      ->randomName(), $_GET['q']);
    $this
      ->assertTrue($this
      ->hasClass($link, 'active'), format_string('Class @class is present on link to the current page', array(
      '@class' => 'active',
    )));
  }

  /**
   * Tests for custom class in l() function.
   */
  function testLCustomClass() {
    $class = $this
      ->randomName();
    $link = l($this
      ->randomName(), $_GET['q'], array(
      'attributes' => array(
        'class' => array(
          $class,
        ),
      ),
    ));
    $this
      ->assertTrue($this
      ->hasClass($link, $class), format_string('Custom class @class is present on link when requested', array(
      '@class' => $class,
    )));
    $this
      ->assertTrue($this
      ->hasClass($link, 'active'), format_string('Class @class is present on link to the current page', array(
      '@class' => 'active',
    )));
  }
  private function hasClass($link, $class) {
    return preg_match('|class="([^\\"\\s]+\\s+)*' . $class . '|', $link);
  }

  /**
   * Test drupal_get_query_parameters().
   */
  function testDrupalGetQueryParameters() {
    $original = array(
      'a' => 1,
      'b' => array(
        'd' => 4,
        'e' => array(
          'f' => 5,
        ),
      ),
      'c' => 3,
      'q' => 'foo/bar',
    );

    // Default arguments.
    $result = $_GET;
    unset($result['q']);
    $this
      ->assertEqual(drupal_get_query_parameters(), $result, "\$_GET['q'] was removed.");

    // Default exclusion.
    $result = $original;
    unset($result['q']);
    $this
      ->assertEqual(drupal_get_query_parameters($original), $result, "'q' was removed.");

    // First-level exclusion.
    $result = $original;
    unset($result['b']);
    $this
      ->assertEqual(drupal_get_query_parameters($original, array(
      'b',
    )), $result, "'b' was removed.");

    // Second-level exclusion.
    $result = $original;
    unset($result['b']['d']);
    $this
      ->assertEqual(drupal_get_query_parameters($original, array(
      'b[d]',
    )), $result, "'b[d]' was removed.");

    // Third-level exclusion.
    $result = $original;
    unset($result['b']['e']['f']);
    $this
      ->assertEqual(drupal_get_query_parameters($original, array(
      'b[e][f]',
    )), $result, "'b[e][f]' was removed.");

    // Multiple exclusions.
    $result = $original;
    unset($result['a'], $result['b']['e'], $result['c']);
    $this
      ->assertEqual(drupal_get_query_parameters($original, array(
      'a',
      'b[e]',
      'c',
    )), $result, "'a', 'b[e]', 'c' were removed.");
  }

  /**
   * Test drupal_http_build_query().
   */
  function testDrupalHttpBuildQuery() {
    $this
      ->assertEqual(drupal_http_build_query(array(
      'a' => ' &#//+%20@۞',
    )), 'a=%20%26%23//%2B%2520%40%DB%9E', 'Value was properly encoded.');
    $this
      ->assertEqual(drupal_http_build_query(array(
      ' &#//+%20@۞' => 'a',
    )), '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', 'Key was properly encoded.');
    $this
      ->assertEqual(drupal_http_build_query(array(
      'a' => '1',
      'b' => '2',
      'c' => '3',
    )), 'a=1&b=2&c=3', 'Multiple values were properly concatenated.');
    $this
      ->assertEqual(drupal_http_build_query(array(
      'a' => array(
        'b' => '2',
        'c' => '3',
      ),
      'd' => 'foo',
    )), 'a%5Bb%5D=2&a%5Bc%5D=3&d=foo', 'Nested array was properly encoded.');
  }

  /**
   * Test drupal_parse_url().
   */
  function testDrupalParseUrl() {

    // Relative URL.
    $url = 'foo/bar?foo=bar&bar=baz&baz#foo';
    $result = array(
      'path' => 'foo/bar',
      'query' => array(
        'foo' => 'bar',
        'bar' => 'baz',
        'baz' => '',
      ),
      'fragment' => 'foo',
    );
    $this
      ->assertEqual(drupal_parse_url($url), $result, 'Relative URL parsed correctly.');

    // Relative URL that is known to confuse parse_url().
    $url = 'foo/bar:1';
    $result = array(
      'path' => 'foo/bar:1',
      'query' => array(),
      'fragment' => '',
    );
    $this
      ->assertEqual(drupal_parse_url($url), $result, 'Relative URL parsed correctly.');

    // Absolute URL.
    $url = '/foo/bar?foo=bar&bar=baz&baz#foo';
    $result = array(
      'path' => '/foo/bar',
      'query' => array(
        'foo' => 'bar',
        'bar' => 'baz',
        'baz' => '',
      ),
      'fragment' => 'foo',
    );
    $this
      ->assertEqual(drupal_parse_url($url), $result, 'Absolute URL parsed correctly.');

    // External URL testing.
    $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo';

    // Test that drupal can recognize an absolute URL. Used to prevent attack vectors.
    $this
      ->assertTrue(url_is_external($url), 'Correctly identified an external URL.');

    // External URL without an explicit protocol.
    $url = '//drupal.org/foo/bar?foo=bar&bar=baz&baz#foo';
    $this
      ->assertTrue(url_is_external($url), 'Correctly identified an external URL without a protocol part.');

    // Internal URL starting with a slash.
    $url = '/drupal.org';
    $this
      ->assertFalse(url_is_external($url), 'Correctly identified an internal URL with a leading slash.');

    // Test the parsing of absolute URLs.
    $url = 'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo';
    $result = array(
      'path' => 'http://drupal.org/foo/bar',
      'query' => array(
        'foo' => 'bar',
        'bar' => 'baz',
        'baz' => '',
      ),
      'fragment' => 'foo',
    );
    $this
      ->assertEqual(drupal_parse_url($url), $result, 'External URL parsed correctly.');

    // Verify proper parsing of URLs when clean URLs are disabled.
    $result = array(
      'path' => 'foo/bar',
      'query' => array(
        'bar' => 'baz',
      ),
      'fragment' => 'foo',
    );

    // Non-clean URLs #1: Absolute URL generated by url().
    $url = $GLOBALS['base_url'] . '/?q=foo/bar&bar=baz#foo';
    $this
      ->assertEqual(drupal_parse_url($url), $result, 'Absolute URL with clean URLs disabled parsed correctly.');

    // Non-clean URLs #2: Relative URL generated by url().
    $url = '?q=foo/bar&bar=baz#foo';
    $this
      ->assertEqual(drupal_parse_url($url), $result, 'Relative URL with clean URLs disabled parsed correctly.');

    // Non-clean URLs #3: URL generated by url() on non-Apache webserver.
    $url = 'index.php?q=foo/bar&bar=baz#foo';
    $this
      ->assertEqual(drupal_parse_url($url), $result, 'Relative URL on non-Apache webserver with clean URLs disabled parsed correctly.');

    // Test that drupal_parse_url() does not allow spoofing a URL to force a malicious redirect.
    $parts = drupal_parse_url('forged:http://cwe.mitre.org/data/definitions/601.html');
    $this
      ->assertFalse(valid_url($parts['path'], TRUE), 'drupal_parse_url() correctly parsed a forged URL.');
  }

  /**
   * Test url() with/without query, with/without fragment, absolute on/off and
   * assert all that works when clean URLs are on and off.
   */
  function testUrl() {
    global $base_url;
    foreach (array(
      FALSE,
      TRUE,
    ) as $absolute) {

      // Get the expected start of the path string.
      $base = $absolute ? $base_url . '/' : base_path();
      $absolute_string = $absolute ? 'absolute' : NULL;

      // Disable Clean URLs.
      $GLOBALS['conf']['clean_url'] = 0;
      $url = $base . '?q=node/123';
      $result = url('node/123', array(
        'absolute' => $absolute,
      ));
      $this
        ->assertEqual($url, $result, "{$url} == {$result}");
      $url = $base . '?q=node/123#foo';
      $result = url('node/123', array(
        'fragment' => 'foo',
        'absolute' => $absolute,
      ));
      $this
        ->assertEqual($url, $result, "{$url} == {$result}");
      $url = $base . '?q=node/123&foo';
      $result = url('node/123', array(
        'query' => array(
          'foo' => NULL,
        ),
        'absolute' => $absolute,
      ));
      $this
        ->assertEqual($url, $result, "{$url} == {$result}");
      $url = $base . '?q=node/123&foo=bar&bar=baz';
      $result = url('node/123', array(
        'query' => array(
          'foo' => 'bar',
          'bar' => 'baz',
        ),
        'absolute' => $absolute,
      ));
      $this
        ->assertEqual($url, $result, "{$url} == {$result}");
      $url = $base . '?q=node/123&foo#bar';
      $result = url('node/123', array(
        'query' => array(
          'foo' => NULL,
        ),
        'fragment' => 'bar',
        'absolute' => $absolute,
      ));
      $this
        ->assertEqual($url, $result, "{$url} == {$result}");
      $url = $base . '?q=node/123&foo#0';
      $result = url('node/123', array(
        'query' => array(
          'foo' => NULL,
        ),
        'fragment' => '0',
        'absolute' => $absolute,
      ));
      $this
        ->assertEqual($url, $result, "{$url} == {$result}");
      $url = $base . '?q=node/123&foo';
      $result = url('node/123', array(
        'query' => array(
          'foo' => NULL,
        ),
        'fragment' => '',
        'absolute' => $absolute,
      ));
      $this
        ->assertEqual($url, $result, "{$url} == {$result}");
      $url = $base;
      $result = url('<front>', array(
        'absolute' => $absolute,
      ));
      $this
        ->assertEqual($url, $result, "{$url} == {$result}");

      // Enable Clean URLs.
      $GLOBALS['conf']['clean_url'] = 1;
      $url = $base . 'node/123';
      $result = url('node/123', array(
        'absolute' => $absolute,
      ));
      $this
        ->assertEqual($url, $result, "{$url} == {$result}");
      $url = $base . 'node/123#foo';
      $result = url('node/123', array(
        'fragment' => 'foo',
        'absolute' => $absolute,
      ));
      $this
        ->assertEqual($url, $result, "{$url} == {$result}");
      $url = $base . 'node/123?foo';
      $result = url('node/123', array(
        'query' => array(
          'foo' => NULL,
        ),
        'absolute' => $absolute,
      ));
      $this
        ->assertEqual($url, $result, "{$url} == {$result}");
      $url = $base . 'node/123?foo=bar&bar=baz';
      $result = url('node/123', array(
        'query' => array(
          'foo' => 'bar',
          'bar' => 'baz',
        ),
        'absolute' => $absolute,
      ));
      $this
        ->assertEqual($url, $result, "{$url} == {$result}");
      $url = $base . 'node/123?foo#bar';
      $result = url('node/123', array(
        'query' => array(
          'foo' => NULL,
        ),
        'fragment' => 'bar',
        'absolute' => $absolute,
      ));
      $this
        ->assertEqual($url, $result, "{$url} == {$result}");
      $url = $base;
      $result = url('<front>', array(
        'absolute' => $absolute,
      ));
      $this
        ->assertEqual($url, $result, "{$url} == {$result}");
    }
  }

  /**
   * Test external URL handling.
   */
  function testExternalUrls() {
    $test_url = 'http://drupal.org/';

    // Verify external URL can contain a fragment.
    $url = $test_url . '#drupal';
    $result = url($url);
    $this
      ->assertEqual($url, $result, 'External URL with fragment works without a fragment in $options.');

    // Verify fragment can be overidden in an external URL.
    $url = $test_url . '#drupal';
    $fragment = $this
      ->randomName(10);
    $result = url($url, array(
      'fragment' => $fragment,
    ));
    $this
      ->assertEqual($test_url . '#' . $fragment, $result, 'External URL fragment is overidden with a custom fragment in $options.');

    // Verify external URL can contain a query string.
    $url = $test_url . '?drupal=awesome';
    $result = url($url);
    $this
      ->assertEqual($url, $result, 'External URL with query string works without a query string in $options.');

    // Verify external URL can be extended with a query string.
    $url = $test_url;
    $query = array(
      $this
        ->randomName(5) => $this
        ->randomName(5),
    );
    $result = url($url, array(
      'query' => $query,
    ));
    $this
      ->assertEqual($url . '?' . http_build_query($query, '', '&'), $result, 'External URL can be extended with a query string in $options.');

    // Verify query string can be extended in an external URL.
    $url = $test_url . '?drupal=awesome';
    $query = array(
      $this
        ->randomName(5) => $this
        ->randomName(5),
    );
    $result = url($url, array(
      'query' => $query,
    ));
    $this
      ->assertEqual($url . '&' . http_build_query($query, '', '&'), $result, 'External URL query string can be extended with a custom query string in $options.');

    // Verify that an internal URL does not result in an external URL without
    // protocol part.
    $url = '/drupal.org';
    $result = url($url);
    $this
      ->assertTrue(strpos($result, '//') === FALSE, 'Internal URL does not turn into an external URL.');

    // Verify that an external URL without protocol part is recognized as such.
    $url = '//drupal.org';
    $result = url($url);
    $this
      ->assertEqual($url, $result, 'External URL without protocol is not altered.');
  }

}

/**
 * Tests url_is_external().
 */
class UrlIsExternalUnitTest extends DrupalUnitTestCase {
  public static function getInfo() {
    return array(
      'name' => 'External URL checking',
      'description' => 'Performs tests on url_is_external().',
      'group' => 'System',
    );
  }

  /**
   * Tests if each URL is external or not.
   */
  function testUrlIsExternal() {
    foreach ($this
      ->examples() as $path => $expected) {
      $this
        ->assertIdentical(url_is_external($path), $expected, $path);
    }
  }

  /**
   * Provides data for testUrlIsExternal().
   *
   * @return array
   *   An array of test data, keyed by a path, with the expected value where
   *   TRUE is external, and FALSE is not external.
   */
  protected function examples() {
    return array(
      // Simple external URLs.
      'http://example.com' => TRUE,
      'https://example.com' => TRUE,
      'http://drupal.org/foo/bar?foo=bar&bar=baz&baz#foo' => TRUE,
      '//drupal.org' => TRUE,
      // Some browsers ignore or strip leading control characters.
      "\0//www.example.com" => TRUE,
      "\10//www.example.com" => TRUE,
      "\37//www.example.com" => TRUE,
      "\n//www.example.com" => TRUE,
      // JSON supports decoding directly from UTF-8 code points.
      json_decode('"\\u00AD"') . "//www.example.com" => TRUE,
      json_decode('"\\u200E"') . "//www.example.com" => TRUE,
      json_decode('"\\uE0020"') . "//www.example.com" => TRUE,
      json_decode('"\\uE000"') . "//www.example.com" => TRUE,
      // Backslashes should be normalized to forward.
      '\\\\example.com' => TRUE,
      // Local URLs.
      'node' => FALSE,
      '/system/ajax' => FALSE,
      '?q=foo:bar' => FALSE,
      'node/edit:me' => FALSE,
      '/drupal.org' => FALSE,
      '<front>' => FALSE,
    );
  }

}

/**
 * Tests for check_plain(), filter_xss(), format_string(), and check_url().
 */
class CommonXssUnitTest extends DrupalUnitTestCase {
  public static function getInfo() {
    return array(
      'name' => 'String filtering tests',
      'description' => 'Confirm that check_plain(), filter_xss(), format_string() and check_url() work correctly, including invalid multi-byte sequences.',
      'group' => 'System',
    );
  }

  /**
   * Check that invalid multi-byte sequences are rejected.
   */
  function testInvalidMultiByte() {

    // Ignore PHP 5.3+ invalid multibyte sequence warning.
    $text = @check_plain("");
    $this
      ->assertEqual($text, '', 'check_plain() rejects invalid sequence "Foo\\xC0barbaz"');

    // Ignore PHP 5.3+ invalid multibyte sequence warning.
    $text = @check_plain("");
    $this
      ->assertEqual($text, '', 'check_plain() rejects invalid sequence "\\xc2\\""');
    $text = check_plain("Fooÿñ");
    $this
      ->assertEqual($text, "Fooÿñ", 'check_plain() accepts valid sequence "Fooÿñ"');
    $text = filter_xss("");
    $this
      ->assertEqual($text, '', 'filter_xss() rejects invalid sequence "Foo\\xC0barbaz"');
    $text = filter_xss("Fooÿñ");
    $this
      ->assertEqual($text, "Fooÿñ", 'filter_xss() accepts valid sequence Fooÿñ');
  }

  /**
   * Check that special characters are escaped.
   */
  function testEscaping() {
    $text = check_plain("<script>");
    $this
      ->assertEqual($text, '&lt;script&gt;', 'check_plain() escapes &lt;script&gt;');
    $text = check_plain('<>&"\'');
    $this
      ->assertEqual($text, '&lt;&gt;&amp;&quot;&#039;', 'check_plain() escapes reserved HTML characters.');
  }

  /**
   * Test t() and format_string() replacement functionality.
   */
  function testFormatStringAndT() {
    foreach (array(
      'format_string',
      't',
    ) as $function) {
      $text = $function('Simple text');
      $this
        ->assertEqual($text, 'Simple text', $function . ' leaves simple text alone.');
      $text = $function('Escaped text: @value', array(
        '@value' => '<script>',
      ));
      $this
        ->assertEqual($text, 'Escaped text: &lt;script&gt;', $function . ' replaces and escapes string.');
      $text = $function('Placeholder text: %value', array(
        '%value' => '<script>',
      ));
      $this
        ->assertEqual($text, 'Placeholder text: <em class="placeholder">&lt;script&gt;</em>', $function . ' replaces, escapes and themes string.');
      $text = $function('Verbatim text: !value', array(
        '!value' => '<script>',
      ));
      $this
        ->assertEqual($text, 'Verbatim text: <script>', $function . ' replaces verbatim string as-is.');
    }
  }

  /**
   * Check that harmful protocols are stripped.
   */
  function testBadProtocolStripping() {

    // Ensure that check_url() strips out harmful protocols, and encodes for
    // HTML. Ensure drupal_strip_dangerous_protocols() can be used to return a
    // plain-text string stripped of harmful protocols.
    $url = 'javascript:http://www.example.com/?x=1&y=2';
    $expected_plain = 'http://www.example.com/?x=1&y=2';
    $expected_html = 'http://www.example.com/?x=1&amp;y=2';
    $this
      ->assertIdentical(check_url($url), $expected_html, 'check_url() filters a URL and encodes it for HTML.');
    $this
      ->assertIdentical(drupal_strip_dangerous_protocols($url), $expected_plain, 'drupal_strip_dangerous_protocols() filters a URL and returns plain text.');
  }

}

/**
 * Tests file size parsing and formatting functions.
 */
class CommonSizeTestCase extends DrupalUnitTestCase {
  protected $exact_test_cases;
  protected $rounded_test_cases;
  public static function getInfo() {
    return array(
      'name' => 'Size parsing test',
      'description' => 'Parse a predefined amount of bytes and compare the output with the expected value.',
      'group' => 'System',
    );
  }
  function setUp() {
    $kb = DRUPAL_KILOBYTE;
    $this->exact_test_cases = array(
      '1 byte' => 1,
      '1 KB' => $kb,
      '1 MB' => $kb * $kb,
      '1 GB' => $kb * $kb * $kb,
      '1 TB' => $kb * $kb * $kb * $kb,
      '1 PB' => $kb * $kb * $kb * $kb * $kb,
      '1 EB' => $kb * $kb * $kb * $kb * $kb * $kb,
      '1 ZB' => $kb * $kb * $kb * $kb * $kb * $kb * $kb,
      '1 YB' => $kb * $kb * $kb * $kb * $kb * $kb * $kb * $kb,
    );
    $this->rounded_test_cases = array(
      '2 bytes' => 2,
      '1 MB' => $kb * $kb - 1,
      // rounded to 1 MB (not 1000 or 1024 kilobyte!)
      round(3623651 / $this->exact_test_cases['1 MB'], 2) . ' MB' => 3623651,
      // megabytes
      round(67234178751368124 / $this->exact_test_cases['1 PB'], 2) . ' PB' => 67234178751368124,
      // petabytes
      round(2.3534682382112583E+26 / $this->exact_test_cases['1 YB'], 2) . ' YB' => 2.3534682382112583E+26,
    );
    parent::setUp();
  }

  /**
   * Check that format_size() returns the expected string.
   */
  function testCommonFormatSize() {
    foreach (array(
      $this->exact_test_cases,
      $this->rounded_test_cases,
    ) as $test_cases) {
      foreach ($test_cases as $expected => $input) {
        $this
          ->assertEqual($result = format_size($input, NULL), $expected, $expected . ' == ' . $result . ' (' . $input . ' bytes)');
      }
    }
  }

  /**
   * Check that parse_size() returns the proper byte sizes.
   */
  function testCommonParseSize() {
    foreach ($this->exact_test_cases as $string => $size) {
      $this
        ->assertEqual($parsed_size = parse_size($string), $size, $size . ' == ' . $parsed_size . ' (' . $string . ')');
    }

    // Some custom parsing tests
    $string = '23476892 bytes';
    $this
      ->assertEqual($parsed_size = parse_size($string), $size = 23476892, $string . ' == ' . $parsed_size . ' bytes');
    $string = '76MRandomStringThatShouldBeIgnoredByParseSize.';

    // 76 MB
    $this
      ->assertEqual($parsed_size = parse_size($string), $size = 79691776, $string . ' == ' . $parsed_size . ' bytes');
    $string = '76.24 Giggabyte';

    // Misspeld text -> 76.24 GB
    $this
      ->assertEqual($parsed_size = parse_size($string), $size = 81862076662, $string . ' == ' . $parsed_size . ' bytes');
  }

  /**
   * Cross-test parse_size() and format_size().
   */
  function testCommonParseSizeFormatSize() {
    foreach ($this->exact_test_cases as $size) {
      $this
        ->assertEqual($size, $parsed_size = parse_size($string = format_size($size, NULL)), $size . ' == ' . $parsed_size . ' (' . $string . ')');
    }
  }

}

/**
 * Test drupal_explode_tags() and drupal_implode_tags().
 */
class DrupalTagsHandlingTestCase extends DrupalUnitTestCase {
  var $validTags = array(
    'Drupal' => 'Drupal',
    'Drupal with some spaces' => 'Drupal with some spaces',
    '"Legendary Drupal mascot of doom: ""Druplicon"""' => 'Legendary Drupal mascot of doom: "Druplicon"',
    '"Drupal, although it rhymes with sloopal, is as awesome as a troopal!"' => 'Drupal, although it rhymes with sloopal, is as awesome as a troopal!',
  );
  public static function getInfo() {
    return array(
      'name' => 'Drupal tags handling',
      'description' => "Performs tests on Drupal's handling of tags, both explosion and implosion tactics used.",
      'group' => 'System',
    );
  }

  /**
   * Explode a series of tags.
   */
  function testDrupalExplodeTags() {
    $string = implode(', ', array_keys($this->validTags));
    $tags = drupal_explode_tags($string);
    $this
      ->assertTags($tags);
  }

  /**
   * Implode a series of tags.
   */
  function testDrupalImplodeTags() {
    $tags = array_values($this->validTags);

    // Let's explode and implode to our heart's content.
    for ($i = 0; $i < 10; $i++) {
      $string = drupal_implode_tags($tags);
      $tags = drupal_explode_tags($string);
    }
    $this
      ->assertTags($tags);
  }

  /**
   * Helper function: asserts that the ending array of tags is what we wanted.
   */
  function assertTags($tags) {
    $original = $this->validTags;
    foreach ($tags as $tag) {
      $key = array_search($tag, $original);
      $this
        ->assertTrue($key, format_string('Make sure tag %tag shows up in the final tags array (originally %original)', array(
        '%tag' => $tag,
        '%original' => $key,
      )));
      unset($original[$key]);
    }
    foreach ($original as $leftover) {
      $this
        ->fail(format_string('Leftover tag %leftover was left over.', array(
        '%leftover' => $leftover,
      )));
    }
  }

}

/**
 * Test the Drupal CSS system.
 */
class CascadingStylesheetsTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Cascading stylesheets',
      'description' => 'Tests adding various cascading stylesheets to the page.',
      'group' => 'System',
    );
  }
  function setUp() {
    parent::setUp('php', 'locale', 'common_test');

    // Reset drupal_add_css() before each test.
    drupal_static_reset('drupal_add_css');
  }

  /**
   * Check default stylesheets as empty.
   */
  function testDefault() {
    $this
      ->assertEqual(array(), drupal_add_css(), 'Default CSS is empty.');
  }

  /**
   * Test that stylesheets in module .info files are loaded.
   */
  function testModuleInfo() {
    $this
      ->drupalGet('');

    // Verify common_test.css in a STYLE media="all" tag.
    $elements = $this
      ->xpath('//style[@media=:media and contains(text(), :filename)]', array(
      ':media' => 'all',
      ':filename' => 'tests/common_test.css',
    ));
    $this
      ->assertTrue(count($elements), "Stylesheet with media 'all' in module .info file found.");

    // Verify common_test.print.css in a STYLE media="print" tag.
    $elements = $this
      ->xpath('//style[@media=:media and contains(text(), :filename)]', array(
      ':media' => 'print',
      ':filename' => 'tests/common_test.print.css',
    ));
    $this
      ->assertTrue(count($elements), "Stylesheet with media 'print' in module .info file found.");
  }

  /**
   * Tests adding a file stylesheet.
   */
  function testAddFile() {
    $path = drupal_get_path('module', 'simpletest') . '/simpletest.css';
    $css = drupal_add_css($path);
    $this
      ->assertEqual($css[$path]['data'], $path, 'Adding a CSS file caches it properly.');
  }

  /**
   * Tests adding an external stylesheet.
   */
  function testAddExternal() {
    $path = 'http://example.com/style.css';
    $css = drupal_add_css($path, 'external');
    $this
      ->assertEqual($css[$path]['type'], 'external', 'Adding an external CSS file caches it properly.');
  }

  /**
   * Makes sure that reseting the CSS empties the cache.
   */
  function testReset() {
    drupal_static_reset('drupal_add_css');
    $this
      ->assertEqual(array(), drupal_add_css(), 'Resetting the CSS empties the cache.');
  }

  /**
   * Tests rendering the stylesheets.
   */
  function testRenderFile() {
    $css = drupal_get_path('module', 'simpletest') . '/simpletest.css';
    drupal_add_css($css);
    $styles = drupal_get_css();
    $this
      ->assertTrue(strpos($styles, $css) > 0, 'Rendered CSS includes the added stylesheet.');

    // Verify that newlines are properly added inside style tags.
    $query_string = variable_get('css_js_query_string', '0');
    $css_processed = "<style type=\"text/css\" media=\"all\">\n@import url(\"" . check_plain(file_create_url($css)) . "?" . $query_string . "\");\n</style>";
    $this
      ->assertEqual(trim($styles), $css_processed, 'Rendered CSS includes newlines inside style tags for JavaScript use.');
  }

  /**
   * Tests rendering an external stylesheet.
   */
  function testRenderExternal() {
    $css = 'http://example.com/style.css';
    drupal_add_css($css, 'external');
    $styles = drupal_get_css();

    // Stylesheet URL may be the href of a LINK tag or in an @import statement
    // of a STYLE tag.
    $this
      ->assertTrue(strpos($styles, 'href="' . $css) > 0 || strpos($styles, '@import url("' . $css . '")') > 0, 'Rendering an external CSS file.');
  }

  /**
   * Tests rendering inline stylesheets with preprocessing on.
   */
  function testRenderInlinePreprocess() {
    $css = 'body { padding: 0px; }';
    $css_preprocessed = '<style type="text/css" media="all">' . "\n<!--/*--><![CDATA[/*><!--*/\n" . drupal_load_stylesheet_content($css, TRUE) . "\n/*]]>*/-->\n" . '</style>';
    drupal_add_css($css, array(
      'type' => 'inline',
    ));
    $styles = drupal_get_css();
    $this
      ->assertEqual(trim($styles), $css_preprocessed, 'Rendering preprocessed inline CSS adds it to the page.');
  }

  /**
   * Tests removing charset when rendering stylesheets with preprocessing on.
   */
  function testRenderRemoveCharsetPreprocess() {
    $cases = array(
      array(
        'asset' => '@charset "UTF-8";html{font-family:"sans-serif";}',
        'expected' => 'html{font-family:"sans-serif";}',
      ),
      // This asset contains extra \n character.
      array(
        'asset' => "@charset 'UTF-8';\nhtml{font-family:'sans-serif';}",
        'expected' => "\nhtml{font-family:'sans-serif';}",
      ),
    );
    foreach ($cases as $case) {
      $this
        ->assertEqual($case['expected'], drupal_load_stylesheet_content($case['asset']), 'CSS optimizing correctly removes the charset declaration.');
    }
  }

  /**
   * Tests rendering inline stylesheets with preprocessing off.
   */
  function testRenderInlineNoPreprocess() {
    $css = 'body { padding: 0px; }';
    drupal_add_css($css, array(
      'type' => 'inline',
      'preprocess' => FALSE,
    ));
    $styles = drupal_get_css();
    $this
      ->assertTrue(strpos($styles, $css) > 0, 'Rendering non-preprocessed inline CSS adds it to the page.');
  }

  /**
   * Tests rendering inline stylesheets through a full page request.
   */
  function testRenderInlineFullPage() {
    $css = 'body { font-size: 254px; }';

    // Inline CSS is minified unless 'preprocess' => FALSE is passed as a
    // drupal_add_css() option.
    $expected = 'body{font-size:254px;}';

    // Create a node, using the PHP filter that tests drupal_add_css().
    $php_format_id = 'php_code';
    $settings = array(
      'type' => 'page',
      'body' => array(
        LANGUAGE_NONE => array(
          array(
            'value' => t('This tests the inline CSS!') . "<?php drupal_add_css('{$css}', 'inline'); ?>",
            'format' => $php_format_id,
          ),
        ),
      ),
      'promote' => 1,
    );
    $node = $this
      ->drupalCreateNode($settings);

    // Fetch the page.
    $this
      ->drupalGet('node/' . $node->nid);
    $this
      ->assertRaw($expected, 'Inline stylesheets appear in the full page rendering.');
  }

  /**
   * Test CSS ordering.
   */
  function testRenderOrder() {

    // A module CSS file.
    drupal_add_css(drupal_get_path('module', 'simpletest') . '/simpletest.css');

    // A few system CSS files, ordered in a strange way.
    $system_path = drupal_get_path('module', 'system');
    drupal_add_css($system_path . '/system.menus.css', array(
      'group' => CSS_SYSTEM,
    ));
    drupal_add_css($system_path . '/system.base.css', array(
      'group' => CSS_SYSTEM,
      'weight' => -10,
    ));
    drupal_add_css($system_path . '/system.theme.css', array(
      'group' => CSS_SYSTEM,
    ));
    $expected = array(
      $system_path . '/system.base.css',
      $system_path . '/system.menus.css',
      $system_path . '/system.theme.css',
      drupal_get_path('module', 'simpletest') . '/simpletest.css',
    );
    $styles = drupal_get_css();

    // Stylesheet URL may be the href of a LINK tag or in an @import statement
    // of a STYLE tag.
    if (preg_match_all('/(href="|url\\(")' . preg_quote($GLOBALS['base_url'] . '/', '/') . '([^?]+)\\?/', $styles, $matches)) {
      $result = $matches[2];
    }
    else {
      $result = array();
    }
    $this
      ->assertIdentical($result, $expected, 'The CSS files are in the expected order.');
  }

  /**
   * Test CSS override.
   */
  function testRenderOverride() {
    $system = drupal_get_path('module', 'system');
    $simpletest = drupal_get_path('module', 'simpletest');
    drupal_add_css($system . '/system.base.css');
    drupal_add_css($simpletest . '/tests/system.base.css');

    // The dummy stylesheet should be the only one included.
    $styles = drupal_get_css();
    $this
      ->assert(strpos($styles, $simpletest . '/tests/system.base.css') !== FALSE, 'The overriding CSS file is output.');
    $this
      ->assert(strpos($styles, $system . '/system.base.css') === FALSE, 'The overridden CSS file is not output.');
    drupal_add_css($simpletest . '/tests/system.base.css');
    drupal_add_css($system . '/system.base.css');

    // The standard stylesheet should be the only one included.
    $styles = drupal_get_css();
    $this
      ->assert(strpos($styles, $system . '/system.base.css') !== FALSE, 'The overriding CSS file is output.');
    $this
      ->assert(strpos($styles, $simpletest . '/tests/system.base.css') === FALSE, 'The overridden CSS file is not output.');
  }

  /**
   * Tests Locale module's CSS Alter to include RTL overrides.
   */
  function testAlter() {

    // Switch the language to a right to left language and add system.base.css.
    global $language;
    $language->direction = LANGUAGE_RTL;
    $path = drupal_get_path('module', 'system');
    drupal_add_css($path . '/system.base.css');

    // Check to see if system.base-rtl.css was also added.
    $styles = drupal_get_css();
    $this
      ->assert(strpos($styles, $path . '/system.base-rtl.css') !== FALSE, 'CSS is alterable as right to left overrides are added.');

    // Change the language back to left to right.
    $language->direction = LANGUAGE_LTR;
  }

  /**
   * Tests that the query string remains intact when adding CSS files that have
   * query string parameters.
   */
  function testAddCssFileWithQueryString() {
    $this
      ->drupalGet('common-test/query-string');
    $query_string = variable_get('css_js_query_string', '0');
    $this
      ->assertRaw(drupal_get_path('module', 'node') . '/node.css?' . $query_string, 'Query string was appended correctly to css.');
    $this
      ->assertRaw(drupal_get_path('module', 'node') . '/node-fake.css?arg1=value1&amp;arg2=value2', 'Query string not escaped on a URI.');
  }

}

/**
 * Test for cleaning HTML identifiers.
 */
class DrupalHTMLIdentifierTestCase extends DrupalUnitTestCase {
  public static function getInfo() {
    return array(
      'name' => 'HTML identifiers',
      'description' => 'Test the functions drupal_html_class(), drupal_html_id() and drupal_clean_css_identifier() for expected behavior',
      'group' => 'System',
    );
  }

  /**
   * Tests that drupal_clean_css_identifier() cleans the identifier properly.
   */
  function testDrupalCleanCSSIdentifier() {

    // Verify that no valid ASCII characters are stripped from the identifier.
    $identifier = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789';
    $this
      ->assertIdentical(drupal_clean_css_identifier($identifier, array()), $identifier, 'Verify valid ASCII characters pass through.');

    // Verify that valid UTF-8 characters are not stripped from the identifier.
    $identifier = '¡¢£¤¥';
    $this
      ->assertIdentical(drupal_clean_css_identifier($identifier, array()), $identifier, 'Verify valid UTF-8 characters pass through.');

    // Verify that invalid characters (including non-breaking space) are stripped from the identifier.
    $this
      ->assertIdentical(drupal_clean_css_identifier('invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', array()), 'invalididentifier', 'Strip invalid characters.');

    // Verify that double underscores are replaced in the identifier by default.
    $identifier = 'css__identifier__with__double__underscores';
    $expected = 'css--identifier--with--double--underscores';
    $this
      ->assertIdentical(drupal_clean_css_identifier($identifier), $expected, 'Verify double underscores are replaced with double hyphens by default.');

    // Verify that double underscores are preserved in the identifier if the
    // variable allow_css_double_underscores is set to TRUE.
    $this
      ->setAllowCSSDoubleUnderscores(TRUE);
    $this
      ->assertIdentical(drupal_clean_css_identifier($identifier), $identifier, 'Verify double underscores are preserved if the allow_css_double_underscores set to TRUE.');

    // To avoid affecting other test cases, set the variable
    // allow_css_double_underscores to FALSE which is the default value.
    $this
      ->setAllowCSSDoubleUnderscores(FALSE);
  }

  /**
   * Set the variable allow_css_double_underscores and reset the cache.
   *
   * @param $value bool
   *   A new value to be set to allow_css_double_underscores.
   */
  function setAllowCSSDoubleUnderscores($value) {
    $GLOBALS['conf']['allow_css_double_underscores'] = $value;
    drupal_static_reset('drupal_clean_css_identifier:allow_css_double_underscores');
  }

  /**
   * Tests that drupal_html_class() cleans the class name properly.
   */
  function testDrupalHTMLClass() {

    // Verify Drupal coding standards are enforced.
    $this
      ->assertIdentical(drupal_html_class('CLASS NAME_[Ü]'), 'class-name--ü', 'Enforce Drupal coding standards.');
  }

  /**
   * Tests that drupal_html_id() cleans the ID properly.
   */
  function testDrupalHTMLId() {

    // Verify that letters, digits, and hyphens are not stripped from the ID.
    $id = 'abcdefghijklmnopqrstuvwxyz-0123456789';
    $this
      ->assertIdentical(drupal_html_id($id), $id, 'Verify valid characters pass through.');

    // Verify that invalid characters are stripped from the ID.
    $this
      ->assertIdentical(drupal_html_id('invalid,./:@\\^`{Üidentifier'), 'invalididentifier', 'Strip invalid characters.');

    // Verify Drupal coding standards are enforced.
    $this
      ->assertIdentical(drupal_html_id('ID NAME_[1]'), 'id-name-1', 'Enforce Drupal coding standards.');

    // Reset the static cache so we can ensure the unique id count is at zero.
    drupal_static_reset('drupal_html_id');

    // Clean up IDs with invalid starting characters.
    $this
      ->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id', 'Test the uniqueness of IDs #1.');
    $this
      ->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id--2', 'Test the uniqueness of IDs #2.');
    $this
      ->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id--3', 'Test the uniqueness of IDs #3.');
  }

}

/**
 * CSS Unit Tests.
 */
class CascadingStylesheetsUnitTest extends DrupalUnitTestCase {
  public static function getInfo() {
    return array(
      'name' => 'CSS Unit Tests',
      'description' => 'Unit tests on CSS functions like aggregation.',
      'group' => 'System',
    );
  }

  /**
   * Tests basic CSS loading with and without optimization via drupal_load_stylesheet().
   *
   * Known tests:
   * - Retain white-space in selectors. (https://drupal.org/node/472820)
   * - Proper URLs in imported files. (https://drupal.org/node/265719)
   * - Retain pseudo-selectors. (https://drupal.org/node/460448)
   * - Don't adjust data URIs. (https://drupal.org/node/2142441)
   * - Files imported from external URLs. (https://drupal.org/node/2014851)
   */
  function testLoadCssBasic() {

    // Array of files to test living in 'simpletest/files/css_test_files/'.
    // - Original: name.css
    // - Unoptimized expected content: name.css.unoptimized.css
    // - Optimized expected content: name.css.optimized.css
    $testfiles = array(
      'css_input_without_import.css',
      'css_input_with_import.css',
      'css_subfolder/css_input_with_import.css',
      'comment_hacks.css',
    );
    $path = drupal_get_path('module', 'simpletest') . '/files/css_test_files';
    foreach ($testfiles as $file) {
      $file_path = $path . '/' . $file;
      $file_url = $GLOBALS['base_url'] . '/' . $file_path;
      $expected = file_get_contents($file_path . '.unoptimized.css');
      $unoptimized_output = drupal_load_stylesheet($file_path, FALSE);
      $this
        ->assertEqual($unoptimized_output, $expected, format_string('Unoptimized CSS file has expected contents (@file)', array(
        '@file' => $file,
      )));
      $expected = file_get_contents($file_path . '.optimized.css');
      $optimized_output = drupal_load_stylesheet($file_path, TRUE);
      $this
        ->assertEqual($optimized_output, $expected, format_string('Optimized CSS file has expected contents (@file)', array(
        '@file' => $file,
      )));

      // Repeat the tests by accessing the stylesheets by URL.
      $expected = file_get_contents($file_path . '.unoptimized.css');
      $unoptimized_output_url = drupal_load_stylesheet($file_url, FALSE);
      $this
        ->assertEqual($unoptimized_output_url, $expected, format_string('Unoptimized CSS file (loaded from an URL) has expected contents (@file)', array(
        '@file' => $file,
      )));
      $expected = file_get_contents($file_path . '.optimized.css');
      $optimized_output_url = drupal_load_stylesheet($file_url, TRUE);
      $this
        ->assertEqual($optimized_output_url, $expected, format_string('Optimized CSS file (loaded from an URL) has expected contents (@file)', array(
        '@file' => $file,
      )));
    }
  }

}

/**
 * Test drupal_http_request().
 */
class DrupalHTTPRequestTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Drupal HTTP request',
      'description' => "Performs tests on Drupal's HTTP request mechanism.",
      'group' => 'System',
    );
  }
  function setUp() {
    parent::setUp('system_test', 'locale');
  }
  function testDrupalHTTPRequest() {
    global $is_https;

    // Parse URL schema.
    $missing_scheme = drupal_http_request('example.com/path');
    $this
      ->assertEqual($missing_scheme->code, -1002, 'Returned with "-1002" error code.');
    $this
      ->assertEqual($missing_scheme->error, 'missing schema', 'Returned with "missing schema" error message.');
    $unable_to_parse = drupal_http_request('http:///path');
    $this
      ->assertEqual($unable_to_parse->code, -1001, 'Returned with "-1001" error code.');
    $this
      ->assertEqual($unable_to_parse->error, 'unable to parse URL', 'Returned with "unable to parse URL" error message.');

    // Fetch page.
    $result = drupal_http_request(url('node', array(
      'absolute' => TRUE,
    )));
    $this
      ->assertEqual($result->code, 200, 'Fetched page successfully.');
    $this
      ->drupalSetContent($result->data);
    $this
      ->assertTitle(t('Welcome to @site-name | @site-name', array(
      '@site-name' => variable_get('site_name', 'Drupal'),
    )), 'Site title matches.');

    // Test that code and status message is returned.
    $result = drupal_http_request(url('pagedoesnotexist', array(
      'absolute' => TRUE,
    )));
    $this
      ->assertTrue(!empty($result->protocol), 'Result protocol is returned.');
    $this
      ->assertEqual($result->code, '404', 'Result code is 404');
    $this
      ->assertEqual($result->status_message, 'Not Found', 'Result status message is "Not Found"');

    // Skip the timeout tests when the testing environment is HTTPS because
    // stream_set_timeout() does not work for SSL connections.
    // @link http://bugs.php.net/bug.php?id=47929
    if (!$is_https) {

      // Test that timeout is respected. The test machine is expected to be able
      // to make the connection (i.e. complete the fsockopen()) in 2 seconds and
      // return within a total of 5 seconds. If the test machine is extremely
      // slow, the test will fail. fsockopen() has been seen to time out in
      // slightly less than the specified timeout, so allow a little slack on
      // the minimum expected time (i.e. 1.8 instead of 2).
      timer_start(__METHOD__);
      $result = drupal_http_request(url('system-test/sleep/10', array(
        'absolute' => TRUE,
      )), array(
        'timeout' => 2,
      ));
      $time = timer_read(__METHOD__) / 1000;
      $this
        ->assertTrue(1.8 < $time && $time < 5, format_string('Request timed out (%time seconds).', array(
        '%time' => $time,
      )));
      $this
        ->assertTrue($result->error, 'An error message was returned.');
      $this
        ->assertEqual($result->code, HTTP_REQUEST_TIMEOUT, 'Proper error code was returned.');
    }
  }
  function testDrupalHTTPRequestBasicAuth() {
    $username = $this
      ->randomName();
    $password = $this
      ->randomName();
    $url = url('system-test/auth', array(
      'absolute' => TRUE,
    ));
    $auth = str_replace('://', '://' . $username . ':' . $password . '@', $url);
    $result = drupal_http_request($auth);
    $this
      ->drupalSetContent($result->data);
    $this
      ->assertRaw($username, 'Username is passed correctly.');
    $this
      ->assertRaw($password, 'Password is passed correctly.');
  }
  function testDrupalHTTPRequestRedirect() {
    $redirect_301 = drupal_http_request(url('system-test/redirect/301', array(
      'absolute' => TRUE,
    )), array(
      'max_redirects' => 1,
    ));
    $this
      ->assertEqual($redirect_301->redirect_code, 301, 'drupal_http_request follows the 301 redirect.');
    $redirect_301 = drupal_http_request(url('system-test/redirect/301', array(
      'absolute' => TRUE,
    )), array(
      'max_redirects' => 0,
    ));
    $this
      ->assertFalse(isset($redirect_301->redirect_code), 'drupal_http_request does not follow 301 redirect if max_redirects = 0.');
    $redirect_invalid = drupal_http_request(url('system-test/redirect-noscheme', array(
      'absolute' => TRUE,
    )), array(
      'max_redirects' => 1,
    ));
    $this
      ->assertEqual($redirect_invalid->code, -1002, format_string('301 redirect to invalid URL returned with error code !error.', array(
      '!error' => $redirect_invalid->error,
    )));
    $this
      ->assertEqual($redirect_invalid->error, 'missing schema', format_string('301 redirect to invalid URL returned with error message "!error".', array(
      '!error' => $redirect_invalid->error,
    )));
    $redirect_invalid = drupal_http_request(url('system-test/redirect-noparse', array(
      'absolute' => TRUE,
    )), array(
      'max_redirects' => 1,
    ));
    $this
      ->assertEqual($redirect_invalid->code, -1001, format_string('301 redirect to invalid URL returned with error message code "!error".', array(
      '!error' => $redirect_invalid->error,
    )));
    $this
      ->assertEqual($redirect_invalid->error, 'unable to parse URL', format_string('301 redirect to invalid URL returned with error message "!error".', array(
      '!error' => $redirect_invalid->error,
    )));
    $redirect_invalid = drupal_http_request(url('system-test/redirect-invalid-scheme', array(
      'absolute' => TRUE,
    )), array(
      'max_redirects' => 1,
    ));
    $this
      ->assertEqual($redirect_invalid->code, -1003, format_string('301 redirect to invalid URL returned with error code !error.', array(
      '!error' => $redirect_invalid->error,
    )));
    $this
      ->assertEqual($redirect_invalid->error, 'invalid schema ftp', format_string('301 redirect to invalid URL returned with error message "!error".', array(
      '!error' => $redirect_invalid->error,
    )));
    $redirect_302 = drupal_http_request(url('system-test/redirect/302', array(
      'absolute' => TRUE,
    )), array(
      'max_redirects' => 1,
    ));
    $this
      ->assertEqual($redirect_302->redirect_code, 302, 'drupal_http_request follows the 302 redirect.');
    $redirect_302 = drupal_http_request(url('system-test/redirect/302', array(
      'absolute' => TRUE,
    )), array(
      'max_redirects' => 0,
    ));
    $this
      ->assertFalse(isset($redirect_302->redirect_code), 'drupal_http_request does not follow 302 redirect if $retry = 0.');
    $redirect_307 = drupal_http_request(url('system-test/redirect/307', array(
      'absolute' => TRUE,
    )), array(
      'max_redirects' => 1,
    ));
    $this
      ->assertEqual($redirect_307->redirect_code, 307, 'drupal_http_request follows the 307 redirect.');
    $redirect_307 = drupal_http_request(url('system-test/redirect/307', array(
      'absolute' => TRUE,
    )), array(
      'max_redirects' => 0,
    ));
    $this
      ->assertFalse(isset($redirect_307->redirect_code), 'drupal_http_request does not follow 307 redirect if max_redirects = 0.');
    $multiple_redirect_final_url = url('system-test/multiple-redirects/0', array(
      'absolute' => TRUE,
    ));
    $multiple_redirect_1 = drupal_http_request(url('system-test/multiple-redirects/1', array(
      'absolute' => TRUE,
    )), array(
      'max_redirects' => 1,
    ));
    $this
      ->assertEqual($multiple_redirect_1->redirect_url, $multiple_redirect_final_url, 'redirect_url contains the final redirection location after 1 redirect.');
    $multiple_redirect_3 = drupal_http_request(url('system-test/multiple-redirects/3', array(
      'absolute' => TRUE,
    )), array(
      'max_redirects' => 3,
    ));
    $this
      ->assertEqual($multiple_redirect_3->redirect_url, $multiple_redirect_final_url, 'redirect_url contains the final redirection location after 3 redirects.');
  }

  /**
   * Tests Content-language headers generated by Drupal.
   */
  function testDrupalHTTPRequestHeaders() {

    // Check the default header.
    $request = drupal_http_request(url('<front>', array(
      'absolute' => TRUE,
    )));
    $this
      ->assertEqual($request->headers['content-language'], 'en', 'Content-Language HTTP header is English.');

    // Add German language and set as default.
    locale_add_language('de', 'German', 'Deutsch', LANGUAGE_LTR, '', '', TRUE, TRUE);

    // Request front page and check for matching Content-Language.
    $request = drupal_http_request(url('<front>', array(
      'absolute' => TRUE,
    )));
    $this
      ->assertEqual($request->headers['content-language'], 'de', 'Content-Language HTTP header is German.');
  }

}

/**
 * Tests parsing of the HTTP response status line.
 */
class DrupalHTTPResponseStatusLineTest extends DrupalUnitTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Drupal HTTP request response status parsing',
      'description' => 'Perform unit tests on _drupal_parse_response_status().',
      'group' => 'System',
    );
  }

  /**
   * Tests parsing HTTP response status line.
   */
  public function testStatusLine() {

    // Grab the big array of test data from statusLineData().
    $data = $this
      ->statusLineData();
    foreach ($data as $test_case) {
      $test_data = array_shift($test_case);
      $expected = array_shift($test_case);
      $outcome = _drupal_parse_response_status($test_data);
      foreach (array_keys($expected) as $key) {
        $this
          ->assertIdentical($outcome[$key], $expected[$key]);
      }
    }
  }

  /**
   * Data provider for testStatusLine().
   *
   * @return array
   *   Test data.
   */
  protected function statusLineData() {
    return array(
      array(
        'HTTP/1.1 200 OK',
        array(
          'http_version' => 'HTTP/1.1',
          'response_code' => '200',
          'reason_phrase' => 'OK',
        ),
      ),
      // Data set with no reason phrase.
      array(
        'HTTP/1.1 200',
        array(
          'http_version' => 'HTTP/1.1',
          'response_code' => '200',
          'reason_phrase' => '',
        ),
      ),
      // Arbitrary strings.
      array(
        'version code multi word explanation',
        array(
          'http_version' => 'version',
          'response_code' => 'code',
          'reason_phrase' => 'multi word explanation',
        ),
      ),
    );
  }

}

/**
 * Testing drupal_add_region_content and drupal_get_region_content.
 */
class DrupalSetContentTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Drupal set/get regions',
      'description' => 'Performs tests on setting and retrieiving content from theme regions.',
      'group' => 'System',
    );
  }

  /**
   * Test setting and retrieving content for theme regions.
   */
  function testRegions() {
    global $theme_key;
    $block_regions = system_region_list($theme_key, REGIONS_ALL, FALSE);
    $delimiter = $this
      ->randomName(32);
    $values = array();

    // Set some random content for each region available.
    foreach ($block_regions as $region) {
      $first_chunk = $this
        ->randomName(32);
      drupal_add_region_content($region, $first_chunk);
      $second_chunk = $this
        ->randomName(32);
      drupal_add_region_content($region, $second_chunk);

      // Store the expected result for a drupal_get_region_content call for this region.
      $values[$region] = $first_chunk . $delimiter . $second_chunk;
    }

    // Ensure drupal_get_region_content returns expected results when fetching all regions.
    $content = drupal_get_region_content(NULL, $delimiter);
    foreach ($content as $region => $region_content) {
      $this
        ->assertEqual($region_content, $values[$region], format_string('@region region text verified when fetching all regions', array(
        '@region' => $region,
      )));
    }

    // Ensure drupal_get_region_content returns expected results when fetching a single region.
    foreach ($block_regions as $region) {
      $region_content = drupal_get_region_content($region, $delimiter);
      $this
        ->assertEqual($region_content, $values[$region], format_string('@region region text verified when fetching single region.', array(
        '@region' => $region,
      )));
    }
  }

}

/**
 * Testing drupal_goto and hook_drupal_goto_alter().
 */
class DrupalGotoTest extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Drupal goto',
      'description' => 'Performs tests on the drupal_goto function and hook_drupal_goto_alter',
      'group' => 'System',
    );
  }
  function setUp() {
    parent::setUp('common_test');
  }

  /**
   * Test drupal_goto().
   */
  function testDrupalGoto() {
    $this
      ->drupalGet('common-test/drupal_goto/redirect');
    $headers = $this
      ->drupalGetHeaders(TRUE);
    list(, $status) = explode(' ', $headers[0][':status'], 3);
    $this
      ->assertEqual($status, 302, 'Expected response code was sent.');
    $this
      ->assertText('drupal_goto', 'Drupal goto redirect succeeded.');
    $this
      ->assertEqual($this
      ->getUrl(), url('common-test/drupal_goto', array(
      'absolute' => TRUE,
    )), 'Drupal goto redirected to expected URL.');
    $this
      ->drupalGet('common-test/drupal_goto/redirect_advanced');
    $headers = $this
      ->drupalGetHeaders(TRUE);
    list(, $status) = explode(' ', $headers[0][':status'], 3);
    $this
      ->assertEqual($status, 301, 'Expected response code was sent.');
    $this
      ->assertText('drupal_goto', 'Drupal goto redirect succeeded.');
    $this
      ->assertEqual($this
      ->getUrl(), url('common-test/drupal_goto', array(
      'query' => array(
        'foo' => '123',
      ),
      'absolute' => TRUE,
    )), 'Drupal goto redirected to expected URL.');

    // Test that calling drupal_goto() on the current path is not dangerous.
    variable_set('common_test_redirect_current_path', TRUE);
    $this
      ->drupalGet('', array(
      'query' => array(
        'q' => 'http://www.example.com/',
      ),
    ));
    $headers = $this
      ->drupalGetHeaders(TRUE);
    list(, $status) = explode(' ', $headers[0][':status'], 3);
    $this
      ->assertEqual($status, 302, 'Expected response code was sent.');
    $this
      ->assertNotEqual($this
      ->getUrl(), 'http://www.example.com/', 'Drupal goto did not redirect to external URL.');
    $this
      ->assertTrue(strpos($this
      ->getUrl(), url('<front>', array(
      'absolute' => TRUE,
    ))) === 0, 'Drupal redirected to itself.');
    variable_del('common_test_redirect_current_path');

    // Test that drupal_goto() respects ?destination=xxx. Use an complicated URL
    // to test that the path is encoded and decoded properly.
    $destination = 'common-test/drupal_goto/destination?foo=%2525&bar=123';
    $this
      ->drupalGet('common-test/drupal_goto/redirect', array(
      'query' => array(
        'destination' => $destination,
      ),
    ));
    $this
      ->assertText('drupal_goto', 'Drupal goto redirect with destination succeeded.');
    $this
      ->assertEqual($this
      ->getUrl(), url('common-test/drupal_goto/destination', array(
      'query' => array(
        'foo' => '%25',
        'bar' => '123',
      ),
      'absolute' => TRUE,
    )), 'Drupal goto redirected to given query string destination.');
  }

  /**
   * Test hook_drupal_goto_alter().
   */
  function testDrupalGotoAlter() {
    $this
      ->drupalGet('common-test/drupal_goto/redirect_fail');
    $this
      ->assertNoText(t("Drupal goto failed to stop program"), "Drupal goto stopped program.");
    $this
      ->assertNoText('drupal_goto_fail', "Drupal goto redirect failed.");
  }

  /**
   * Test drupal_get_destination().
   */
  function testDrupalGetDestination() {
    $query = $this
      ->randomName(10);

    // Verify that a 'destination' query string is used as destination.
    $this
      ->drupalGet('common-test/destination', array(
      'query' => array(
        'destination' => $query,
      ),
    ));
    $this
      ->assertText('The destination: ' . $query, 'The given query string destination is determined as destination.');

    // Verify that the current path is used as destination.
    $this
      ->drupalGet('common-test/destination', array(
      'query' => array(
        $query => NULL,
      ),
    ));
    $url = 'common-test/destination?' . $query;
    $this
      ->assertText('The destination: ' . $url, 'The current path is determined as destination.');
  }

}

/**
 * Tests for the JavaScript system.
 */
class JavaScriptTestCase extends DrupalWebTestCase {

  /**
   * Store configured value for JavaScript preprocessing.
   */
  protected $preprocess_js = NULL;
  public static function getInfo() {
    return array(
      'name' => 'JavaScript',
      'description' => 'Tests the JavaScript system.',
      'group' => 'System',
    );
  }
  function setUp() {

    // Enable Locale and SimpleTest in the test environment.
    parent::setUp('locale', 'simpletest', 'common_test');

    // Disable preprocessing
    $this->preprocess_js = variable_get('preprocess_js', 0);
    variable_set('preprocess_js', 0);

    // Reset drupal_add_js() and drupal_add_library() statics before each test.
    drupal_static_reset('drupal_add_js');
    drupal_static_reset('drupal_add_library');
  }
  function tearDown() {

    // Restore configured value for JavaScript preprocessing.
    variable_set('preprocess_js', $this->preprocess_js);
    parent::tearDown();
  }

  /**
   * Test default JavaScript is empty.
   */
  function testDefault() {
    $this
      ->assertEqual(array(), drupal_add_js(), 'Default JavaScript is empty.');
  }

  /**
   * Test adding a JavaScript file.
   */
  function testAddFile() {
    $javascript = drupal_add_js('misc/collapse.js');
    $this
      ->assertTrue(array_key_exists('misc/jquery.js', $javascript), 'jQuery is added when a file is added.');
    $this
      ->assertTrue(array_key_exists('misc/drupal.js', $javascript), 'Drupal.js is added when file is added.');
    $this
      ->assertTrue(array_key_exists('misc/collapse.js', $javascript), 'JavaScript files are correctly added.');
    $this
      ->assertEqual(base_path(), $javascript['settings']['data'][0]['basePath'], 'Base path JavaScript setting is correctly set.');
    url('', array(
      'prefix' => &$prefix,
    ));
    $this
      ->assertEqual(empty($prefix) ? '' : $prefix, $javascript['settings']['data'][1]['pathPrefix'], 'Path prefix JavaScript setting is correctly set.');
  }

  /**
   * Test adding settings.
   */
  function testAddSetting() {
    $javascript = drupal_add_js(array(
      'drupal' => 'rocks',
      'dries' => 280342800,
    ), 'setting');
    $this
      ->assertEqual(280342800, $javascript['settings']['data'][2]['dries'], 'JavaScript setting is set correctly.');
    $this
      ->assertEqual('rocks', $javascript['settings']['data'][2]['drupal'], 'The other JavaScript setting is set correctly.');
  }

  /**
   * Tests adding an external JavaScript File.
   */
  function testAddExternal() {
    $path = 'http://example.com/script.js';
    $javascript = drupal_add_js($path, 'external');
    $this
      ->assertTrue(array_key_exists('http://example.com/script.js', $javascript), 'Added an external JavaScript file.');
  }

  /**
   * Test drupal_get_js() for JavaScript settings.
   */
  function testHeaderSetting() {

    // Only the second of these two entries should appear in Drupal.settings.
    drupal_add_js(array(
      'commonTest' => 'commonTestShouldNotAppear',
    ), 'setting');
    drupal_add_js(array(
      'commonTest' => 'commonTestShouldAppear',
    ), 'setting');

    // All three of these entries should appear in Drupal.settings.
    drupal_add_js(array(
      'commonTestArray' => array(
        'commonTestValue0',
      ),
    ), 'setting');
    drupal_add_js(array(
      'commonTestArray' => array(
        'commonTestValue1',
      ),
    ), 'setting');
    drupal_add_js(array(
      'commonTestArray' => array(
        'commonTestValue2',
      ),
    ), 'setting');

    // Only the second of these two entries should appear in Drupal.settings.
    drupal_add_js(array(
      'commonTestArray' => array(
        'key' => 'commonTestOldValue',
      ),
    ), 'setting');
    drupal_add_js(array(
      'commonTestArray' => array(
        'key' => 'commonTestNewValue',
      ),
    ), 'setting');
    $javascript = drupal_get_js('header');
    $this
      ->assertTrue(strpos($javascript, 'basePath') > 0, 'Rendered JavaScript header returns basePath setting.');
    $this
      ->assertTrue(strpos($javascript, 'misc/jquery.js') > 0, 'Rendered JavaScript header includes jQuery.');
    $this
      ->assertTrue(strpos($javascript, 'pathPrefix') > 0, 'Rendered JavaScript header returns pathPrefix setting.');

    // Test whether drupal_add_js can be used to override a previous setting.
    $this
      ->assertTrue(strpos($javascript, 'commonTestShouldAppear') > 0, 'Rendered JavaScript header returns custom setting.');
    $this
      ->assertTrue(strpos($javascript, 'commonTestShouldNotAppear') === FALSE, 'drupal_add_js() correctly overrides a custom setting.');

    // Test whether drupal_add_js can be used to add numerically indexed values
    // to an array.
    $array_values_appear = strpos($javascript, 'commonTestValue0') > 0 && strpos($javascript, 'commonTestValue1') > 0 && strpos($javascript, 'commonTestValue2') > 0;
    $this
      ->assertTrue($array_values_appear, 'drupal_add_js() correctly adds settings to the end of an indexed array.');

    // Test whether drupal_add_js can be used to override the entry for an
    // existing key in an associative array.
    $associative_array_override = strpos($javascript, 'commonTestNewValue') > 0 && strpos($javascript, 'commonTestOldValue') === FALSE;
    $this
      ->assertTrue($associative_array_override, 'drupal_add_js() correctly overrides settings within an associative array.');
  }

  /**
   * Test to see if resetting the JavaScript empties the cache.
   */
  function testReset() {
    drupal_add_js('misc/collapse.js');
    drupal_static_reset('drupal_add_js');
    $this
      ->assertEqual(array(), drupal_add_js(), 'Resetting the JavaScript correctly empties the cache.');
  }

  /**
   * Test adding inline scripts.
   */
  function testAddInline() {
    $inline = 'jQuery(function () { });';
    $javascript = drupal_add_js($inline, array(
      'type' => 'inline',
      'scope' => 'footer',
    ));
    $this
      ->assertTrue(array_key_exists('misc/jquery.js', $javascript), 'jQuery is added when inline scripts are added.');
    $data = end($javascript);
    $this
      ->assertEqual($inline, $data['data'], 'Inline JavaScript is correctly added to the footer.');
  }

  /**
   * Test rendering an external JavaScript file.
   */
  function testRenderExternal() {
    $external = 'http://example.com/example.js';
    drupal_add_js($external, 'external');
    $javascript = drupal_get_js();

    // Local files have a base_path() prefix, external files should not.
    $this
      ->assertTrue(strpos($javascript, 'src="' . $external) > 0, 'Rendering an external JavaScript file.');
  }

  /**
   * Test drupal_get_js() with a footer scope.
   */
  function testFooterHTML() {
    $inline = 'jQuery(function () { });';
    drupal_add_js($inline, array(
      'type' => 'inline',
      'scope' => 'footer',
    ));
    $javascript = drupal_get_js('footer');
    $this
      ->assertTrue(strpos($javascript, $inline) > 0, 'Rendered JavaScript footer returns the inline code.');
  }

  /**
   * Test the 'javascript_always_use_jquery' variable.
   */
  function testJavaScriptAlwaysUseJQuery() {

    // The default front page of the site should use jQuery and other standard
    // scripts and settings.
    $this
      ->drupalGet('');
    $this
      ->assertRaw('misc/jquery.js', 'Default behavior: The front page of the site includes jquery.js.');
    $this
      ->assertRaw('misc/drupal.js', 'Default behavior: The front page of the site includes drupal.js.');
    $this
      ->assertRaw('Drupal.settings', 'Default behavior: The front page of the site includes Drupal settings.');
    $this
      ->assertRaw('basePath', 'Default behavior: The front page of the site includes the basePath Drupal setting.');

    // The default front page should not use jQuery and other standard scripts
    // and settings when the 'javascript_always_use_jquery' variable is set to
    // FALSE.
    variable_set('javascript_always_use_jquery', FALSE);
    $this
      ->drupalGet('');
    $this
      ->assertNoRaw('misc/jquery.js', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include jquery.js.');
    $this
      ->assertNoRaw('misc/drupal.js', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include drupal.js.');
    $this
      ->assertNoRaw('Drupal.settings', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include Drupal settings.');
    $this
      ->assertNoRaw('basePath', 'When "javascript_always_use_jquery" is FALSE: The front page of the site does not include the basePath Drupal setting.');
    variable_del('javascript_always_use_jquery');

    // When only settings have been added via drupal_add_js(), drupal_get_js()
    // should still return jQuery and other standard scripts and settings.
    $this
      ->resetStaticVariables();
    drupal_add_js(array(
      'testJavaScriptSetting' => 'test',
    ), 'setting');
    $javascript = drupal_get_js();
    $this
      ->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes jquery.js.');
    $this
      ->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes drupal.js.');
    $this
      ->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes Drupal.settings.');
    $this
      ->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes the basePath Drupal setting.');
    $this
      ->assertTrue(strpos($javascript, 'testJavaScriptSetting') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when only settings have been added includes the added Drupal settings.');

    // When only settings have been added via drupal_add_js() and the
    // 'javascript_always_use_jquery' variable is set to FALSE, drupal_get_js()
    // should not return jQuery and other standard scripts and settings, nor
    // should it return the requested settings (since they cannot actually be
    // addded to the page without jQuery).
    $this
      ->resetStaticVariables();
    variable_set('javascript_always_use_jquery', FALSE);
    drupal_add_js(array(
      'testJavaScriptSetting' => 'test',
    ), 'setting');
    $javascript = drupal_get_js();
    $this
      ->assertTrue(strpos($javascript, 'misc/jquery.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include jquery.js.');
    $this
      ->assertTrue(strpos($javascript, 'misc/drupal.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include drupal.js.');
    $this
      ->assertTrue(strpos($javascript, 'Drupal.settings') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include Drupal.settings.');
    $this
      ->assertTrue(strpos($javascript, 'basePath') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include the basePath Drupal setting.');
    $this
      ->assertTrue(strpos($javascript, 'testJavaScriptSetting') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when only settings have been added does not include the added Drupal settings.');
    variable_del('javascript_always_use_jquery');

    // When a regular file has been added via drupal_add_js(), drupal_get_js()
    // should return jQuery and other standard scripts and settings.
    $this
      ->resetStaticVariables();
    drupal_add_js('misc/collapse.js');
    $javascript = drupal_get_js();
    $this
      ->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes jquery.js.');
    $this
      ->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes drupal.js.');
    $this
      ->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes Drupal.settings.');
    $this
      ->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the basePath Drupal setting.');
    $this
      ->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the custom file.');

    // When a regular file has been added via drupal_add_js() and the
    // 'javascript_always_use_jquery' variable is set to FALSE, drupal_get_js()
    // should still return jQuery and other standard scripts and settings
    // (since the file is assumed to require jQuery by default).
    $this
      ->resetStaticVariables();
    variable_set('javascript_always_use_jquery', FALSE);
    drupal_add_js('misc/collapse.js');
    $javascript = drupal_get_js();
    $this
      ->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes jquery.js.');
    $this
      ->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes drupal.js.');
    $this
      ->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes Drupal.settings.');
    $this
      ->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the basePath Drupal setting.');
    $this
      ->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file has been added includes the custom file.');
    variable_del('javascript_always_use_jquery');

    // When a file that does not require jQuery has been added via
    // drupal_add_js(), drupal_get_js() should still return jQuery and other
    // standard scripts and settings by default.
    $this
      ->resetStaticVariables();
    drupal_add_js('misc/collapse.js', array(
      'requires_jquery' => FALSE,
    ));
    $javascript = drupal_get_js();
    $this
      ->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes jquery.js.');
    $this
      ->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes drupal.js.');
    $this
      ->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes Drupal.settings.');
    $this
      ->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the basePath Drupal setting.');
    $this
      ->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'Default behavior: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the custom file.');

    // When a file that does not require jQuery has been added via
    // drupal_add_js() and the 'javascript_always_use_jquery' variable is set
    // to FALSE, drupal_get_js() should not return jQuery and other standard
    // scripts and setting, but it should still return the requested file.
    $this
      ->resetStaticVariables();
    variable_set('javascript_always_use_jquery', FALSE);
    drupal_add_js('misc/collapse.js', array(
      'requires_jquery' => FALSE,
    ));
    $javascript = drupal_get_js();
    $this
      ->assertTrue(strpos($javascript, 'misc/jquery.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include jquery.js.');
    $this
      ->assertTrue(strpos($javascript, 'misc/drupal.js') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include drupal.js.');
    $this
      ->assertTrue(strpos($javascript, 'Drupal.settings') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include Drupal.settings.');
    $this
      ->assertTrue(strpos($javascript, 'basePath') === FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added does not include the basePath Drupal setting.');
    $this
      ->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when a custom JavaScript file that does not require jQuery has been added includes the custom file.');
    variable_del('javascript_always_use_jquery');

    // When 'javascript_always_use_jquery' is set to FALSE and a file that does
    // not require jQuery is added, followed by one that does, drupal_get_js()
    // should return jQuery and other standard scripts and settings, in
    // addition to both of the requested files.
    $this
      ->resetStaticVariables();
    variable_set('javascript_always_use_jquery', FALSE);
    drupal_add_js('misc/collapse.js', array(
      'requires_jquery' => FALSE,
    ));
    drupal_add_js('misc/ajax.js');
    $javascript = drupal_get_js();
    $this
      ->assertTrue(strpos($javascript, 'misc/jquery.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes jquery.js.');
    $this
      ->assertTrue(strpos($javascript, 'misc/drupal.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes drupal.js.');
    $this
      ->assertTrue(strpos($javascript, 'Drupal.settings') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes Drupal.settings.');
    $this
      ->assertTrue(strpos($javascript, 'basePath') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the basePath Drupal setting.');
    $this
      ->assertTrue(strpos($javascript, 'misc/collapse.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the first custom file.');
    $this
      ->assertTrue(strpos($javascript, 'misc/ajax.js') !== FALSE, 'When "javascript_always_use_jquery" is FALSE: The JavaScript returned by drupal_get_js() when at least one custom JavaScript file that requires jQuery has been added includes the second custom file.');
    variable_del('javascript_always_use_jquery');
  }

  /**
   * Test drupal_add_js() sets preproccess to false when cache is set to false.
   */
  function testNoCache() {
    $javascript = drupal_add_js('misc/collapse.js', array(
      'cache' => FALSE,
    ));
    $this
      ->assertFalse($javascript['misc/collapse.js']['preprocess'], 'Setting cache to FALSE sets proprocess to FALSE when adding JavaScript.');
  }

  /**
   * Test adding a JavaScript file with a different group.
   */
  function testDifferentGroup() {
    $javascript = drupal_add_js('misc/collapse.js', array(
      'group' => JS_THEME,
    ));
    $this
      ->assertEqual($javascript['misc/collapse.js']['group'], JS_THEME, 'Adding a JavaScript file with a different group caches the given group.');
  }

  /**
   * Test adding a JavaScript file with a different weight.
   */
  function testDifferentWeight() {
    $javascript = drupal_add_js('misc/collapse.js', array(
      'weight' => 2,
    ));
    $this
      ->assertEqual($javascript['misc/collapse.js']['weight'], 2, 'Adding a JavaScript file with a different weight caches the given weight.');
  }

  /**
   * Tests JavaScript aggregation when files are added to a different scope.
   */
  function testAggregationOrder() {

    // Enable JavaScript aggregation.
    variable_set('preprocess_js', 1);
    drupal_static_reset('drupal_add_js');

    // Add two JavaScript files to the current request and build the cache.
    drupal_add_js('misc/ajax.js');
    drupal_add_js('misc/autocomplete.js');
    $js_items = drupal_add_js();
    drupal_build_js_cache(array(
      'misc/ajax.js' => $js_items['misc/ajax.js'],
      'misc/autocomplete.js' => $js_items['misc/autocomplete.js'],
    ));

    // Store the expected key for the first item in the cache.
    $cache = array_keys(variable_get('drupal_js_cache_files', array()));
    $expected_key = $cache[0];

    // Reset variables and add a file in a different scope first.
    variable_del('drupal_js_cache_files');
    drupal_static_reset('drupal_add_js');
    drupal_add_js('some/custom/javascript_file.js', array(
      'scope' => 'footer',
    ));
    drupal_add_js('misc/ajax.js');
    drupal_add_js('misc/autocomplete.js');

    // Rebuild the cache.
    $js_items = drupal_add_js();
    drupal_build_js_cache(array(
      'misc/ajax.js' => $js_items['misc/ajax.js'],
      'misc/autocomplete.js' => $js_items['misc/autocomplete.js'],
    ));

    // Compare the expected key for the first file to the current one.
    $cache = array_keys(variable_get('drupal_js_cache_files', array()));
    $key = $cache[0];
    $this
      ->assertEqual($key, $expected_key, 'JavaScript aggregation is not affected by ordering in different scopes.');
  }

  /**
   * Test JavaScript ordering.
   */
  function testRenderOrder() {

    // Add a bunch of JavaScript in strange ordering.
    drupal_add_js('(function($){alert("Weight 5 #1");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
      'weight' => 5,
    ));
    drupal_add_js('(function($){alert("Weight 0 #1");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
    ));
    drupal_add_js('(function($){alert("Weight 0 #2");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
    ));
    drupal_add_js('(function($){alert("Weight -8 #1");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
      'weight' => -8,
    ));
    drupal_add_js('(function($){alert("Weight -8 #2");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
      'weight' => -8,
    ));
    drupal_add_js('(function($){alert("Weight -8 #3");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
      'weight' => -8,
    ));
    drupal_add_js('http://example.com/example.js?Weight -5 #1', array(
      'type' => 'external',
      'scope' => 'footer',
      'weight' => -5,
    ));
    drupal_add_js('(function($){alert("Weight -8 #4");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
      'weight' => -8,
    ));
    drupal_add_js('(function($){alert("Weight 5 #2");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
      'weight' => 5,
    ));
    drupal_add_js('(function($){alert("Weight 0 #3");})(jQuery);', array(
      'type' => 'inline',
      'scope' => 'footer',
    ));

    // Construct the expected result from the regex.
    $expected = array(
      "-8 #1",
      "-8 #2",
      "-8 #3",
      "-8 #4",
      "-5 #1",
      // The external script.
      "0 #1",
      "0 #2",
      "0 #3",
      "5 #1",
      "5 #2",
    );

    // Retrieve the rendered JavaScript and test against the regex.
    $js = drupal_get_js('footer');
    $matches = array();
    if (preg_match_all('/Weight\\s([-0-9]+\\s[#0-9]+)/', $js, $matches)) {
      $result = $matches[1];
    }
    else {
      $result = array();
    }
    $this
      ->assertIdentical($result, $expected, 'JavaScript is added in the expected weight order.');
  }

  /**
   * Test rendering the JavaScript with a file's weight above jQuery's.
   */
  function testRenderDifferentWeight() {

    // JavaScript files are sorted first by group, then by the 'every_page'
    // flag, then by weight (see drupal_sort_css_js()), so to test the effect of
    // weight, we need the other two options to be the same.
    drupal_add_js('misc/collapse.js', array(
      'group' => JS_LIBRARY,
      'every_page' => TRUE,
      'weight' => -21,
    ));
    $javascript = drupal_get_js();
    $this
      ->assertTrue(strpos($javascript, 'misc/collapse.js') < strpos($javascript, 'misc/jquery.js'), 'Rendering a JavaScript file above jQuery.');
  }

  /**
   * Test altering a JavaScript's weight via hook_js_alter().
   *
   * @see simpletest_js_alter()
   */
  function testAlter() {

    // Add both tableselect.js and simpletest.js, with a larger weight on SimpleTest.
    drupal_add_js('misc/tableselect.js');
    drupal_add_js(drupal_get_path('module', 'simpletest') . '/simpletest.js', array(
      'weight' => 9999,
    ));

    // Render the JavaScript, testing if simpletest.js was altered to be before
    // tableselect.js. See simpletest_js_alter() to see where this alteration
    // takes place.
    $javascript = drupal_get_js();
    $this
      ->assertTrue(strpos($javascript, 'simpletest.js') < strpos($javascript, 'misc/tableselect.js'), 'Altering JavaScript weight through the alter hook.');
  }

  /**
   * Adds a library to the page and tests for both its JavaScript and its CSS.
   */
  function testLibraryRender() {
    $result = drupal_add_library('system', 'farbtastic');
    $this
      ->assertTrue($result !== FALSE, 'Library was added without errors.');
    $scripts = drupal_get_js();
    $styles = drupal_get_css();
    $this
      ->assertTrue(strpos($scripts, 'misc/farbtastic/farbtastic.js'), 'JavaScript of library was added to the page.');
    $this
      ->assertTrue(strpos($styles, 'misc/farbtastic/farbtastic.css'), 'Stylesheet of library was added to the page.');
  }

  /**
   * Adds a JavaScript library to the page and alters it.
   *
   * @see common_test_library_alter()
   */
  function testLibraryAlter() {

    // Verify that common_test altered the title of Farbtastic.
    $library = drupal_get_library('system', 'farbtastic');
    $this
      ->assertEqual($library['title'], 'Farbtastic: Altered Library', 'Registered libraries were altered.');

    // common_test_library_alter() also added a dependency on jQuery Form.
    drupal_add_library('system', 'farbtastic');
    $scripts = drupal_get_js();
    $this
      ->assertTrue(strpos($scripts, 'misc/jquery.form.js'), 'Altered library dependencies are added to the page.');
  }

  /**
   * Tests that multiple modules can implement the same library.
   *
   * @see common_test_library()
   */
  function testLibraryNameConflicts() {
    $farbtastic = drupal_get_library('common_test', 'farbtastic');
    $this
      ->assertEqual($farbtastic['title'], 'Custom Farbtastic Library', 'Alternative libraries can be added to the page.');
  }

  /**
   * Tests non-existing libraries.
   */
  function testLibraryUnknown() {
    $result = drupal_get_library('unknown', 'unknown');
    $this
      ->assertFalse($result, 'Unknown library returned FALSE.');
    drupal_static_reset('drupal_get_library');
    $result = drupal_add_library('unknown', 'unknown');
    $this
      ->assertFalse($result, 'Unknown library returned FALSE.');
    $scripts = drupal_get_js();
    $this
      ->assertTrue(strpos($scripts, 'unknown') === FALSE, 'Unknown library was not added to the page.');
  }

  /**
   * Tests the addition of libraries through the #attached['library'] property.
   */
  function testAttachedLibrary() {
    $element['#attached']['library'][] = array(
      'system',
      'farbtastic',
    );
    drupal_render($element);
    $scripts = drupal_get_js();
    $this
      ->assertTrue(strpos($scripts, 'misc/farbtastic/farbtastic.js'), 'The attached_library property adds the additional libraries.');
  }

  /**
   * Tests retrieval of libraries via drupal_get_library().
   */
  function testGetLibrary() {

    // Retrieve all libraries registered by a module.
    $libraries = drupal_get_library('common_test');
    $this
      ->assertTrue(isset($libraries['farbtastic']), 'Retrieved all module libraries.');

    // Retrieve all libraries for a module not implementing hook_library().
    // Note: This test installs Locale module.
    $libraries = drupal_get_library('locale');
    $this
      ->assertEqual($libraries, array(), 'Retrieving libraries from a module not implementing hook_library() returns an emtpy array.');

    // Retrieve a specific library by module and name.
    $farbtastic = drupal_get_library('common_test', 'farbtastic');
    $this
      ->assertEqual($farbtastic['version'], '5.3', 'Retrieved a single library.');

    // Retrieve a non-existing library by module and name.
    $farbtastic = drupal_get_library('common_test', 'foo');
    $this
      ->assertIdentical($farbtastic, FALSE, 'Retrieving a non-existing library returns FALSE.');
  }

  /**
   * Tests that the query string remains intact when adding JavaScript files
   *  that have query string parameters.
   */
  function testAddJsFileWithQueryString() {
    $this
      ->drupalGet('common-test/query-string');
    $query_string = variable_get('css_js_query_string', '0');
    $this
      ->assertRaw(drupal_get_path('module', 'node') . '/node.js?' . $query_string, 'Query string was appended correctly to js.');
  }

  /**
   * Resets static variables related to adding JavaScript to a page.
   */
  function resetStaticVariables() {
    drupal_static_reset('drupal_add_js');
    drupal_static_reset('drupal_add_library');
    drupal_static_reset('drupal_get_library');
  }

}

/**
 * Tests for drupal_render().
 */
class DrupalRenderTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'drupal_render()',
      'description' => 'Performs functional tests on drupal_render().',
      'group' => 'System',
    );
  }
  function setUp() {
    parent::setUp('common_test');
  }

  /**
   * Tests the output drupal_render() for some elementary input values.
   */
  function testDrupalRenderBasics() {
    $types = array(
      array(
        'name' => 'null',
        'value' => NULL,
        'expected' => '',
      ),
      array(
        'name' => 'no value',
        'expected' => '',
      ),
      array(
        'name' => 'empty string',
        'value' => '',
        'expected' => '',
      ),
      array(
        'name' => 'no access',
        'value' => array(
          '#markup' => 'foo',
          '#access' => FALSE,
        ),
        'expected' => '',
      ),
      array(
        'name' => 'previously printed',
        'value' => array(
          '#markup' => 'foo',
          '#printed' => TRUE,
        ),
        'expected' => '',
      ),
      array(
        'name' => 'printed in prerender',
        'value' => array(
          '#markup' => 'foo',
          '#pre_render' => array(
            'common_test_drupal_render_printing_pre_render',
          ),
        ),
        'expected' => '',
      ),
      array(
        'name' => 'basic renderable array',
        'value' => array(
          '#markup' => 'foo',
        ),
        'expected' => 'foo',
      ),
    );
    foreach ($types as $type) {
      $this
        ->assertIdentical(drupal_render($type['value']), $type['expected'], '"' . $type['name'] . '" input rendered correctly by drupal_render().');
    }
  }

  /**
   * Test sorting by weight.
   */
  function testDrupalRenderSorting() {
    $first = $this
      ->randomName();
    $second = $this
      ->randomName();

    // Build an array with '#weight' set for each element.
    $elements = array(
      'second' => array(
        '#weight' => 10,
        '#markup' => $second,
      ),
      'first' => array(
        '#weight' => 0,
        '#markup' => $first,
      ),
    );
    $output = drupal_render($elements);

    // The lowest weight element should appear last in $output.
    $this
      ->assertTrue(strpos($output, $second) > strpos($output, $first), 'Elements were sorted correctly by weight.');

    // Confirm that the $elements array has '#sorted' set to TRUE.
    $this
      ->assertTrue($elements['#sorted'], "'#sorted' => TRUE was added to the array");

    // Pass $elements through element_children() and ensure it remains
    // sorted in the correct order. drupal_render() will return an empty string
    // if used on the same array in the same request.
    $children = element_children($elements);
    $this
      ->assertTrue(array_shift($children) == 'first', 'Child found in the correct order.');
    $this
      ->assertTrue(array_shift($children) == 'second', 'Child found in the correct order.');

    // The same array structure again, but with #sorted set to TRUE.
    $elements = array(
      'second' => array(
        '#weight' => 10,
        '#markup' => $second,
      ),
      'first' => array(
        '#weight' => 0,
        '#markup' => $first,
      ),
      '#sorted' => TRUE,
    );
    $output = drupal_render($elements);

    // The elements should appear in output in the same order as the array.
    $this
      ->assertTrue(strpos($output, $second) < strpos($output, $first), 'Elements were not sorted.');
  }

  /**
   * Test #attached functionality in children elements.
   */
  function testDrupalRenderChildrenAttached() {

    // The cache system is turned off for POST requests.
    $request_method = $_SERVER['REQUEST_METHOD'];
    $_SERVER['REQUEST_METHOD'] = 'GET';

    // Create an element with a child and subchild.  Each element loads a
    // different JavaScript file using #attached.
    $parent_js = drupal_get_path('module', 'user') . '/user.js';
    $child_js = drupal_get_path('module', 'forum') . '/forum.js';
    $subchild_js = drupal_get_path('module', 'book') . '/book.js';
    $element = array(
      '#type' => 'fieldset',
      '#cache' => array(
        'keys' => array(
          'simpletest',
          'drupal_render',
          'children_attached',
        ),
      ),
      '#attached' => array(
        'js' => array(
          $parent_js,
        ),
      ),
      '#title' => 'Parent',
    );
    $element['child'] = array(
      '#type' => 'fieldset',
      '#attached' => array(
        'js' => array(
          $child_js,
        ),
      ),
      '#title' => 'Child',
    );
    $element['child']['subchild'] = array(
      '#attached' => array(
        'js' => array(
          $subchild_js,
        ),
      ),
      '#markup' => 'Subchild',
    );

    // Render the element and verify the presence of #attached JavaScript.
    drupal_render($element);
    $scripts = drupal_get_js();
    $this
      ->assertTrue(strpos($scripts, $parent_js), 'The element #attached JavaScript was included.');
    $this
      ->assertTrue(strpos($scripts, $child_js), 'The child #attached JavaScript was included.');
    $this
      ->assertTrue(strpos($scripts, $subchild_js), 'The subchild #attached JavaScript was included.');

    // Load the element from cache and verify the presence of the #attached
    // JavaScript.
    drupal_static_reset('drupal_add_js');
    $this
      ->assertTrue(drupal_render_cache_get($element), 'The element was retrieved from cache.');
    $scripts = drupal_get_js();
    $this
      ->assertTrue(strpos($scripts, $parent_js), 'The element #attached JavaScript was included when loading from cache.');
    $this
      ->assertTrue(strpos($scripts, $child_js), 'The child #attached JavaScript was included when loading from cache.');
    $this
      ->assertTrue(strpos($scripts, $subchild_js), 'The subchild #attached JavaScript was included when loading from cache.');
    $_SERVER['REQUEST_METHOD'] = $request_method;
  }

  /**
   * Test passing arguments to the theme function.
   */
  function testDrupalRenderThemeArguments() {
    $element = array(
      '#theme' => 'common_test_foo',
    );

    // Test that defaults work.
    $this
      ->assertEqual(drupal_render($element), 'foobar', 'Defaults work');
    $element = array(
      '#theme' => 'common_test_foo',
      '#foo' => $this
        ->randomName(),
      '#bar' => $this
        ->randomName(),
    );

    // Test that passing arguments to the theme function works.
    $this
      ->assertEqual(drupal_render($element), $element['#foo'] . $element['#bar'], 'Passing arguments to theme functions works');
  }

  /**
   * Test rendering form elements without passing through form_builder().
   */
  function testDrupalRenderFormElements() {

    // Define a series of form elements.
    $element = array(
      '#type' => 'button',
      '#value' => $this
        ->randomName(),
    );
    $this
      ->assertRenderedElement($element, '//input[@type=:type]', array(
      ':type' => 'submit',
    ));
    $element = array(
      '#type' => 'textfield',
      '#title' => $this
        ->randomName(),
      '#value' => $this
        ->randomName(),
    );
    $this
      ->assertRenderedElement($element, '//input[@type=:type]', array(
      ':type' => 'text',
    ));
    $element = array(
      '#type' => 'password',
      '#title' => $this
        ->randomName(),
    );
    $this
      ->assertRenderedElement($element, '//input[@type=:type]', array(
      ':type' => 'password',
    ));
    $element = array(
      '#type' => 'textarea',
      '#title' => $this
        ->randomName(),
      '#value' => $this
        ->randomName(),
    );
    $this
      ->assertRenderedElement($element, '//textarea');
    $element = array(
      '#type' => 'radio',
      '#title' => $this
        ->randomName(),
      '#value' => FALSE,
    );
    $this
      ->assertRenderedElement($element, '//input[@type=:type]', array(
      ':type' => 'radio',
    ));
    $element = array(
      '#type' => 'checkbox',
      '#title' => $this
        ->randomName(),
    );
    $this
      ->assertRenderedElement($element, '//input[@type=:type]', array(
      ':type' => 'checkbox',
    ));
    $element = array(
      '#type' => 'select',
      '#title' => $this
        ->randomName(),
      '#options' => array(
        0 => $this
          ->randomName(),
        1 => $this
          ->randomName(),
      ),
    );
    $this
      ->assertRenderedElement($element, '//select');
    $element = array(
      '#type' => 'file',
      '#title' => $this
        ->randomName(),
    );
    $this
      ->assertRenderedElement($element, '//input[@type=:type]', array(
      ':type' => 'file',
    ));
    $element = array(
      '#type' => 'item',
      '#title' => $this
        ->randomName(),
      '#markup' => $this
        ->randomName(),
    );
    $this
      ->assertRenderedElement($element, '//div[contains(@class, :class) and contains(., :markup)]/label[contains(., :label)]', array(
      ':class' => 'form-type-item',
      ':markup' => $element['#markup'],
      ':label' => $element['#title'],
    ));
    $element = array(
      '#type' => 'hidden',
      '#title' => $this
        ->randomName(),
      '#value' => $this
        ->randomName(),
    );
    $this
      ->assertRenderedElement($element, '//input[@type=:type]', array(
      ':type' => 'hidden',
    ));
    $element = array(
      '#type' => 'link',
      '#title' => $this
        ->randomName(),
      '#href' => $this
        ->randomName(),
      '#options' => array(
        'absolute' => TRUE,
      ),
    );
    $this
      ->assertRenderedElement($element, '//a[@href=:href and contains(., :title)]', array(
      ':href' => url($element['#href'], array(
        'absolute' => TRUE,
      )),
      ':title' => $element['#title'],
    ));
    $element = array(
      '#type' => 'fieldset',
      '#title' => $this
        ->randomName(),
    );
    $this
      ->assertRenderedElement($element, '//fieldset/legend[contains(., :title)]', array(
      ':title' => $element['#title'],
    ));
    $element['item'] = array(
      '#type' => 'item',
      '#title' => $this
        ->randomName(),
      '#markup' => $this
        ->randomName(),
    );
    $this
      ->assertRenderedElement($element, '//fieldset/div/div[contains(@class, :class) and contains(., :markup)]', array(
      ':class' => 'form-type-item',
      ':markup' => $element['item']['#markup'],
    ));
  }
  protected function assertRenderedElement(array $element, $xpath, array $xpath_args = array()) {
    $original_element = $element;
    $this
      ->drupalSetContent(drupal_render($element));
    $this
      ->verbose('<pre>' . check_plain(var_export($original_element, TRUE)) . '</pre>' . '<pre>' . check_plain(var_export($element, TRUE)) . '</pre>' . '<hr />' . $this
      ->drupalGetContent());

    // @see DrupalWebTestCase::xpath()
    $xpath = $this
      ->buildXPathQuery($xpath, $xpath_args);
    $element += array(
      '#value' => NULL,
    );
    $this
      ->assertFieldByXPath($xpath, $element['#value'], format_string('#type @type was properly rendered.', array(
      '@type' => var_export($element['#type'], TRUE),
    )));
  }

  /**
   * Tests caching of render items.
   */
  function testDrupalRenderCache() {

    // Force a request via GET.
    $request_method = $_SERVER['REQUEST_METHOD'];
    $_SERVER['REQUEST_METHOD'] = 'GET';

    // Create an empty element.
    $test_element = array(
      '#cache' => array(
        'cid' => 'render_cache_test',
      ),
      '#markup' => '',
    );

    // Render the element and confirm that it goes through the rendering
    // process (which will set $element['#printed']).
    $element = $test_element;
    drupal_render($element);
    $this
      ->assertTrue(isset($element['#printed']), 'No cache hit');

    // Render the element again and confirm that it is retrieved from the cache
    // instead (so $element['#printed'] will not be set).
    $element = $test_element;
    drupal_render($element);
    $this
      ->assertFalse(isset($element['#printed']), 'Cache hit');

    // Test that user 1 does not share the cache with other users who have the
    // same roles, even when DRUPAL_CACHE_PER_ROLE is used.
    $user1 = user_load(1);
    $first_authenticated_user = $this
      ->drupalCreateUser();
    $second_authenticated_user = $this
      ->drupalCreateUser();
    $user1->roles = array_intersect_key($user1->roles, array(
      DRUPAL_AUTHENTICATED_RID => TRUE,
    ));
    user_save($user1);

    // Load all the accounts again, to make sure we have complete account
    // objects.
    $user1 = user_load(1);
    $first_authenticated_user = user_load($first_authenticated_user->uid);
    $second_authenticated_user = user_load($second_authenticated_user->uid);
    $this
      ->assertEqual($user1->roles, $first_authenticated_user->roles, 'User 1 has the same roles as an authenticated user.');

    // Impersonate user 1 and render content that only user 1 should have
    // permission to see.
    $original_user = $GLOBALS['user'];
    $original_session_state = drupal_save_session();
    drupal_save_session(FALSE);
    $GLOBALS['user'] = $user1;
    $test_element = array(
      '#cache' => array(
        'keys' => array(
          'test',
        ),
        'granularity' => DRUPAL_CACHE_PER_ROLE,
      ),
    );
    $element = $test_element;
    $element['#markup'] = 'content for user 1';
    $output = drupal_render($element);
    $this
      ->assertEqual($output, 'content for user 1');

    // Verify the cache is working by rendering the same element but with
    // different markup passed in; the result should be the same.
    $element = $test_element;
    $element['#markup'] = 'should not be used';
    $output = drupal_render($element);
    $this
      ->assertEqual($output, 'content for user 1');

    // Verify that the first authenticated user does not see the same content
    // as user 1.
    $GLOBALS['user'] = $first_authenticated_user;
    $element = $test_element;
    $element['#markup'] = 'content for authenticated users';
    $output = drupal_render($element);
    $this
      ->assertEqual($output, 'content for authenticated users');

    // Verify that the second authenticated user shares the cache with the
    // first authenticated user.
    $GLOBALS['user'] = $second_authenticated_user;
    $element = $test_element;
    $element['#markup'] = 'should not be used';
    $output = drupal_render($element);
    $this
      ->assertEqual($output, 'content for authenticated users');

    // Restore the original logged-in user.
    $GLOBALS['user'] = $original_user;
    drupal_save_session($original_session_state);

    // Restore the previous request method.
    $_SERVER['REQUEST_METHOD'] = $request_method;
  }

}

/**
 * Test for valid_url().
 */
class ValidUrlTestCase extends DrupalUnitTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Valid URL',
      'description' => "Performs tests on Drupal's valid URL function.",
      'group' => 'System',
    );
  }

  /**
   * Test valid absolute URLs.
   */
  function testValidAbsolute() {
    $url_schemes = array(
      'http',
      'https',
      'ftp',
    );
    $valid_absolute_urls = array(
      'example.com',
      'www.example.com',
      'ex-ample.com',
      '3xampl3.com',
      'example.com/paren(the)sis',
      'example.com/index.html#pagetop',
      'example.com:8080',
      'subdomain.example.com',
      'example.com/index.php?q=node',
      'example.com/index.php?q=node&param=false',
      'user@www.example.com',
      'user:pass@www.example.com:8080/login.php?do=login&style=%23#pagetop',
      '127.0.0.1',
      'example.org?',
      'john%20doe:secret:foo@example.org/',
      'example.org/~,$\'*;',
      'caf%C3%A9.example.org',
      '[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80/index.html',
    );
    foreach ($url_schemes as $scheme) {
      foreach ($valid_absolute_urls as $url) {
        $test_url = $scheme . '://' . $url;
        $valid_url = valid_url($test_url, TRUE);
        $this
          ->assertTrue($valid_url, format_string('@url is a valid url.', array(
          '@url' => $test_url,
        )));
      }
    }
  }

  /**
   * Test invalid absolute URLs.
   */
  function testInvalidAbsolute() {
    $url_schemes = array(
      'http',
      'https',
      'ftp',
    );
    $invalid_ablosule_urls = array(
      '',
      'ex!ample.com',
      'ex%ample.com',
    );
    foreach ($url_schemes as $scheme) {
      foreach ($invalid_ablosule_urls as $url) {
        $test_url = $scheme . '://' . $url;
        $valid_url = valid_url($test_url, TRUE);
        $this
          ->assertFalse($valid_url, format_string('@url is NOT a valid url.', array(
          '@url' => $test_url,
        )));
      }
    }
  }

  /**
   * Test valid relative URLs.
   */
  function testValidRelative() {
    $valid_relative_urls = array(
      'paren(the)sis',
      'index.html#pagetop',
      'index.php?q=node',
      'index.php?q=node&param=false',
      'login.php?do=login&style=%23#pagetop',
    );
    foreach (array(
      '',
      '/',
    ) as $front) {
      foreach ($valid_relative_urls as $url) {
        $test_url = $front . $url;
        $valid_url = valid_url($test_url);
        $this
          ->assertTrue($valid_url, format_string('@url is a valid url.', array(
          '@url' => $test_url,
        )));
      }
    }
  }

  /**
   * Test invalid relative URLs.
   */
  function testInvalidRelative() {
    $invalid_relative_urls = array(
      'ex^mple',
      'example<>',
      'ex%ample',
    );
    foreach (array(
      '',
      '/',
    ) as $front) {
      foreach ($invalid_relative_urls as $url) {
        $test_url = $front . $url;
        $valid_url = valid_url($test_url);
        $this
          ->assertFALSE($valid_url, format_string('@url is NOT a valid url.', array(
          '@url' => $test_url,
        )));
      }
    }
  }

}

/**
 * Tests for CRUD API functions.
 */
class DrupalDataApiTest extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Data API functions',
      'description' => 'Tests the performance of CRUD APIs.',
      'group' => 'System',
    );
  }
  function setUp() {
    parent::setUp('database_test');
  }

  /**
   * Test the drupal_write_record() API function.
   */
  function testDrupalWriteRecord() {

    // Insert a record - no columns allow NULL values.
    $person = new stdClass();
    $person->name = 'John';
    $person->unknown_column = 123;
    $insert_result = drupal_write_record('test', $person);
    $this
      ->assertTrue($insert_result == SAVED_NEW, 'Correct value returned when a record is inserted with drupal_write_record() for a table with a single-field primary key.');
    $this
      ->assertTrue(isset($person->id), 'Primary key is set on record created with drupal_write_record().');
    $this
      ->assertIdentical($person->age, 0, 'Age field set to default value.');
    $this
      ->assertIdentical($person->job, 'Undefined', 'Job field set to default value.');

    // Verify that the record was inserted.
    $result = db_query("SELECT * FROM {test} WHERE id = :id", array(
      ':id' => $person->id,
    ))
      ->fetchObject();
    $this
      ->assertIdentical($result->name, 'John', 'Name field set.');
    $this
      ->assertIdentical($result->age, '0', 'Age field set to default value.');
    $this
      ->assertIdentical($result->job, 'Undefined', 'Job field set to default value.');
    $this
      ->assertFalse(isset($result->unknown_column), 'Unknown column was ignored.');

    // Update the newly created record.
    $person->name = 'Peter';
    $person->age = 27;
    $person->job = NULL;
    $update_result = drupal_write_record('test', $person, array(
      'id',
    ));
    $this
      ->assertTrue($update_result == SAVED_UPDATED, 'Correct value returned when a record updated with drupal_write_record() for table with single-field primary key.');

    // Verify that the record was updated.
    $result = db_query("SELECT * FROM {test} WHERE id = :id", array(
      ':id' => $person->id,
    ))
      ->fetchObject();
    $this
      ->assertIdentical($result->name, 'Peter', 'Name field set.');
    $this
      ->assertIdentical($result->age, '27', 'Age field set.');
    $this
      ->assertIdentical($result->job, '', 'Job field set and cast to string.');

    // Try to insert NULL in columns that does not allow this.
    $person = new stdClass();
    $person->name = 'Ringo';
    $person->age = NULL;
    $person->job = NULL;
    $insert_result = drupal_write_record('test', $person);
    $this
      ->assertTrue(isset($person->id), 'Primary key is set on record created with drupal_write_record().');
    $result = db_query("SELECT * FROM {test} WHERE id = :id", array(
      ':id' => $person->id,
    ))
      ->fetchObject();
    $this
      ->assertIdentical($result->name, 'Ringo', 'Name field set.');
    $this
      ->assertIdentical($result->age, '0', 'Age field set.');
    $this
      ->assertIdentical($result->job, '', 'Job field set.');

    // Insert a record - the "age" column allows NULL.
    $person = new stdClass();
    $person->name = 'Paul';
    $person->age = NULL;
    $insert_result = drupal_write_record('test_null', $person);
    $this
      ->assertTrue(isset($person->id), 'Primary key is set on record created with drupal_write_record().');
    $result = db_query("SELECT * FROM {test_null} WHERE id = :id", array(
      ':id' => $person->id,
    ))
      ->fetchObject();
    $this
      ->assertIdentical($result->name, 'Paul', 'Name field set.');
    $this
      ->assertIdentical($result->age, NULL, 'Age field set.');

    // Insert a record - do not specify the value of a column that allows NULL.
    $person = new stdClass();
    $person->name = 'Meredith';
    $insert_result = drupal_write_record('test_null', $person);
    $this
      ->assertTrue(isset($person->id), 'Primary key is set on record created with drupal_write_record().');
    $this
      ->assertIdentical($person->age, 0, 'Age field set to default value.');
    $result = db_query("SELECT * FROM {test_null} WHERE id = :id", array(
      ':id' => $person->id,
    ))
      ->fetchObject();
    $this
      ->assertIdentical($result->name, 'Meredith', 'Name field set.');
    $this
      ->assertIdentical($result->age, '0', 'Age field set to default value.');

    // Update the newly created record.
    $person->name = 'Mary';
    $person->age = NULL;
    $update_result = drupal_write_record('test_null', $person, array(
      'id',
    ));
    $result = db_query("SELECT * FROM {test_null} WHERE id = :id", array(
      ':id' => $person->id,
    ))
      ->fetchObject();
    $this
      ->assertIdentical($result->name, 'Mary', 'Name field set.');
    $this
      ->assertIdentical($result->age, NULL, 'Age field set.');

    // Insert a record - the "data" column should be serialized.
    $person = new stdClass();
    $person->name = 'Dave';
    $update_result = drupal_write_record('test_serialized', $person);
    $result = db_query("SELECT * FROM {test_serialized} WHERE id = :id", array(
      ':id' => $person->id,
    ))
      ->fetchObject();
    $this
      ->assertIdentical($result->name, 'Dave', 'Name field set.');
    $this
      ->assertIdentical($result->info, NULL, 'Info field set.');
    $person->info = array();
    $update_result = drupal_write_record('test_serialized', $person, array(
      'id',
    ));
    $result = db_query("SELECT * FROM {test_serialized} WHERE id = :id", array(
      ':id' => $person->id,
    ))
      ->fetchObject();
    $this
      ->assertIdentical(unserialize($result->info), array(), 'Info field updated.');

    // Update the serialized record.
    $data = array(
      'foo' => 'bar',
      1 => 2,
      'empty' => '',
      'null' => NULL,
    );
    $person->info = $data;
    $update_result = drupal_write_record('test_serialized', $person, array(
      'id',
    ));
    $result = db_query("SELECT * FROM {test_serialized} WHERE id = :id", array(
      ':id' => $person->id,
    ))
      ->fetchObject();
    $this
      ->assertIdentical(unserialize($result->info), $data, 'Info field updated.');

    // Run an update query where no field values are changed. The database
    // layer should return zero for number of affected rows, but
    // db_write_record() should still return SAVED_UPDATED.
    $update_result = drupal_write_record('test_null', $person, array(
      'id',
    ));
    $this
      ->assertTrue($update_result == SAVED_UPDATED, 'Correct value returned when a valid update is run without changing any values.');

    // Insert an object record for a table with a multi-field primary key.
    $node_access = new stdClass();
    $node_access->nid = mt_rand();
    $node_access->gid = mt_rand();
    $node_access->realm = $this
      ->randomName();
    $insert_result = drupal_write_record('node_access', $node_access);
    $this
      ->assertTrue($insert_result == SAVED_NEW, 'Correct value returned when a record is inserted with drupal_write_record() for a table with a multi-field primary key.');

    // Update the record.
    $update_result = drupal_write_record('node_access', $node_access, array(
      'nid',
      'gid',
      'realm',
    ));
    $this
      ->assertTrue($update_result == SAVED_UPDATED, 'Correct value returned when a record is updated with drupal_write_record() for a table with a multi-field primary key.');
  }

}

/**
 * Tests Simpletest error and exception collector.
 */
class DrupalErrorCollectionUnitTest extends DrupalWebTestCase {

  /**
   * Errors triggered during the test.
   *
   * Errors are intercepted by the overriden implementation
   * of DrupalWebTestCase::error below.
   *
   * @var Array
   */
  protected $collectedErrors = array();
  public static function getInfo() {
    return array(
      'name' => 'SimpleTest error collector',
      'description' => 'Performs tests on the Simpletest error and exception collector.',
      'group' => 'SimpleTest',
    );
  }
  function setUp() {
    parent::setUp('system_test', 'error_test');
  }

  /**
   * Test that simpletest collects errors from the tested site.
   */
  function testErrorCollect() {
    $this->collectedErrors = array();
    $this
      ->drupalGet('error-test/generate-warnings-with-report');
    $this
      ->assertEqual(count($this->collectedErrors), 3, 'Three errors were collected');
    if (count($this->collectedErrors) == 3) {
      $this
        ->assertError($this->collectedErrors[0], 'Notice', 'error_test_generate_warnings()', 'error_test.module', 'Undefined variable: bananas');
      $this
        ->assertError($this->collectedErrors[1], 'Warning', 'error_test_generate_warnings()', 'error_test.module', 'Division by zero');
      $this
        ->assertError($this->collectedErrors[2], 'User warning', 'error_test_generate_warnings()', 'error_test.module', 'Drupal is awesome');
    }
    else {

      // Give back the errors to the log report.
      foreach ($this->collectedErrors as $error) {
        parent::error($error['message'], $error['group'], $error['caller']);
      }
    }
  }

  /**
   * Error handler that collects errors in an array.
   *
   * This test class is trying to verify that simpletest correctly sees errors
   * and warnings. However, it can't generate errors and warnings that
   * propagate up to the testing framework itself, or these tests would always
   * fail. So, this special copy of error() doesn't propagate the errors up
   * the class hierarchy. It just stuffs them into a protected collectedErrors
   * array for various assertions to inspect.
   */
  protected function error($message = '', $group = 'Other', array $caller = NULL) {

    // Due to a WTF elsewhere, simpletest treats debug() and verbose()
    // messages as if they were an 'error'. But, we don't want to collect
    // those here. This function just wants to collect the real errors (PHP
    // notices, PHP fatal errors, etc.), and let all the 'errors' from the
    // 'User notice' group bubble up to the parent classes to be handled (and
    // eventually displayed) as normal.
    if ($group == 'User notice') {
      parent::error($message, $group, $caller);
    }
    else {
      $this->collectedErrors[] = array(
        'message' => $message,
        'group' => $group,
        'caller' => $caller,
      );
    }
  }

  /**
   * Assert that a collected error matches what we are expecting.
   */
  function assertError($error, $group, $function, $file, $message = NULL) {
    $this
      ->assertEqual($error['group'], $group, format_string("Group was %group", array(
      '%group' => $group,
    )));
    $this
      ->assertEqual($error['caller']['function'], $function, format_string("Function was %function", array(
      '%function' => $function,
    )));
    $this
      ->assertEqual(drupal_basename($error['caller']['file']), $file, format_string("File was %file", array(
      '%file' => $file,
    )));
    if (isset($message)) {
      $this
        ->assertEqual($error['message'], $message, format_string("Message was %message", array(
        '%message' => $message,
      )));
    }
  }

}

/**
 * Test the drupal_parse_info_file() API function.
 */
class ParseInfoFilesTestCase extends DrupalUnitTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Parsing .info files',
      'description' => 'Tests parsing .info files.',
      'group' => 'System',
    );
  }

  /**
   * Parse an example .info file an verify the results.
   */
  function testParseInfoFile() {
    $info_values = drupal_parse_info_file(drupal_get_path('module', 'simpletest') . '/tests/common_test_info.txt');
    $this
      ->assertEqual($info_values['simple_string'], 'A simple string', 'Simple string value was parsed correctly.', 'System');
    $this
      ->assertEqual($info_values['simple_constant'], WATCHDOG_INFO, 'Constant value was parsed correctly.', 'System');
    $this
      ->assertEqual($info_values['double_colon'], 'dummyClassName::', 'Value containing double-colon was parsed correctly.', 'System');
  }

}

/**
 * Tests for the drupal_system_listing() function.
 */
class DrupalSystemListingTestCase extends DrupalWebTestCase {

  /**
   * Use the testing profile; this is needed for testDirectoryPrecedence().
   */
  protected $profile = 'testing';
  public static function getInfo() {
    return array(
      'name' => 'Drupal system listing',
      'description' => 'Tests the mechanism for scanning system directories in drupal_system_listing().',
      'group' => 'System',
    );
  }

  /**
   * Test that files in different directories take precedence as expected.
   */
  function testDirectoryPrecedence() {

    // Define the module files we will search for, and the directory precedence
    // we expect.
    $expected_directories = array(
      // When the copy of the module in the profile directory is incompatible
      // with Drupal core, the copy in the core modules directory takes
      // precedence.
      'drupal_system_listing_incompatible_test' => array(
        'modules/simpletest/tests',
        'profiles/testing/modules',
      ),
      // When both copies of the module are compatible with Drupal core, the
      // copy in the profile directory takes precedence.
      'drupal_system_listing_compatible_test' => array(
        'profiles/testing/modules',
        'modules/simpletest/tests',
      ),
    );

    // This test relies on two versions of the same module existing in
    // different places in the filesystem. Without that, the test has no
    // meaning, so assert their presence first.
    foreach ($expected_directories as $module => $directories) {
      foreach ($directories as $directory) {
        $filename = "{$directory}/{$module}/{$module}.module";
        $this
          ->assertTrue(file_exists(DRUPAL_ROOT . '/' . $filename), format_string('@filename exists.', array(
          '@filename' => $filename,
        )));
      }
    }

    // Now scan the directories and check that the files take precedence as
    // expected.
    $files = drupal_system_listing('/\\.module$/', 'modules', 'name', 1);
    foreach ($expected_directories as $module => $directories) {
      $expected_directory = array_shift($directories);
      $expected_filename = "{$expected_directory}/{$module}/{$module}.module";
      $this
        ->assertEqual($files[$module]->uri, $expected_filename, format_string('Module @module was found at @filename.', array(
        '@module' => $module,
        '@filename' => $expected_filename,
      )));
    }
  }

}

/**
 * Tests for the format_date() function.
 */
class FormatDateUnitTest extends DrupalWebTestCase {

  /**
   * Arbitrary langcode for a custom language.
   */
  const LANGCODE = 'xx';
  public static function getInfo() {
    return array(
      'name' => 'Format date',
      'description' => 'Test the format_date() function.',
      'group' => 'System',
    );
  }
  function setUp() {
    parent::setUp('locale');
    variable_set('configurable_timezones', 1);
    variable_set('date_format_long', 'l, j. F Y - G:i');
    variable_set('date_format_medium', 'j. F Y - G:i');
    variable_set('date_format_short', 'Y M j - g:ia');
    variable_set('locale_custom_strings_' . self::LANGCODE, array(
      '' => array(
        'Sunday' => 'domingo',
      ),
      'Long month name' => array(
        'March' => 'marzo',
      ),
    ));
    $this
      ->refreshVariables();
  }

  /**
   * Test admin-defined formats in format_date().
   */
  function testAdminDefinedFormatDate() {

    // Create an admin user.
    $this->admin_user = $this
      ->drupalCreateUser(array(
      'administer site configuration',
    ));
    $this
      ->drupalLogin($this->admin_user);

    // Add new date format.
    $admin_date_format = 'j M y';
    $edit = array(
      'date_format' => $admin_date_format,
    );
    $this
      ->drupalPost('admin/config/regional/date-time/formats/add', $edit, 'Add format');

    // Add a new date format which just differs in the case.
    $admin_date_format_uppercase = 'j M Y';
    $edit = array(
      'date_format' => $admin_date_format_uppercase,
    );
    $this
      ->drupalPost('admin/config/regional/date-time/formats/add', $edit, t('Add format'));
    $this
      ->assertText(t('Custom date format added.'));

    // Add new date type.
    $edit = array(
      'date_type' => 'Example Style',
      'machine_name' => 'example_style',
      'date_format' => $admin_date_format,
    );
    $this
      ->drupalPost('admin/config/regional/date-time/types/add', $edit, 'Add date type');

    // Add a second date format with a different case than the first.
    $edit = array(
      'machine_name' => 'example_style_uppercase',
      'date_type' => 'Example Style Uppercase',
      'date_format' => $admin_date_format_uppercase,
    );
    $this
      ->drupalPost('admin/config/regional/date-time/types/add', $edit, t('Add date type'));
    $this
      ->assertText(t('New date type added successfully.'));
    $timestamp = strtotime('2007-03-10T00:00:00+00:00');
    $this
      ->assertIdentical(format_date($timestamp, 'example_style', '', 'America/Los_Angeles'), '9 Mar 07', 'Test format_date() using an admin-defined date type.');
    $this
      ->assertIdentical(format_date($timestamp, 'example_style_uppercase', '', 'America/Los_Angeles'), '9 Mar 2007', 'Test format_date() using an admin-defined date type with different case.');
    $this
      ->assertIdentical(format_date($timestamp, 'undefined_style'), format_date($timestamp, 'medium'), 'Test format_date() defaulting to medium when $type not found.');
  }

  /**
   * Tests for the format_date() function.
   */
  function testFormatDate() {
    global $user, $language;
    $timestamp = strtotime('2007-03-26T00:00:00+00:00');
    $this
      ->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test all parameters.');
    $this
      ->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test translated format.');
    $this
      ->assertIdentical(format_date($timestamp, 'custom', '\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), 'l, 25-Mar-07 17:00:00 PDT', 'Test an escaped format string.');
    $this
      ->assertIdentical(format_date($timestamp, 'custom', '\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\domingo, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash character.');
    $this
      ->assertIdentical(format_date($timestamp, 'custom', '\\\\\\l, d-M-y H:i:s T', 'America/Los_Angeles', self::LANGCODE), '\\l, 25-Mar-07 17:00:00 PDT', 'Test format containing backslash followed by escaped format string.');
    $this
      ->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London', 'en'), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.');

    // Create an admin user and add Spanish language.
    $admin_user = $this
      ->drupalCreateUser(array(
      'administer languages',
    ));
    $this
      ->drupalLogin($admin_user);
    $edit = array(
      'langcode' => self::LANGCODE,
      'name' => self::LANGCODE,
      'native' => self::LANGCODE,
      'direction' => LANGUAGE_LTR,
      'prefix' => self::LANGCODE,
    );
    $this
      ->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));

    // Create a test user to carry out the tests.
    $test_user = $this
      ->drupalCreateUser();
    $this
      ->drupalLogin($test_user);
    $edit = array(
      'language' => self::LANGCODE,
      'mail' => $test_user->mail,
      'timezone' => 'America/Los_Angeles',
    );
    $this
      ->drupalPost('user/' . $test_user->uid . '/edit', $edit, t('Save'));

    // Disable session saving as we are about to modify the global $user.
    drupal_save_session(FALSE);

    // Save the original user and language and then replace it with the test user and language.
    $real_user = $user;
    $user = user_load($test_user->uid, TRUE);
    $real_language = $language->language;
    $language->language = $user->language;

    // Simulate a Drupal bootstrap with the logged-in user.
    date_default_timezone_set(drupal_get_user_timezone());
    $this
      ->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'America/Los_Angeles', 'en'), 'Sunday, 25-Mar-07 17:00:00 PDT', 'Test a different language.');
    $this
      ->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T', 'Europe/London'), 'Monday, 26-Mar-07 01:00:00 BST', 'Test a different time zone.');
    $this
      ->assertIdentical(format_date($timestamp, 'custom', 'l, d-M-y H:i:s T'), 'domingo, 25-Mar-07 17:00:00 PDT', 'Test custom date format.');
    $this
      ->assertIdentical(format_date($timestamp, 'long'), 'domingo, 25. marzo 2007 - 17:00', 'Test long date format.');
    $this
      ->assertIdentical(format_date($timestamp, 'medium'), '25. marzo 2007 - 17:00', 'Test medium date format.');
    $this
      ->assertIdentical(format_date($timestamp, 'short'), '2007 Mar 25 - 5:00pm', 'Test short date format.');
    $this
      ->assertIdentical(format_date($timestamp), '25. marzo 2007 - 17:00', 'Test default date format.');

    // Restore the original user and language, and enable session saving.
    $user = $real_user;
    $language->language = $real_language;

    // Restore default time zone.
    date_default_timezone_set(drupal_get_user_timezone());
    drupal_save_session(TRUE);
  }

}

/**
 * Tests for the format_date() function.
 */
class DrupalAttributesUnitTest extends DrupalUnitTestCase {
  public static function getInfo() {
    return array(
      'name' => 'HTML Attributes',
      'description' => 'Perform unit tests on the drupal_attributes() function.',
      'group' => 'System',
    );
  }

  /**
   * Tests that drupal_html_class() cleans the class name properly.
   */
  function testDrupalAttributes() {

    // Verify that special characters are HTML encoded.
    $this
      ->assertIdentical(drupal_attributes(array(
      'title' => '&"\'<>',
    )), ' title="&amp;&quot;&#039;&lt;&gt;"', 'HTML encode attribute values.');

    // Verify multi-value attributes are concatenated with spaces.
    $attributes = array(
      'class' => array(
        'first',
        'last',
      ),
    );
    $this
      ->assertIdentical(drupal_attributes(array(
      'class' => array(
        'first',
        'last',
      ),
    )), ' class="first last"', 'Concatenate multi-value attributes.');

    // Verify empty attribute values are rendered.
    $this
      ->assertIdentical(drupal_attributes(array(
      'alt' => '',
    )), ' alt=""', 'Empty attribute value #1.');
    $this
      ->assertIdentical(drupal_attributes(array(
      'alt' => NULL,
    )), ' alt=""', 'Empty attribute value #2.');

    // Verify multiple attributes are rendered.
    $attributes = array(
      'id' => 'id-test',
      'class' => array(
        'first',
        'last',
      ),
      'alt' => 'Alternate',
    );
    $this
      ->assertIdentical(drupal_attributes($attributes), ' id="id-test" class="first last" alt="Alternate"', 'Multiple attributes.');

    // Verify empty attributes array is rendered.
    $this
      ->assertIdentical(drupal_attributes(array()), '', 'Empty attributes array.');
  }

}

/**
 * Tests converting PHP variables to JSON strings and back.
 */
class DrupalJSONTest extends DrupalUnitTestCase {
  public static function getInfo() {
    return array(
      'name' => 'JSON',
      'description' => 'Perform unit tests on the drupal_json_encode() and drupal_json_decode() functions.',
      'group' => 'System',
    );
  }

  /**
   * Tests converting PHP variables to JSON strings and back.
   */
  function testJSON() {

    // Setup a string with the full ASCII table.
    // @todo: Add tests for non-ASCII characters and Unicode.
    $str = '';
    for ($i = 0; $i < 128; $i++) {
      $str .= chr($i);
    }

    // Characters that must be escaped.
    // We check for unescaped " separately.
    $html_unsafe = array(
      '<',
      '>',
      '\'',
      '&',
    );

    // The following are the encoded forms of: < > ' & "
    $html_unsafe_escaped = array(
      '\\u003C',
      '\\u003E',
      '\\u0027',
      '\\u0026',
      '\\u0022',
    );

    // Verify there aren't character encoding problems with the source string.
    $this
      ->assertIdentical(strlen($str), 128, 'A string with the full ASCII table has the correct length.');
    foreach ($html_unsafe as $char) {
      $this
        ->assertTrue(strpos($str, $char) > 0, format_string('A string with the full ASCII table includes @s.', array(
        '@s' => $char,
      )));
    }

    // Verify that JSON encoding produces a string with all of the characters.
    $json = drupal_json_encode($str);
    $this
      ->assertTrue(strlen($json) > strlen($str), 'A JSON encoded string is larger than the source string.');

    // The first and last characters should be ", and no others.
    $this
      ->assertTrue($json[0] == '"', 'A JSON encoded string begins with ".');
    $this
      ->assertTrue($json[strlen($json) - 1] == '"', 'A JSON encoded string ends with ".');
    $this
      ->assertTrue(substr_count($json, '"') == 2, 'A JSON encoded string contains exactly two ".');

    // Verify that encoding/decoding is reversible.
    $json_decoded = drupal_json_decode($json);
    $this
      ->assertIdentical($str, $json_decoded, 'Encoding a string to JSON and decoding back results in the original string.');

    // Verify reversibility for structured data. Also verify that necessary
    // characters are escaped.
    $source = array(
      TRUE,
      FALSE,
      0,
      1,
      '0',
      '1',
      $str,
      array(
        'key1' => $str,
        'key2' => array(
          'nested' => TRUE,
        ),
      ),
    );
    $json = drupal_json_encode($source);
    foreach ($html_unsafe as $char) {
      $this
        ->assertTrue(strpos($json, $char) === FALSE, format_string('A JSON encoded string does not contain @s.', array(
        '@s' => $char,
      )));
    }

    // Verify that JSON encoding escapes the HTML unsafe characters
    foreach ($html_unsafe_escaped as $char) {
      $this
        ->assertTrue(strpos($json, $char) > 0, format_string('A JSON encoded string contains @s.', array(
        '@s' => $char,
      )));
    }
    $json_decoded = drupal_json_decode($json);
    $this
      ->assertNotIdentical($source, $json, 'An array encoded in JSON is not identical to the source.');
    $this
      ->assertIdentical($source, $json_decoded, 'Encoding structured data to JSON and decoding back results in the original data.');
  }

}

/**
 * Tests for RDF namespaces XML serialization.
 */
class DrupalGetRdfNamespacesTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'RDF namespaces XML serialization tests',
      'description' => 'Confirm that the serialization of RDF namespaces via drupal_get_rdf_namespaces() is output and parsed correctly in the XHTML document.',
      'group' => 'System',
    );
  }
  function setUp() {
    parent::setUp('rdf', 'rdf_test');
  }

  /**
   * Test RDF namespaces.
   */
  function testGetRdfNamespaces() {

    // Fetches the front page and extracts XML namespaces.
    $this
      ->drupalGet('');
    $xml = new SimpleXMLElement($this->content);
    $ns = $xml
      ->getDocNamespaces();
    $this
      ->assertEqual($ns['rdfs'], 'http://www.w3.org/2000/01/rdf-schema#', 'A prefix declared once is displayed.');
    $this
      ->assertEqual($ns['foaf'], 'http://xmlns.com/foaf/0.1/', 'The same prefix declared in several implementations of hook_rdf_namespaces() is valid as long as all the namespaces are the same.');
    $this
      ->assertEqual($ns['foaf1'], 'http://xmlns.com/foaf/0.1/', 'Two prefixes can be assigned the same namespace.');
    $this
      ->assertTrue(!isset($ns['dc']), 'A prefix with conflicting namespaces is discarded.');
  }

}

/**
 * Basic tests for drupal_add_feed().
 */
class DrupalAddFeedTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'drupal_add_feed() tests',
      'description' => 'Make sure that drupal_add_feed() works correctly with various constructs.',
      'group' => 'System',
    );
  }

  /**
   * Test drupal_add_feed() with paths, URLs, and titles.
   */
  function testBasicFeedAddNoTitle() {
    $path = $this
      ->randomName(12);
    $external_url = 'http://' . $this
      ->randomName(12) . '/' . $this
      ->randomName(12);
    $fully_qualified_local_url = url($this
      ->randomName(12), array(
      'absolute' => TRUE,
    ));
    $path_for_title = $this
      ->randomName(12);
    $external_for_title = 'http://' . $this
      ->randomName(12) . '/' . $this
      ->randomName(12);
    $fully_qualified_for_title = url($this
      ->randomName(12), array(
      'absolute' => TRUE,
    ));

    // Possible permutations of drupal_add_feed() to test.
    // - 'input_url': the path passed to drupal_add_feed(),
    // - 'output_url': the expected URL to be found in the header.
    // - 'title' == the title of the feed as passed into drupal_add_feed().
    $urls = array(
      'path without title' => array(
        'input_url' => $path,
        'output_url' => url($path, array(
          'absolute' => TRUE,
        )),
        'title' => '',
      ),
      'external URL without title' => array(
        'input_url' => $external_url,
        'output_url' => $external_url,
        'title' => '',
      ),
      'local URL without title' => array(
        'input_url' => $fully_qualified_local_url,
        'output_url' => $fully_qualified_local_url,
        'title' => '',
      ),
      'path with title' => array(
        'input_url' => $path_for_title,
        'output_url' => url($path_for_title, array(
          'absolute' => TRUE,
        )),
        'title' => $this
          ->randomName(12),
      ),
      'external URL with title' => array(
        'input_url' => $external_for_title,
        'output_url' => $external_for_title,
        'title' => $this
          ->randomName(12),
      ),
      'local URL with title' => array(
        'input_url' => $fully_qualified_for_title,
        'output_url' => $fully_qualified_for_title,
        'title' => $this
          ->randomName(12),
      ),
    );
    foreach ($urls as $description => $feed_info) {
      drupal_add_feed($feed_info['input_url'], $feed_info['title']);
    }
    $this
      ->drupalSetContent(drupal_get_html_head());
    foreach ($urls as $description => $feed_info) {
      $this
        ->assertPattern($this
        ->urlToRSSLinkPattern($feed_info['output_url'], $feed_info['title']), format_string('Found correct feed header for %description', array(
        '%description' => $description,
      )));
    }
  }

  /**
   * Create a pattern representing the RSS feed in the page.
   */
  function urlToRSSLinkPattern($url, $title = '') {

    // Escape any regular expression characters in the URL ('?' is the worst).
    $url = preg_replace('/([+?.*])/', '[$0]', $url);
    $generated_pattern = '%<link +rel="alternate" +type="application/rss.xml" +title="' . $title . '" +href="' . $url . '" */>%';
    return $generated_pattern;
  }

}

/**
 * Test for theme_feed_icon().
 */
class FeedIconTest extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Feed icon',
      'description' => 'Check escaping of theme_feed_icon()',
      'group' => 'System',
    );
  }

  /**
   * Check that special characters are correctly escaped. Test for issue #1211668.
   */
  function testFeedIconEscaping() {
    $variables = array();
    $variables['url'] = 'node';
    $variables['title'] = '<>&"\'';
    $text = theme_feed_icon($variables);
    preg_match('/title="(.*?)"/', $text, $matches);
    $this
      ->assertEqual($matches[1], 'Subscribe to &amp;&quot;&#039;', 'theme_feed_icon() escapes reserved HTML characters.');
  }

}

/**
 * Test array diff functions.
 */
class ArrayDiffUnitTest extends DrupalUnitTestCase {

  /**
   * Array to use for testing.
   *
   * @var array
   */
  protected $array1;

  /**
   * Array to use for testing.
   *
   * @var array
   */
  protected $array2;
  public static function getInfo() {
    return array(
      'name' => 'Array differences',
      'description' => 'Performs tests on drupal_array_diff_assoc_recursive().',
      'group' => 'System',
    );
  }
  function setUp() {
    parent::setUp();
    $this->array1 = array(
      'same' => 'yes',
      'different' => 'no',
      'array_empty_diff' => array(),
      'null' => NULL,
      'int_diff' => 1,
      'array_diff' => array(
        'same' => 'same',
        'array' => array(
          'same' => 'same',
        ),
      ),
      'array_compared_to_string' => array(
        'value',
      ),
      'string_compared_to_array' => 'value',
      'new' => 'new',
    );
    $this->array2 = array(
      'same' => 'yes',
      'different' => 'yes',
      'array_empty_diff' => array(),
      'null' => NULL,
      'int_diff' => '1',
      'array_diff' => array(
        'same' => 'different',
        'array' => array(
          'same' => 'same',
        ),
      ),
      'array_compared_to_string' => 'value',
      'string_compared_to_array' => array(
        'value',
      ),
    );
  }

  /**
   * Tests drupal_array_diff_assoc_recursive().
   */
  public function testArrayDiffAssocRecursive() {
    $expected = array(
      'different' => 'no',
      'int_diff' => 1,
      // The 'array' key should not be returned, as it's the same.
      'array_diff' => array(
        'same' => 'same',
      ),
      'array_compared_to_string' => array(
        'value',
      ),
      'string_compared_to_array' => 'value',
      'new' => 'new',
    );
    $this
      ->assertIdentical(drupal_array_diff_assoc_recursive($this->array1, $this->array2), $expected);
  }

}

/**
 * Tests the functionality of drupal_get_query_array().
 */
class DrupalGetQueryArrayTestCase extends DrupalWebTestCase {
  public static function getInfo() {
    return array(
      'name' => 'Query parsing using drupal_get_query_array()',
      'description' => 'Tests that drupal_get_query_array() correctly parses query parameters.',
      'group' => 'System',
    );
  }

  /**
   * Tests that drupal_get_query_array() correctly explodes query parameters.
   */
  public function testDrupalGetQueryArray() {
    $url = "http://my.site.com/somepath?foo=/content/folder[@name='foo']/folder[@name='bar']";
    $parsed = parse_url($url);
    $result = drupal_get_query_array($parsed['query']);
    $this
      ->assertEqual($result['foo'], "/content/folder[@name='foo']/folder[@name='bar']", 'drupal_get_query_array() should only explode parameters on the first equals sign.');
  }

}

Classes

Namesort descending Description
ArrayDiffUnitTest Test array diff functions.
CascadingStylesheetsTestCase Test the Drupal CSS system.
CascadingStylesheetsUnitTest CSS Unit Tests.
CommonSizeTestCase Tests file size parsing and formatting functions.
CommonURLUnitTest Tests for URL generation functions.
CommonXssUnitTest Tests for check_plain(), filter_xss(), format_string(), and check_url().
DrupalAddFeedTestCase Basic tests for drupal_add_feed().
DrupalAlterTestCase Tests for URL generation functions.
DrupalAttributesUnitTest Tests for the format_date() function.
DrupalDataApiTest Tests for CRUD API functions.
DrupalErrorCollectionUnitTest Tests Simpletest error and exception collector.
DrupalGetQueryArrayTestCase Tests the functionality of drupal_get_query_array().
DrupalGetRdfNamespacesTestCase Tests for RDF namespaces XML serialization.
DrupalGotoTest Testing drupal_goto and hook_drupal_goto_alter().
DrupalHTMLIdentifierTestCase Test for cleaning HTML identifiers.
DrupalHTTPRequestTestCase Test drupal_http_request().
DrupalHTTPResponseStatusLineTest Tests parsing of the HTTP response status line.
DrupalJSONTest Tests converting PHP variables to JSON strings and back.
DrupalRenderTestCase Tests for drupal_render().
DrupalSetContentTestCase Testing drupal_add_region_content and drupal_get_region_content.
DrupalSystemListingTestCase Tests for the drupal_system_listing() function.
DrupalTagsHandlingTestCase Test drupal_explode_tags() and drupal_implode_tags().
FeedIconTest Test for theme_feed_icon().
FormatDateUnitTest Tests for the format_date() function.
JavaScriptTestCase Tests for the JavaScript system.
ParseInfoFilesTestCase Test the drupal_parse_info_file() API function.
UrlIsExternalUnitTest Tests url_is_external().
ValidUrlTestCase Test for valid_url().