home | career | drupal | java | mac | mysql | perl | php | scala | uml | unix

Drupal example source code file (common.test)

This example Drupal source code file (common.test) is included in the DevDaily.com "Drupal Source Code Warehouse" project. The intent of this project is to help you "Learn Drupal by Example".

PHP - Drupal tags/keywords

array, css, directory, file, foreach, function, is, javascript, php, result, string, the, url, with

The common.test Drupal example source code

<?php
// $Id: common.test,v 1.138 2010/12/15 04:21:39 webchick Exp $

/**
 * @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, t('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, t('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, t('First argument to drupal_alter() was altered.'));
    $this->assertEqual($entity_copy, $entity_expected, t('Second argument to drupal_alter() was altered.'));
    $this->assertEqual($array2_copy, $array2_expected, t('Third argument to drupal_alter() was altered.'));
  }
}

/**
 * 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, t('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'), t('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), t('Custom class @class is present on link when requested', array('@class' => $class)));
    $this->assertTrue($this->hasClass($link, 'active'), t('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, t("\$_GET['q'] was removed."));

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

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

    // Second-level exclusion.
    $result = $original;
    unset($result['b']['d']);
    $this->assertEqual(drupal_get_query_parameters($original, array('b[d]')), $result, t("'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, t("'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, t("'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', t('Value was properly encoded.'));
    $this->assertEqual(drupal_http_build_query(array(' &#//+%20@۞' => 'a')), '%20%26%23%2F%2F%2B%2520%40%DB%9E=a', t('Key was properly encoded.'));
    $this->assertEqual(drupal_http_build_query(array('a' => '1', 'b' => '2', 'c' => '3')), 'a=1&b=2&c=3', t('Multiple values were properly concatenated.'));
    $this->assertEqual(drupal_http_build_query(array('a' => array('b' => '2', 'c' => '3'), 'd' => 'foo')), 'a[b]=2&a[c]=3&d=foo', t('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, t('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, t('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, t('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), t('Correctly identified an external URL.'));

    // Test the parsing of absolute URLs.
    $result = array(
      'path' => 'http://drupal.org/foo/bar',
      'query' => array('foo' => 'bar', 'bar' => 'baz', 'baz' => ''),
      'fragment' => 'foo',
    );
    $this->assertEqual(drupal_parse_url($url), $result, t('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, t('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, t('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, t('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), t('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, t('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, t('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, t('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, t('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, t('External URL query string can be extended with a custom query string in $options.'));
  }
}

/**
 * Tests for the check_plain() and filter_xss() functions.
 */
class CommonXssUnitTest extends DrupalUnitTestCase {

  public static function getInfo() {
    return array(
      'name' => 'String filtering tests',
      'description' => 'Confirm that check_plain(), filter_xss(), 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("Foo\xC0barbaz");
     $this->assertEqual($text, '', 'check_plain() rejects invalid sequence "Foo\xC0barbaz"');
     // Ignore PHP 5.3+ invalid multibyte sequence warning.
     $text = @check_plain("\xc2\"");
     $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("Foo\xC0barbaz");
     $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.');
  }

  /**
   * 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, t('check_url() filters a URL and encodes it for HTML.'));
    $this->assertIdentical(drupal_strip_dangerous_protocols($url), $expected_plain, t('drupal_strip_dangerous_protocols() filters a URL and returns plain text.'));
  }
}

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(235346823821125814962843827 / ($this->exact_test_cases['1 YB']), 2) . ' YB' => 235346823821125814962843827, // yottabytes
    );
    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 DrupalWebTestCase {
  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, t('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(t('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(), t('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, t('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', t('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(), t('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, t('Rendered CSS includes the added stylesheet.'));
  }

  /**
   * 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, t('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, t('Rendering preprocessed inline CSS adds it to the page.'));
  }

  /**
   * 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, t('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, t('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, t('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, t('The overriding CSS file is output.'));
    $this->assert(strpos($styles, $system . '/system.base.css') === FALSE, t('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, t('The overriding CSS file is output.'));
    $this->assert(strpos($styles, $simpletest . '/tests/system.base.css') === FALSE, t('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, t('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, t('Query string was appended correctly to css.'));
    $this->assertRaw(drupal_get_path('module', 'node') . '/node-fake.css?arg1=value1&amp;arg2=value2', t('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, t('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, t('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', t('Strip invalid characters.'));
  }

  /**
   * 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--ü', t('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, t('Verify valid characters pass through.'));

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

    // Verify Drupal coding standards are enforced.
    $this->assertIdentical(drupal_html_id('ID NAME_[1]'), 'id-name-1', t('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', t('Test the uniqueness of IDs #1.'));
    $this->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id--2', t('Test the uniqueness of IDs #2.'));
    $this->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id--3', t('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. (http://drupal.org/node/472820)
   * - Proper URLs in imported files. (http://drupal.org/node/265719)
   * - Retain pseudo-selectors. (http://drupal.org/node/460448)
   */
  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',
      'comment_hacks.css'
    );
    $path = drupal_get_path('module', 'simpletest') . '/files/css_test_files';
    foreach ($testfiles as $file) {
      $expected = file_get_contents("$path/$file.unoptimized.css");
      $unoptimized_output = drupal_load_stylesheet("$path/$file.unoptimized.css", FALSE);
      $this->assertEqual($unoptimized_output, $expected, t('Unoptimized CSS file has expected contents (@file)', array('@file' => $file)));

      $expected = file_get_contents("$path/$file.optimized.css");
      $optimized_output = drupal_load_stylesheet("$path/$file", TRUE);
      $this->assertEqual($optimized_output, $expected, t('Optimized CSS file has expected contents (@file)', array('@file' => $file)));

      // Repeat the tests by accessing the stylesheets by URL.
      $expected = file_get_contents("$path/$file.unoptimized.css");
      $unoptimized_output_url = drupal_load_stylesheet($GLOBALS['base_url'] . "/$path/$file.unoptimized.css", FALSE);
      $this->assertEqual($unoptimized_output, $expected, t('Unoptimized CSS file (loaded from an URL) has expected contents (@file)', array('@file' => $file)));

      $expected = file_get_contents("$path/$file.optimized.css");
      $optimized_output = drupal_load_stylesheet($GLOBALS['base_url'] . "/$path/$file", TRUE);
      $this->assertEqual($optimized_output, $expected, t('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');
  }

  function testDrupalHTTPRequest() {
    global $is_https;

    // Parse URL schema.
    $missing_scheme = drupal_http_request('example.com/path');
    $this->assertEqual($missing_scheme->code, -1002, t('Returned with "-1002" error code.'));
    $this->assertEqual($missing_scheme->error, 'missing schema', t('Returned with "missing schema" error message.'));

    $unable_to_parse = drupal_http_request('http:///path');
    $this->assertEqual($unable_to_parse->code, -1001, t('Returned with "-1001" error code.'));
    $this->assertEqual($unable_to_parse->error, 'unable to parse URL', t('Returned with "unable to parse URL" error message.'));

    // Fetch page.
    $result = drupal_http_request(url('node', array('absolute' => TRUE)));
    $this->assertEqual($result->code, 200, t('Fetched page successfully.'));
    $this->drupalSetContent($result->data);
    $this->assertTitle(t('Welcome to @site-name | @site-name', array('@site-name' => variable_get('site_name', 'Drupal'))), t('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),  t('Result protocol is returned.'));
    $this->assertEqual($result->code, '404', t('Result code is 404'));
    $this->assertEqual($result->status_message, 'Not Found', t('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, t('Request timed out (%time seconds).', array('%time' => $time)));
      $this->assertTrue($result->error, t('An error message was returned.'));
      $this->assertEqual($result->code, HTTP_REQUEST_TIMEOUT, t('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, t('$_SERVER["PHP_AUTH_USER"] is passed correctly.'));
    $this->assertRaw($password, t('$_SERVER["PHP_AUTH_PW"] 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, t('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), t('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, t('301 redirect to invalid URL returned with error code !error.', array('!error' => $redirect_invalid->error)));
    $this->assertEqual($redirect_invalid->error, 'missing schema', t('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, t('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', t('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, t('301 redirect to invalid URL returned with error code !error.', array('!error' => $redirect_invalid->error)));
    $this->assertEqual($redirect_invalid->error, 'invalid schema ftp', t('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, t('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), t('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, t('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), t('drupal_http_request does not follow 307 redirect if max_redirects = 0.'));
  }
}

/**
 * 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 = array_keys(system_region_list($theme_key));
    $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], t('@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], t('@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, t('Expected response code was sent.'));
    $this->assertText('drupal_goto', t('Drupal goto redirect succeeded.'));
    $this->assertEqual($this->getUrl(), url('common-test/drupal_goto', array('absolute' => TRUE)), t('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, t('Expected response code was sent.'));
    $this->assertText('drupal_goto', t('Drupal goto redirect succeeded.'));
    $this->assertEqual($this->getUrl(), url('common-test/drupal_goto', array('query' => array('foo' => '123'), 'absolute' => TRUE)), t('Drupal goto redirected to expected URL.'));

    // 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', t('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)), t('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"), t("Drupal goto stopped program."));
    $this->assertNoText('drupal_goto_fail', t("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, t('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, t('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(), t('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), t('jQuery is added when a file is added.'));
    $this->assertTrue(array_key_exists('misc/drupal.js', $javascript), t('Drupal.js is added when file is added.'));
    $this->assertTrue(array_key_exists('misc/collapse.js', $javascript), t('JavaScript files are correctly added.'));
    $this->assertEqual(base_path(), $javascript['settings']['data'][0]['basePath'], t('Base path 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'][1]['dries'], t('JavaScript setting is set correctly.'));
    $this->assertEqual('rocks', $javascript['settings']['data'][1]['drupal'], t('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), t('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, t('Rendered JavaScript header returns basePath setting.'));
    $this->assertTrue(strpos($javascript, 'misc/jquery.js') > 0, t('Rendered JavaScript header includes jQuery.'));

    // Test whether drupal_add_js can be used to override a previous setting.
    $this->assertTrue(strpos($javascript, 'commonTestShouldAppear') > 0, t('Rendered JavaScript header returns custom setting.'));
    $this->assertTrue(strpos($javascript, 'commonTestShouldNotAppear') === FALSE, t('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, t('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, t('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(), t('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), t('jQuery is added when inline scripts are added.'));
    $data = end($javascript);
    $this->assertEqual($inline, $data['data'], t('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, t('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, t('Rendered JavaScript footer returns the inline code.'));
  }

  /**
   * 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'], t('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, t('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, t('Adding a JavaScript file with a different weight caches the given weight.'));
  }

  /**
   * 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, t('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'), t('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'), t('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, t('Library was added without errors.'));
    $scripts = drupal_get_js();
    $styles = drupal_get_css();
    $this->assertTrue(strpos($scripts, 'misc/farbtastic/farbtastic.js'), t('JavaScript of library was added to the page.'));
    $this->assertTrue(strpos($styles, 'misc/farbtastic/farbtastic.css'), t('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', t('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'), t('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', t('Alternative libraries can be added to the page.'));
  }

  /**
   * Tests non-existing libraries.
   */
  function testLibraryUnknown() {
    $result = drupal_get_library('unknown', 'unknown');
    $this->assertFalse($result, t('Unknown library returned FALSE.'));
    drupal_static_reset('drupal_get_library');

    $result = drupal_add_library('unknown', 'unknown');
    $this->assertFalse($result, t('Unknown library returned FALSE.'));
    $scripts = drupal_get_js();
    $this->assertTrue(strpos($scripts, 'unknown') === FALSE, t('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'), t('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']), t('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(), t('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', t('Retrieved a single library.'));
    // Retrieve a non-existing library by module and name.
    $farbtastic = drupal_get_library('common_test', 'foo');
    $this->assertIdentical($farbtastic, FALSE, t('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, t('Query string was appended correctly to js.'));
  }
}

/**
 * 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');
  }

  /**
   * 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), t('Elements were sorted correctly by weight.'));

    // Confirm that the $elements array has '#sorted' set to TRUE.
    $this->assertTrue($elements['#sorted'], t("'#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', t('Child found in the correct order.'));
    $this->assertTrue(array_shift($children) == 'second', t('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), t('Elements were not sorted.'));
  }

  /**
   * 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'], t('#type @type was properly rendered.', array(
      '@type' => var_export($element['#type'], TRUE),
    )));
  }
}

/**
 * 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, t('@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, t('@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, t('@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, t('@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, t('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), t('Primary key is set on record created with drupal_write_record().'));
    $this->assertIdentical($person->age, 0, t('Age field set to default value.'));
    $this->assertIdentical($person->job, 'Undefined', t('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', t('Name field set.'));
    $this->assertIdentical($result->age, '0', t('Age field set to default value.'));
    $this->assertIdentical($result->job, 'Undefined', t('Job field set to default value.'));
    $this->assertFalse(isset($result->unknown_column), t('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, t('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', t('Name field set.'));
    $this->assertIdentical($result->age, '27', t('Age field set.'));
    $this->assertIdentical($result->job, '', t('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), t('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', t('Name field set.'));
    $this->assertIdentical($result->age, '0', t('Age field set.'));
    $this->assertIdentical($result->job, '', t('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), t('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', t('Name field set.'));
    $this->assertIdentical($result->age, NULL, t('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), t('Primary key is set on record created with drupal_write_record().'));
    $this->assertIdentical($person->age, 0, t('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', t('Name field set.'));
    $this->assertIdentical($result->age, '0', t('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', t('Name field set.'));
    $this->assertIdentical($result->age, NULL, t('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', t('Name field set.'));
    $this->assertIdentical($result->info, NULL, t('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(), t('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, t('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, t('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, t('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, t('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, t('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);
    }
    // Everything else should be collected but not propagated.
    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, t("Group was %group", array('%group' => $group)));
    $this->assertEqual($error['caller']['function'], $function, t("Function was %function", array('%function' => $function)));
    $this->assertEqual(basename($error['caller']['file']), $file, t("File was %file", array('%file' => $file)));
    if (isset($message)) {
      $this->assertEqual($error['message'], $message, t("Message was %message", array('%message' => $message)));
    }
  }
}

/**
 * Test the drupal_parse_info_file() API function.
 */
class ParseInfoFilesTestCase extends DrupalWebTestCase {
  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', t('Simple string value was parsed correctly.'), t('System'));
    $this->assertEqual($info_values['simple_constant'], WATCHDOG_INFO, t('Constant value was parsed correctly.'), t('System'));
    $this->assertEqual($info_values['double_colon'], 'dummyClassName::', t('Value containing double-colon was parsed correctly.'), t('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), t('@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, t('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, t('Add format'));

    // 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, t('Add date type'));

    $timestamp = strtotime('2007-03-10T00:00:00+00:00');
    $this->assertIdentical(format_date($timestamp, 'example_style', '', 'America/Los_Angeles'), '9 Mar 07', t('Test format_date() using an admin-defined date type.'));
    $this->assertIdentical(format_date($timestamp, 'undefined_style'), format_date($timestamp, 'medium'), t('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', t('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', t('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', t('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', t('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', t('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', t('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', t('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', t('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', t('Test custom date format.'));
    $this->assertIdentical(format_date($timestamp, 'long'), 'domingo, 25. marzo 2007 - 17:00', t('Test long date format.'));
    $this->assertIdentical(format_date($timestamp, 'medium'), '25. marzo 2007 - 17:00', t('Test medium date format.'));
    $this->assertIdentical(format_date($timestamp, 'short'), '2007 Mar 25 - 5:00pm', t('Test short date format.'));
    $this->assertIdentical(format_date($timestamp), '25. marzo 2007 - 17:00', t('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;"', t('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"', t('Concatenate multi-value attributes.'));

    // Verify empty attribute values are rendered.
    $this->assertIdentical(drupal_attributes(array('alt' => '')), ' alt=""', t('Empty attribute value #1.'));
    $this->assertIdentical(drupal_attributes(array('alt' => NULL)), ' alt=""', t('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"', t('Multiple attributes.'));

    // Verify empty attributes array is rendered.
    $this->assertIdentical(drupal_attributes(array()), '', t('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.
    $html_unsafe = array('<', '>', '&');
    $html_unsafe_escaped = array('\u003c', '\u003e', '\u0026');

    // Verify there aren't character encoding problems with the source string.
    $this->assertIdentical(strlen($str), 128, t('A string with the full ASCII table has the correct length.'));
    foreach ($html_unsafe as $char) {
      $this->assertTrue(strpos($str, $char) > 0, t('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), t('A JSON encoded string is larger than the source string.'));

    // Verify that encoding/decoding is reversible.
    $json_decoded = drupal_json_decode($json);
    $this->assertIdentical($str, $json_decoded, t('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, t('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, t('A JSON encoded string contains @s.', array('@s' => $char)));
    }
    $json_decoded = drupal_json_decode($json);
    $this->assertNotIdentical($source, $json, t('An array encoded in JSON is not identical to the source.'));
    $this->assertIdentical($source, $json_decoded, t('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#', t('A prefix declared once is displayed.'));
    $this->assertEqual($ns['foaf'], 'http://xmlns.com/foaf/0.1/', t('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/', t('Two prefixes can be assigned the same namespace.'));
    $this->assertTrue(!isset($ns['dc']), t('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']), t('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;
  }
}

Other Drupal examples (source code examples)

Here is a short list of links related to this Drupal common.test source code file:

new blog posts

"Drupal" is a registered trademark of Dries Buytaert.

my drupal tutorials and examples  

Copyright 1998-2016 Alvin Alexander, alvinalexander.com
All Rights Reserved.

Beginning in 2016, a portion of the proceeds from pages under the '/drupal-code-examples/' URI will be donated to charity.