--- /dev/null
+<?php
+
+namespace Drupal\KernelTests\Core\Config;
+
+use Drupal\KernelTests\KernelTestBase;
+
+/**
+ * Calculating the difference between two sets of configuration.
+ *
+ * @group config
+ */
+class ConfigDiffTest extends KernelTestBase {
+
+ /**
+ * Modules to enable.
+ *
+ * @var array
+ */
+ public static $modules = ['config_test', 'system'];
+
+ /**
+ * Tests calculating the difference between two sets of configuration.
+ */
+ public function testDiff() {
+ $active = $this->container->get('config.storage');
+ $sync = $this->container->get('config.storage.sync');
+ $config_name = 'config_test.system';
+ $change_key = 'foo';
+ $remove_key = '404';
+ $add_key = 'biff';
+ $add_data = 'bangpow';
+ $change_data = 'foobar';
+
+ // Install the default config.
+ $this->installConfig(['config_test']);
+ $original_data = \Drupal::config($config_name)->get();
+
+ // Change a configuration value in sync.
+ $sync_data = $original_data;
+ $sync_data[$change_key] = $change_data;
+ $sync_data[$add_key] = $add_data;
+ $sync->write($config_name, $sync_data);
+
+ // Verify that the diff reflects a change.
+ $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
+ $edits = $diff->getEdits();
+ $this->assertYamlEdit($edits, $change_key, 'change',
+ [$change_key . ': ' . $original_data[$change_key]],
+ [$change_key . ': ' . $change_data]);
+
+ // Reset data back to original, and remove a key
+ $sync_data = $original_data;
+ unset($sync_data[$remove_key]);
+ $sync->write($config_name, $sync_data);
+
+ // Verify that the diff reflects a removed key.
+ $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
+ $edits = $diff->getEdits();
+ $this->assertYamlEdit($edits, $change_key, 'copy');
+ $this->assertYamlEdit($edits, $remove_key, 'delete',
+ [$remove_key . ': ' . $original_data[$remove_key]],
+ FALSE
+ );
+
+ // Reset data back to original and add a key
+ $sync_data = $original_data;
+ $sync_data[$add_key] = $add_data;
+ $sync->write($config_name, $sync_data);
+
+ // Verify that the diff reflects an added key.
+ $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
+ $edits = $diff->getEdits();
+ $this->assertYamlEdit($edits, $change_key, 'copy');
+ $this->assertYamlEdit($edits, $add_key, 'add', FALSE, [$add_key . ': ' . $add_data]);
+
+ // Test diffing a renamed config entity.
+ $test_entity_id = $this->randomMachineName();
+ $test_entity = entity_create('config_test', [
+ 'id' => $test_entity_id,
+ 'label' => $this->randomMachineName(),
+ ]);
+ $test_entity->save();
+ $data = $active->read('config_test.dynamic.' . $test_entity_id);
+ $sync->write('config_test.dynamic.' . $test_entity_id, $data);
+ $config_name = 'config_test.dynamic.' . $test_entity_id;
+ $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name, $config_name);
+ // Prove the fields match.
+ $edits = $diff->getEdits();
+ $this->assertEqual($edits[0]->type, 'copy', 'The first item in the diff is a copy.');
+ $this->assertEqual(count($edits), 1, 'There is one item in the diff');
+
+ // Rename the entity.
+ $new_test_entity_id = $this->randomMachineName();
+ $test_entity->set('id', $new_test_entity_id);
+ $test_entity->save();
+
+ $diff = \Drupal::service('config.manager')->diff($active, $sync, 'config_test.dynamic.' . $new_test_entity_id, $config_name);
+ $edits = $diff->getEdits();
+ $this->assertYamlEdit($edits, 'uuid', 'copy');
+ $this->assertYamlEdit($edits, 'id', 'change',
+ ['id: ' . $new_test_entity_id],
+ ['id: ' . $test_entity_id]);
+ $this->assertYamlEdit($edits, 'label', 'copy');
+ $this->assertEqual($edits[2]->type, 'copy', 'The third item in the diff is a copy.');
+ $this->assertEqual(count($edits), 3, 'There are three items in the diff.');
+ }
+
+ /**
+ * Tests calculating the difference between two sets of config collections.
+ */
+ public function testCollectionDiff() {
+ /** @var \Drupal\Core\Config\StorageInterface $active */
+ $active = $this->container->get('config.storage');
+ /** @var \Drupal\Core\Config\StorageInterface $sync */
+ $sync = $this->container->get('config.storage.sync');
+ $active_test_collection = $active->createCollection('test');
+ $sync_test_collection = $sync->createCollection('test');
+
+ $config_name = 'config_test.test';
+ $data = ['foo' => 'bar'];
+
+ $active->write($config_name, $data);
+ $sync->write($config_name, $data);
+ $active_test_collection->write($config_name, $data);
+ $sync_test_collection->write($config_name, ['foo' => 'baz']);
+
+ // Test the fields match in the default collection diff.
+ $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name);
+ $edits = $diff->getEdits();
+ $this->assertEqual($edits[0]->type, 'copy', 'The first item in the diff is a copy.');
+ $this->assertEqual(count($edits), 1, 'There is one item in the diff');
+
+ // Test that the differences are detected when diffing the collection.
+ $diff = \Drupal::service('config.manager')->diff($active, $sync, $config_name, NULL, 'test');
+ $edits = $diff->getEdits();
+ $this->assertYamlEdit($edits, 'foo', 'change', ['foo: bar'], ['foo: baz']);
+ }
+
+ /**
+ * Helper method to test that an edit is found in a diff'd YAML file.
+ *
+ * @param array $edits
+ * A list of edits.
+ * @param string $field
+ * The field key that is being asserted.
+ * @param string $type
+ * The type of edit that is being asserted.
+ * @param mixed $orig
+ * (optional) The original value of of the edit. If not supplied, assertion
+ * is skipped.
+ * @param mixed $closing
+ * (optional) The closing value of of the edit. If not supplied, assertion
+ * is skipped.
+ */
+ protected function assertYamlEdit(array $edits, $field, $type, $orig = NULL, $closing = NULL) {
+ $match = FALSE;
+ foreach ($edits as $edit) {
+ // Choose which section to search for the field.
+ $haystack = $type == 'add' ? $edit->closing : $edit->orig;
+ // Look through each line and try and find the key.
+ if (is_array($haystack)) {
+ foreach ($haystack as $item) {
+ if (strpos($item, $field . ':') === 0) {
+ $match = TRUE;
+ // Assert that the edit is of the type specified.
+ $this->assertEqual($edit->type, $type, "The $field item in the diff is a $type");
+ // If an original value was given, assert that it matches.
+ if (isset($orig)) {
+ $this->assertIdentical($edit->orig, $orig, "The original value for key '$field' is correct.");
+ }
+ // If a closing value was given, assert that it matches.
+ if (isset($closing)) {
+ $this->assertIdentical($edit->closing, $closing, "The closing value for key '$field' is correct.");
+ }
+ // Break out of the search entirely.
+ break 2;
+ }
+ }
+ }
+ }
+
+ // If we didn't match anything, fail.
+ if (!$match) {
+ $this->fail("$field edit was not matched");
+ }
+ }
+
+}