diff --git a/core/lib/Drupal/Core/Cache/APCBackend.php b/core/lib/Drupal/Core/Cache/APCBackend.php
new file mode 100644
index 0000000..29e108e
--- /dev/null
+++ b/core/lib/Drupal/Core/Cache/APCBackend.php
@@ -0,0 +1,350 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Cache\APCBackend.
+ */
+
+namespace Drupal\Core\Cache;
+
+/**
+ * Stores cache items in the Alternative PHP Cache (APC).
+ */
+class APCBackend implements CacheBackendInterface {
+
+  /**
+   * The name of the cache bin to use.
+   *
+   * @var string
+   */
+  protected $bin;
+
+  /**
+   * The APC user variable key prefix to use for all cache items.
+   *
+   * @var string
+   */
+  protected $prefix;
+
+  /**
+   * The APC user variable key prefix of this cache bin.
+   *
+   * @var string
+   */
+  protected $binPrefix;
+
+  /**
+   * The APC user variable key prefix of the cache tags bin.
+   *
+   * @var string
+   */
+  protected $tagsPrefix;
+
+  /**
+   * A static cache of all tags checked during the request.
+   *
+   * @var array
+   */
+  protected static $tagCache = array();
+
+  /**
+   * {@inheritdoc}
+   *
+   * @todo Inject $prefix.
+   */
+  public function __construct($bin, $prefix) {
+    $this->bin = $bin;
+    $this->prefix = $prefix;
+
+    // Set the bin-specific prefix.
+    $this->binPrefix = $this->prefix . $this->bin . '::';
+    // Set the cache tags bin prefix.
+    $this->tagsPrefix = $this->prefix . 'tags::';
+  }
+
+  /**
+   * Prepends the APC user variable prefix for this bin to a cache item ID.
+   *
+   * @param string $cid
+   *   The cache item ID to prefix.
+   *
+   * @return string
+   *   The APC key for the cache item ID.
+   */
+  protected function getAPCKey($cid) {
+    return $this->binPrefix . $cid;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function get($cid, $allow_invalid = FALSE) {
+    $cache = apc_fetch($this->getAPCKey($cid));
+    return $this->prepareItem($cache, $allow_invalid);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMultiple(&$cids, $allow_invalid = FALSE) {
+    // Translate the requested cache item IDs to APC keys.
+    $map = array();
+    foreach ($cids as $cid) {
+      $map[$this->getAPCKey($cid)] = $cid;
+    }
+
+    $result = apc_fetch(array_keys($map));
+    $cache = array();
+    foreach ($result as $key => $item) {
+      $item = $this->prepareItem($item);
+      if ($item) {
+        $cache[$map[$key]] = $item;
+      }
+    }
+    unset($result);
+
+    $cids = array_diff($cids, array_keys($cache));
+    return $cache;
+  }
+
+  /**
+   * Returns all cached items, optionally limited by a cache ID prefix.
+   *
+   * APC is a memory cache, shared across all server processes. To prevent cache
+   * item clashes with other applications/installations, every cache item is
+   * prefixed with a unique string for this application. Therefore, functions
+   * like apc_clear_cache() cannot be used, and instead, a list of all cache
+   * items belonging to this application need to be retrieved through this
+   * method instead.
+   *
+   * @param string $prefix
+   *   (optional) A cache ID prefix to limit the result to.
+   *
+   * @return APCIterator
+   *   An APCIterator containing matched items.
+   */
+  public function getAll($prefix = '') {
+    return new \APCIterator('user', '/^' . preg_quote($this->binPrefix . $prefix, '/') . '/', APC_ITER_KEY);
+  }
+
+  /**
+   * Prepares a cached item.
+   *
+   * Checks that items are either permanent or did not expire.
+   *
+   * @param stdClass $cache
+   *   An item loaded from cache_get() or cache_get_multiple().
+   *
+   * @return mixed
+   *   The item with data unserialized as appropriate or FALSE if there is no
+   *   valid item to load.
+   */
+  protected function prepareItem($cache, $allow_invalid) {
+    if (!isset($cache->data)) {
+      return FALSE;
+    }
+
+    // The cache data is invalid if any of its tags have been cleared since.
+    if ($cache->tags) {
+      $cache->tags = explode(' ', $cache->tags);
+      if (!$this->validTags($cache->checksum, $cache->tags)) {
+        return FALSE;
+      }
+    }
+
+    if (!$allow_invalid && !$cache->valid) {
+      return FALSE;
+    }
+
+    return $cache;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function set($cid, $data, $expire = CacheBackendInterface::CACHE_PERMANENT, array $tags = array()) {
+    $cache = new \stdClass();
+    $cache->cid = $cid;
+    $cache->created = REQUEST_TIME;
+    $cache->expire = $expire;
+    $cache->tags = implode(' ', $this->flattenTags($tags));
+    $cache->checksum = $this->checksumTags($tags);
+
+    // APC serializes/unserializes any structure itself.
+    $cache->serialized = 0;
+    $cache->data = $data;
+
+    // apc_store()'s $ttl argument can be omitted but also set to 0 (zero),
+    // which happens to be identical to CACHE_PERMANENT.
+    apc_store($this->getAPCKey($cid), $cache, $expire);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete($cid) {
+    apc_delete($this->getAPCKey($cid));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteMultiple(array $cids) {
+    apc_delete(array_map(array($this, 'getAPCKey'), $cids));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteAll() {
+    foreach ($this->getAll() as $key => $data) {
+      apc_delete($key);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function garbageCollection() {
+    // Any call to apc_fetch() causes APC to expunge expired items.
+    apc_fetch('');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeBin() {
+    // @todo Just flush here instead? or do nothing?
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isEmpty() {
+    return $this->getAll()->getTotalCount() === 0;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidate($cid) {
+    $this->invalidateMultiple(array($cid));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidateMultiple(array $cids) {
+    foreach ($this->getMultiple($cids) as $cache) {
+      $cache->expire = REQUEST_TIME - 1;
+      $this->set($cache->cid, $cache);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidateAll() {
+    foreach ($this->getAll() as $cache) {
+      $cache->expire = REQUEST_TIME - 1;
+      $this->set($cache->cid, $cache);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteTags(array $tags) {
+    foreach ($this->flattenTags($tags) as $tag) {
+      apc_delete($this->tagsPrefix . $tag);
+      unset(self::$tagCache[$tag]);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function invalidateTags(array $tags) {
+    foreach ($this->flattenTags($tags) as $tag) {
+      unset(self::$tagCache[$tag]);
+      apc_inc($this->tagsPrefix . $tag, 1, $success);
+      if (!$success) {
+        apc_store($this->tagsPrefix . $tag, 1);
+      }
+    }
+  }
+
+  /**
+   * Compares two checksums of tags. Used to determine whether to serve a cached
+   * item or treat it as invalidated.
+   *
+   * @param integer $checksum
+   *   The initial checksum to compare against.
+   * @param array $tags
+   *   An array of tags to calculate a checksum for.
+   *
+   * @return boolean
+   *   TRUE if the checksums match, FALSE otherwise.
+   */
+  protected function validTags($checksum, array $tags) {
+    return $checksum == $this->checksumTags($tags);
+  }
+
+  /**
+   * Flattens a tags array into a numeric array suitable for string storage.
+   *
+   * @param array $tags
+   *   Associative array of tags to flatten.
+   *
+   * @return array
+   *   Numeric array of flattened tag identifiers.
+   */
+  protected function flattenTags(array $tags) {
+    if (isset($tags[0])) {
+      return $tags;
+    }
+
+    $flat_tags = array();
+    foreach ($tags as $namespace => $values) {
+      if (is_array($values)) {
+        foreach ($values as $value) {
+          $flat_tags[] = "$namespace:$value";
+        }
+      }
+      else {
+        $flat_tags[] = "$namespace:$values";
+      }
+    }
+    return $flat_tags;
+  }
+
+  /**
+   * Returns the sum total of validations for a given set of tags.
+   *
+   * @param array $tags
+   *   Associative array of tags.
+   *
+   * @return integer
+   *   Sum of all invalidations.
+   */
+  protected function checksumTags($tags) {
+    $checksum = 0;
+    $query_tags = array();
+
+    foreach ($this->flattenTags($tags) as $tag) {
+      if (isset(self::$tagCache[$tag])) {
+        $checksum += self::$tagCache[$tag];
+      }
+      else {
+        $query_tags[] = $this->tagsPrefix . $tag;
+      }
+   }
+    if ($query_tags) {
+      $result = apc_fetch($query_tags);
+      self::$tagCache = array_merge(self::$tagCache, $result);
+      $checksum += array_sum($result);
+    }
+    return $checksum;
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/APCBackendUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Cache/APCBackendUnitTest.php
new file mode 100644
index 0000000..0c2b085
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Tests/Cache/APCBackendUnitTest.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\system\Tests\Cache\APCBackendUnitTest.
+ */
+
+namespace Drupal\system\Tests\Cache;
+
+use Drupal\Core\Cache\APCBackend;
+
+/**
+ * Tests the APC cache backend.
+ *
+ * @todo APC still contains the following variables after each tearDown() of a
+ *   test method:
+ *   - cache::theme_registry:build:modules
+ *   - cache::theme_registry:stark
+ *   - tags::*
+ *   The first two are caused by CacheArray::__destruct() being invoked *after*
+ *   TestBase::tearDown() reset static caches.
+ *   Cache tag invalidation counters, however, are not flushed by design. It
+ *   might be possible to flush them in a tearDown() override method in this
+ *   class though.
+ */
+class APCBackendUnitTest extends GenericCacheBackendUnitTestBase {
+
+  public static function getInfo() {
+    return array(
+      'name' => 'APC cache backend',
+      'description' => 'Tests the APC cache backend.',
+      'group' => 'Cache',
+    );
+  }
+
+  protected function checkRequirements() {
+    $requirements = parent::checkRequirements();
+    if (!extension_loaded('apc')) {
+      $requirements[] = 'APC extension not found.';
+    }
+    else {
+      if (version_compare(phpversion('apc'), '3.1.1', '<')) {
+        $requirements[] = 'APC extension must be newer than 3.1.1 for APCIterator support.';
+      }
+      if (drupal_is_cli() && !ini_get('apc.enable_cli')) {
+        $requirements[] = 'apc.enable_cli must be enabled to run this test.';
+      }
+    }
+    return $requirements;
+  }
+
+  protected function createCacheBackend($bin) {
+    return new APCBackend($bin);
+  }
+}
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
index 49b1a44..fda80fb 100755
--- a/core/scripts/run-tests.sh
+++ b/core/scripts/run-tests.sh
@@ -17,14 +17,13 @@
 }
 
 if ($args['execute-test']) {
-  // Masquerade as Apache for running tests.
-  simpletest_script_init("Apache");
+  simpletest_script_init();
   simpletest_script_run_one_test($args['test-id'], $args['execute-test']);
   // Sub-process script execution ends here.
 }
 else {
   // Run administrative functions as CLI.
-  simpletest_script_init(NULL);
+  simpletest_script_init();
 }
 
 // Bootstrap to perform initial validation or other operations.
@@ -257,7 +256,7 @@ function simpletest_script_parse_args() {
 /**
  * Initialize script variables and perform general setup requirements.
  */
-function simpletest_script_init($server_software) {
+function simpletest_script_init() {
   global $args, $php;
 
   $host = 'localhost';
@@ -299,7 +298,7 @@ function simpletest_script_init($server_software) {
   $_SERVER['HTTP_HOST'] = $host;
   $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
   $_SERVER['SERVER_ADDR'] = '127.0.0.1';
-  $_SERVER['SERVER_SOFTWARE'] = $server_software;
+  $_SERVER['SERVER_SOFTWARE'] = NULL;
   $_SERVER['SERVER_NAME'] = 'localhost';
   $_SERVER['REQUEST_URI'] = $path .'/';
   $_SERVER['REQUEST_METHOD'] = 'GET';
@@ -550,9 +549,13 @@ function simpletest_script_cleanup($test_id, $test_class, $exitcode) {
   // Retrieve the last database prefix used for testing.
   list($db_prefix, ) = simpletest_last_test_get($test_id);
 
-  // If no database prefix was found, then the test was not set up correctly.
+  // If no database prefix was found, then the test was not set up correctly,
+  // unless the test process exited successfully, in which case the test only
+  // failed a requirements check.
   if (empty($db_prefix)) {
-    echo "\nFATAL $test_class: Found no database prefix for test ID $test_id. (Check whether setUp() is invoked correctly.)";
+    if ($exitcode) {
+      echo "\nFATAL $test_class: Found no database prefix for test ID $test_id. (Check whether setUp() is invoked correctly.)";
+    }
     return;
   }
 
