/**
  * Merges an info file into a package's info file.
  *
  * @param string $package_info
  *   The Yaml encoded package info.
  * @param string $info_file_uri
  *   The info file's URI.
  */
 protected function mergeInfoFile($package_info, $info_file_uri) {
   $package_info = Yaml::decode($package_info);
   $existing_info = \Drupal::service('info_parser')->parse($info_file_uri);
   // Ensure the entire 'features' data is replaced by new data.
   unset($existing_info['features']);
   return Yaml::encode($this->featuresManager->arrayMergeUnique($existing_info, $package_info));
 }
 /**
  * {@inheritdoc}
  */
 public function findAll()
 {
     $all = array();
     $files = $this->findFiles();
     $file_cache = FileCacheFactory::get('yaml_discovery:' . $this->fileCacheKeySuffix);
     // Try to load from the file cache first.
     foreach ($file_cache->getMultiple(array_keys($files)) as $file => $data) {
         $all[$files[$file]][$this->getIdentifier($file, $data)] = $data;
         unset($files[$file]);
     }
     // If there are files left that were not returned from the cache, load and
     // parse them now. This list was flipped above and is keyed by filename.
     if ($files) {
         foreach ($files as $file => $provider) {
             // If a file is empty or its contents are commented out, return an empty
             // array instead of NULL for type consistency.
             try {
                 $data = Yaml::decode(file_get_contents($file)) ?: [];
             } catch (InvalidDataTypeException $e) {
                 throw new DiscoveryException("The {$file} contains invalid YAML", 0, $e);
             }
             $data[static::FILE_KEY] = $file;
             $all[$provider][$this->getIdentifier($file, $data)] = $data;
             $file_cache->set($file, $data);
         }
     }
     return $all;
 }
 /**
  * Tests export of configuration.
  */
 function testExport()
 {
     // Verify the export page with export submit button is available.
     $this->drupalGet('admin/config/development/configuration/full/export');
     $this->assertFieldById('edit-submit', t('Export'));
     // Submit the export form and verify response.
     $this->drupalPostForm('admin/config/development/configuration/full/export', array(), t('Export'));
     $this->assertResponse(200, 'User can access the download callback.');
     // Get the archived binary file provided to user for download.
     $archive_data = $this->drupalGetContent();
     // Temporarily save the archive file.
     $uri = file_unmanaged_save_data($archive_data, 'temporary://config.tar.gz');
     // Extract the archive and verify it's not empty.
     $file_path = file_directory_temp() . '/' . file_uri_target($uri);
     $archiver = new Tar($file_path);
     $archive_contents = $archiver->listContents();
     $this->assert(!empty($archive_contents), 'Downloaded archive file is not empty.');
     // Prepare the list of config files from active storage, see
     // \Drupal\config\Controller\ConfigController::downloadExport().
     $storage_active = $this->container->get('config.storage');
     $config_files = array();
     foreach ($storage_active->listAll() as $config_name) {
         $config_files[] = $config_name . '.yml';
     }
     // Assert that the downloaded archive file contents are the same as the test
     // site active store.
     $this->assertIdentical($archive_contents, $config_files);
     // Ensure the test configuration override is in effect but was not exported.
     $this->assertIdentical(\Drupal::config('system.maintenance')->get('message'), 'Foo');
     $archiver->extract(file_directory_temp(), array('system.maintenance.yml'));
     $file_contents = file_get_contents(file_directory_temp() . '/' . 'system.maintenance.yml');
     $exported = Yaml::decode($file_contents);
     $this->assertNotIdentical($exported['message'], 'Foo');
 }
 /**
  * Merges an info file into a package's info file.
  *
  * @param string $package_info
  *   The Yaml encoded package info.
  * @param string $info_file_uri
  *   The info file's URI.
  */
 protected function mergeInfoFile($package_info, $info_file_uri)
 {
     $package_info = Yaml::decode($package_info);
     /** @var \Drupal\Core\Extension\InfoParserInterface $existing_info */
     $existing_info = \Drupal::service('info_parser')->parse($info_file_uri);
     return Yaml::encode($this->featuresManager->mergeInfoArray($existing_info, $package_info));
 }
 public function execute()
 {
     $file = $this->getUnaliasedPath($this->configuration['in']);
     $data = file_exists($file) ? YAML::decode(file_get_contents($file)) : [];
     $keys = explode('/', $this->configuration['key']);
     NestedArray::setValue($data, $keys, $this->configuration['value']);
     file_put_contents($file, YAML::encode($data));
 }
 /**
  * {@inheritdoc}
  */
 public function findAll()
 {
     $all = array();
     foreach ($this->findFiles() as $provider => $file) {
         $all[$provider] = Yaml::decode(file_get_contents($file));
     }
     return $all;
 }
Example #7
0
 /**
  * {@inheritdoc}
  */
 public function validateForm(array &$form, FormStateInterface $form_state)
 {
     $config_text = $form_state->getValue('config') ?: 'attributes:';
     try {
         $form_state->set('config', Yaml::decode($config_text));
     } catch (InvalidDataTypeException $e) {
         $form_state->setErrorByName('config', $e->getMessage());
     }
     parent::validateForm($form, $form_state);
 }
Example #8
0
 /**
  * {@inheritdoc}
  */
 public function getDerivativeDefinitions($base_plugin_definition)
 {
     $sweets_list = drupal_get_path('module', 'breakfast') . '/sweets.yml';
     $sweets = Yaml::decode(file_get_contents($sweets_list));
     foreach ($sweets as $key => $sweet) {
         $this->derivatives[$key] = $base_plugin_definition;
         $this->derivatives[$key] += array('label' => $sweet['label'], 'image' => $sweet['image'], 'ingredients' => $sweet['ingredients']);
     }
     return $this->derivatives;
 }
 /**
  * Tests that serialization of server entities doesn't lead to data loss.
  */
 public function testServerSerialization()
 {
     // As our test server, just use the one from the DB Defaults module.
     $path = __DIR__ . '/../../../search_api_db/search_api_db_defaults/config/optional/search_api.server.default_server.yml';
     $values = Yaml::decode(file_get_contents($path));
     $server = new Server($values, 'search_api_server');
     $serialized = unserialize(serialize($server));
     $this->assertNotEmpty($serialized);
     $this->assertEquals($server, $serialized);
 }
 /**
  * Tests that serialization of index entities doesn't lead to data loss.
  */
 public function testIndexSerialization()
 {
     // As our test index, just use the one from the DB Defaults module.
     $path = __DIR__ . '/../../../search_api_db/search_api_db_defaults/config/optional/search_api.index.default_index.yml';
     $index_values = Yaml::decode(file_get_contents($path));
     $index = new Index($index_values, 'search_api_index');
     /** @var \Drupal\search_api\IndexInterface $serialized */
     $serialized = unserialize(serialize($index));
     $this->assertNotEmpty($serialized);
     $this->assertEquals($index, $serialized);
 }
Example #11
0
 protected static function yamlDecode($file, $aliases = [])
 {
     $code = "alias:\n";
     $aliases['path'] = dirname($file);
     $yaml = new Dumper();
     $yaml->setIndentation(2);
     foreach ($aliases as $key => $value) {
         $code .= '  - &' . $key . "\n" . $yaml->dump($value, PHP_INT_MAX, 4, TRUE, FALSE) . "\n";
     }
     return Yaml::decode($code . file_get_contents($file));
 }
 /**
  * {@inheritdoc}
  */
 public function submitForm(array &$form, FormStateInterface $form_state)
 {
     $config = $this->config('jquery_ui_filter.settings');
     $data = $form_state->getValue('jquery_ui_filter');
     foreach (jQueryUiFilter::$widgets as $name => $widget) {
         $data[$name]['options'] = (Yaml::decode($data[$name]['options']) ?: []) + $widget['options'];
     }
     $config->setData($data);
     $config->save();
     parent::submitForm($form, $form_state);
 }
Example #13
0
 /**
  * @param $group    String
  */
 private function getBreakpointByName($group)
 {
     $breakpointsManager = $this->getDrupalService('breakpoint.manager');
     $typeExtension = implode(',', array_values($breakpointsManager->getGroupProviders($group)));
     if ($typeExtension == 'theme') {
         $projectPath = drupal_get_path('theme', $group);
     }
     if ($typeExtension == 'module') {
         $projectPath = drupal_get_path('module', $group);
     }
     return Yaml::decode(file_get_contents($projectPath . '/' . $group . '.breakpoints.yml'));
 }
 /**
  * Test ConfigDevelAutoExportSubscriber::writeBackConfig().
  */
 public function testWriteBackConfig()
 {
     $config_data = array('id' => $this->randomMachineName(), 'langcode' => 'en', 'uuid' => '836769f4-6791-402d-9046-cc06e20be87f');
     $config = $this->getMockBuilder('\\Drupal\\Core\\Config\\Config')->disableOriginalConstructor()->getMock();
     $config->expects($this->any())->method('getName')->will($this->returnValue($this->randomMachineName()));
     $config->expects($this->any())->method('get')->will($this->returnValue($config_data));
     $file_names = array(vfsStream::url('public://' . $this->randomMachineName() . '.yml'), vfsStream::url('public://' . $this->randomMachineName() . '.yml'));
     $configDevelSubscriber = new ConfigDevelAutoExportSubscriber($this->configFactory, $this->configManager);
     $configDevelSubscriber->writeBackConfig($config, $file_names);
     $data = $config_data;
     unset($data['uuid']);
     foreach ($file_names as $file_name) {
         $this->assertEquals($data, Yaml::decode(file_get_contents($file_name)));
     }
 }
 /**
  * {@inheritdoc}
  */
 public function getAllTemplates()
 {
     $templates = [];
     foreach ($this->moduleHandler->getModuleDirectories() as $directory) {
         $full_directory = $directory . '/' . $this->directory;
         if (file_exists($full_directory)) {
             $files = scandir($full_directory);
             foreach ($files as $file) {
                 if ($file[0] !== '.' && fnmatch('*.yml', $file)) {
                     $templates[basename($file, '.yml')] = Yaml::decode(file_get_contents("{$full_directory}/{$file}"));
                 }
             }
         }
     }
     return $templates;
 }
 /**
  * {@inheritdoc}
  */
 public function load(ViewsDuplicateBuilderPluginInterface $builder)
 {
     $templates =& drupal_static(__FUNCTION__, array());
     $template_id = $builder->getViewTemplateId();
     if (!isset($templates[$template_id])) {
         $dir = drupal_get_path('module', $builder->getDefinitionValue('provider')) . '/views_templates';
         if (is_dir($dir)) {
             $file_path = $dir . '/' . $builder->getViewTemplateId() . '.yml';
             if (is_file($file_path)) {
                 $templates[$template_id] = Yaml::decode(file_get_contents($file_path));
             } else {
                 throw new FileNotFoundException($file_path);
             }
         }
     }
     return $templates[$template_id];
 }
Example #17
0
 /**
  * {@inheritdoc}
  */
 public function validateForm(array &$form, FormStateInterface $form_state)
 {
     $value = $form_state->getValue('new');
     // try to parse the new provided value
     try {
         $parsed_value = Yaml::decode($value);
         // Config::setData needs array for the new configuration and
         // a simple string is valid YAML for any reason.
         if (is_array($parsed_value)) {
             $form_state->setValue('parsed_value', $parsed_value);
         } else {
             $form_state->setErrorByName('new', $this->t('Invalid input'));
         }
     } catch (InvalidDataTypeException $e) {
         $form_state->setErrorByName('new', $this->t('Invalid input: %error', array('%error' => $e->getMessage())));
     }
 }
 /**
  * Test the import subscriber.
  */
 public function testSubscribers()
 {
     // Without this the config exporter breaks.
     \Drupal::service('config.installer')->installDefaultConfig('module', 'config_devel');
     $filename = vfsStream::url('public://' . static::CONFIGNAME . '.yml');
     drupal_mkdir(vfsStream::url('public://exported'));
     $exported_filename = vfsStream::url('public://exported/' . static::CONFIGNAME . '.yml');
     \Drupal::configFactory()->getEditable('config_devel.settings')->set('auto_import', array(array('filename' => $filename, 'hash' => '')))->set('auto_export', array($exported_filename))->save();
     $this->storage = \Drupal::service('config.storage');
     $this->assertFalse($this->storage->exists(static::CONFIGNAME));
     $subscriber = \Drupal::service('config_devel.auto_import_subscriber');
     for ($i = 2; $i; $i--) {
         $data['label'] = $this->randomString();
         file_put_contents($filename, Yaml::encode($data));
         // The import fires an export too.
         $subscriber->autoImportConfig();
         $this->doAssert($data, Yaml::decode(file_get_contents($exported_filename)));
     }
 }
Example #19
0
 /**
  * Tests export of configuration.
  */
 function testExport()
 {
     // Verify the export page with export submit button is available.
     $this->drupalGet('admin/config/development/configuration/full/export');
     $this->assertFieldById('edit-submit', t('Export'));
     // Submit the export form and verify response.
     $this->drupalPostForm('admin/config/development/configuration/full/export', array(), t('Export'));
     $this->assertResponse(200, 'User can access the download callback.');
     // Test if header contains file name with hostname and timestamp.
     $request = \Drupal::request();
     $hostname = str_replace('.', '-', $request->getHttpHost());
     $header_content_disposition = $this->drupalGetHeader('content-disposition');
     $header_match = (bool) preg_match('/attachment; filename="config-' . preg_quote($hostname) . '-\\d{4}-\\d{2}-\\d{2}-\\d{2}-\\d{2}\\.tar\\.gz"/', $header_content_disposition);
     $this->assertTrue($header_match, "Header with filename matches the expected format.");
     // Get the archived binary file provided to user for download.
     $archive_data = $this->getRawContent();
     // Temporarily save the archive file.
     $uri = file_unmanaged_save_data($archive_data, 'temporary://config.tar.gz');
     // Extract the archive and verify it's not empty.
     $file_path = file_directory_temp() . '/' . file_uri_target($uri);
     $archiver = new Tar($file_path);
     $archive_contents = $archiver->listContents();
     $this->assert(!empty($archive_contents), 'Downloaded archive file is not empty.');
     // Prepare the list of config files from active storage, see
     // \Drupal\config\Controller\ConfigController::downloadExport().
     $storage_active = $this->container->get('config.storage');
     $config_files = array();
     foreach ($storage_active->listAll() as $config_name) {
         $config_files[] = $config_name . '.yml';
     }
     // Assert that the downloaded archive file contents are the same as the test
     // site active store.
     $this->assertIdentical($archive_contents, $config_files);
     // Ensure the test configuration override is in effect but was not exported.
     $this->assertIdentical(\Drupal::config('system.maintenance')->get('message'), 'Foo');
     $archiver->extract(file_directory_temp(), array('system.maintenance.yml'));
     $file_contents = file_get_contents(file_directory_temp() . '/' . 'system.maintenance.yml');
     $exported = Yaml::decode($file_contents);
     $this->assertNotIdentical($exported['message'], 'Foo');
     // Check the single export form doesn't have "form-required" elements.
     $this->drupalGet('admin/config/development/configuration/single/export');
     $this->assertNoRaw('js-form-required form-required', 'No form required fields are found.');
 }
Example #20
0
 /**
  * @covers ::decode
  */
 public function testDecode()
 {
     // Test that files without line break endings are properly interpreted.
     $yaml = 'foo: bar';
     $expected = array('foo' => 'bar');
     $this->assertSame($expected, Yaml::decode($yaml));
     $yaml .= "\n";
     $this->assertSame($expected, Yaml::decode($yaml));
     $yaml .= "\n";
     $this->assertSame($expected, Yaml::decode($yaml));
     $yaml = "{}\n";
     $expected = array();
     $this->assertSame($expected, Yaml::decode($yaml));
     $yaml = '';
     $this->assertNULL(Yaml::decode($yaml));
     $yaml .= "\n";
     $this->assertNULL(Yaml::decode($yaml));
     $yaml .= "\n";
     $this->assertNULL(Yaml::decode($yaml));
 }
 /**
  * Tests post-update responsive_image_post_update_dependency().
  *
  * @see responsive_image_post_update_dependency()
  */
 public function testPostUpdateDependency()
 {
     // Installing the 'wide' responsive image style.
     $wide_image_style = Yaml::decode(file_get_contents(__DIR__ . '/../../../../../profiles/standard/config/optional/responsive_image.styles.wide.yml'));
     $this->config('responsive_image.styles.wide')->setData($wide_image_style)->save(TRUE);
     // Change 'field_image' formatter to a responsive image formatter.
     $options = ['type' => 'responsive_image', 'label' => 'hidden', 'settings' => ['responsive_image_style' => 'wide', 'image_link' => ''], 'third_party_settings' => []];
     $display = $this->config('core.entity_view_display.node.article.default');
     $display->set('content.field_image', $options)->save(TRUE);
     // Check that there's no dependency to 'responsive_image.styles.wide'.
     $dependencies = $display->get('dependencies.config') ?: [];
     $this->assertFalse(in_array('responsive_image.styles.wide', $dependencies));
     // Run updates.
     $this->runUpdates();
     /** @var \Drupal\Core\Entity\Display\EntityViewDisplayInterface $view_display */
     $view_display = EntityViewDisplay::load('node.article.default');
     $dependencies = $view_display->getDependencies() + ['config' => []];
     // Check that post-update added a 'responsive_image.styles.wide' dependency.
     $this->assertTrue(in_array('responsive_image.styles.wide', $dependencies['config']));
 }
Example #22
0
 /**
  * {@inheritdoc}
  */
 public function parse($filename)
 {
     if (!file_exists($filename)) {
         $parsed_info = array();
     } else {
         try {
             $parsed_info = Yaml::decode(file_get_contents($filename));
         } catch (InvalidDataTypeException $e) {
             throw new InfoParserException("Unable to parse {$filename} " . $e->getMessage());
         }
         $missing_keys = array_diff($this->getRequiredKeys(), array_keys($parsed_info));
         if (!empty($missing_keys)) {
             throw new InfoParserException('Missing required keys (' . implode(', ', $missing_keys) . ') in ' . $filename);
         }
         if (isset($parsed_info['version']) && $parsed_info['version'] === 'VERSION') {
             $parsed_info['version'] = \Drupal::VERSION;
         }
     }
     return $parsed_info;
 }
 /**
  * {@inheritdoc}
  */
 public function parse($filename)
 {
     if (!file_exists($filename)) {
         $parsed_info = array();
     } else {
         try {
             $parsed_info = Yaml::decode(file_get_contents($filename));
         } catch (InvalidDataTypeException $e) {
             $message = SafeMarkup::format("Unable to parse !file: !error", array('!file' => $filename, '!error' => $e->getMessage()));
             throw new InfoParserException($message);
         }
         $missing_keys = array_diff($this->getRequiredKeys(), array_keys($parsed_info));
         if (!empty($missing_keys)) {
             $message = SafeMarkup::format('Missing required keys (!missing_keys) in !file.', array('!missing_keys' => implode(', ', $missing_keys), '!file' => $filename));
             throw new InfoParserException($message);
         }
         if (isset($parsed_info['version']) && $parsed_info['version'] === 'VERSION') {
             $parsed_info['version'] = \Drupal::VERSION;
         }
     }
     return $parsed_info;
 }
 /**
  * {@inheritdoc}
  */
 public function findAll()
 {
     $all = array();
     $files = $this->findFiles();
     $provider_by_files = array_flip($files);
     $file_cache = FileCacheFactory::get('yaml_discovery:' . $this->name);
     // Try to load from the file cache first.
     foreach ($file_cache->getMultiple($files) as $file => $data) {
         $all[$provider_by_files[$file]] = $data;
         unset($provider_by_files[$file]);
     }
     // If there are files left that were not returned from the cache, load and
     // parse them now. This list was flipped above and is keyed by filename.
     if ($provider_by_files) {
         foreach ($provider_by_files as $file => $provider) {
             // If a file is empty or its contents are commented out, return an empty
             // array instead of NULL for type consistency.
             $all[$provider] = Yaml::decode(file_get_contents($file)) ?: [];
             $file_cache->set($file, $all[$provider]);
         }
     }
     return $all;
 }
 protected function setUp()
 {
     // Copy the testing_config_overrides install profile so we can change the
     // configuration to include a dependency that can not be met. File API
     // functions are not available yet.
     $dest = $this->siteDirectory . '/profiles/testing_config_overrides';
     mkdir($dest, 0777, TRUE);
     $source = DRUPAL_ROOT . '/core/profiles/testing_config_overrides';
     $iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($source, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
     foreach ($iterator as $item) {
         if ($item->isDir()) {
             mkdir($dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
         } else {
             copy($item, $dest . DIRECTORY_SEPARATOR . $iterator->getSubPathName());
         }
     }
     // Add a dependency that can not be met because User is installed before
     // Action.
     $config_file = $dest . DIRECTORY_SEPARATOR . InstallStorage::CONFIG_INSTALL_DIRECTORY . DIRECTORY_SEPARATOR . 'system.action.user_block_user_action.yml';
     $action = Yaml::decode(file_get_contents($config_file));
     $action['dependencies']['module'][] = 'action';
     file_put_contents($config_file, Yaml::encode($action));
     parent::setUp();
 }
 /**
  * {@inheritdoc}
  */
 public function parse($filename)
 {
     if (!isset(static::$parsedInfos[$filename])) {
         if (!file_exists($filename)) {
             static::$parsedInfos[$filename] = array();
         } else {
             try {
                 static::$parsedInfos[$filename] = Yaml::decode(file_get_contents($filename));
             } catch (InvalidDataTypeException $e) {
                 $message = String::format("Unable to parse !file: !error", array('!file' => $filename, '!error' => $e->getMessage()));
                 throw new InfoParserException($message);
             }
             $missing_keys = array_diff($this->getRequiredKeys(), array_keys(static::$parsedInfos[$filename]));
             if (!empty($missing_keys)) {
                 $message = format_plural(count($missing_keys), 'Missing required key (!missing_keys) in !file.', 'Missing required keys (!missing_keys) in !file.', array('!missing_keys' => implode(', ', $missing_keys), '!file' => $filename));
                 throw new InfoParserException($message);
             }
             if (isset(static::$parsedInfos[$filename]['version']) && static::$parsedInfos[$filename]['version'] === 'VERSION') {
                 static::$parsedInfos[$filename]['version'] = \Drupal::VERSION;
             }
         }
     }
     return static::$parsedInfos[$filename];
 }
Example #27
0
 /**
  * Changes parameters in the services.yml file.
  *
  * @param $name
  *   The name of the parameter.
  * @param $value
  *   The value of the parameter.
  */
 protected function setContainerParameter($name, $value)
 {
     $filename = $this->siteDirectory . '/services.yml';
     chmod($filename, 0666);
     $services = Yaml::decode(file_get_contents($filename));
     $services['parameters'][$name] = $value;
     file_put_contents($filename, Yaml::encode($services));
     // Ensure that the cache is deleted for the yaml file loader.
     $file_cache = FileCacheFactory::get('container_yaml_loader');
     $file_cache->delete($filename);
 }
Example #28
0
<?php

/**
 * @file
 * Text fixture.
 */
use Drupal\Component\Serialization\Yaml;
use Drupal\Core\Database\Database;
$connection = Database::getConnection();
$connection->insert('config')->fields(array('collection' => '', 'name' => 'views.view.test_token_view', 'data' => serialize(Yaml::decode(file_get_contents('core/modules/views/tests/modules/views_test_config/test_views/views.view.test_token_view.yml')))))->execute();
Example #29
0
 /**
  * {@inheritdoc}
  */
 public function decode($raw)
 {
     $data = Yaml::decode($raw);
     // A simple string is valid YAML for any reason.
     if (!is_array($data)) {
         return FALSE;
     }
     return $data;
 }
 /**
  * Tests creating a feature via UI and download it.
  */
 public function testCreateFeaturesUI()
 {
     $feature_name = 'test_feature2';
     $admin_user = $this->createUser(['administer site configuration', 'export configuration', 'administer modules']);
     $this->drupalLogin($admin_user);
     $this->drupalPlaceBlock('local_actions_block');
     $this->drupalGet('admin/config/development/features');
     $this->clickLink('Create new feature');
     $this->assertResponse(200);
     $edit = ['name' => 'Test feature', 'machine_name' => $feature_name, 'description' => 'Test description: <strong>giraffe</strong>', 'version' => '8.x-1.0', 'system_simple[sources][selected][system.theme]' => TRUE, 'system_simple[sources][selected][user.settings]' => TRUE];
     $this->drupalPostForm(NULL, $edit, $this->t('Download Archive'));
     $this->assertResponse(200);
     $archive = $this->getRawContent();
     $filename = tempnam($this->tempFilesDirectory, 'feature');
     file_put_contents($filename, $archive);
     $archive = new ArchiveTar($filename);
     $files = $archive->listContent();
     $this->assertEqual(4, count($files));
     $this->assertEqual($feature_name . '/' . $feature_name . '.info.yml', $files[0]['filename']);
     $this->assertEqual($feature_name . '/' . $feature_name . '.features.yml', $files[1]['filename']);
     $this->assertEqual($feature_name . '/config/install/system.theme.yml', $files[2]['filename']);
     $this->assertEqual($feature_name . '/config/install/user.settings.yml', $files[3]['filename']);
     // Ensure that the archive contains the expected values.
     $info_filename = tempnam($this->tempFilesDirectory, 'feature');
     file_put_contents($info_filename, $archive->extractInString($feature_name . '/' . $feature_name . '.info.yml'));
     $features_info_filename = tempnam($this->tempFilesDirectory, 'feature');
     file_put_contents($features_info_filename, $archive->extractInString($feature_name . '/' . $feature_name . '.features.yml'));
     /** @var \Drupal\Core\Extension\InfoParser $info_parser */
     $info_parser = \Drupal::service('info_parser');
     $parsed_info = $info_parser->parse($info_filename);
     $this->assertEqual('Test feature', $parsed_info['name']);
     $parsed_features_info = Yaml::decode(file_get_contents($features_info_filename));
     $this->assertEqual(['required' => ['system.theme', 'user.settings']], $parsed_features_info);
     $archive->extract(\Drupal::service('kernel')->getSitePath() . '/modules');
     $module_path = \Drupal::service('kernel')->getSitePath() . '/modules/' . $feature_name;
     // Ensure that the features listing renders the right content.
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertLink('Test feature');
     $this->assertEqual($feature_name, (string) $tr->children()[2]);
     $description_column = (string) $tr->children()[3]->asXml();
     $this->assertTrue(strpos($description_column, 'system.theme') !== FALSE);
     $this->assertTrue(strpos($description_column, 'user.settings') !== FALSE);
     $this->assertRaw('Test description: <strong>giraffe</strong>');
     $this->assertEqual('Uninstalled', (string) $tr->children()[5]);
     $this->assertEqual('', (string) $tr->children()[6]);
     // Remove one and add new configuration.
     $this->clickLink('Test feature');
     $edit = ['system_simple[included][system.theme]' => FALSE, 'user_role[sources][selected][authenticated]' => TRUE];
     $this->drupalPostForm(NULL, $edit, $this->t('Write'));
     $info_filename = $module_path . '/' . $feature_name . '.info.yml';
     $parsed_info = $info_parser->parse($info_filename);
     $this->assertEqual('Test feature', $parsed_info['name']);
     $features_info_filename = $module_path . '/' . $feature_name . '.features.yml';
     $parsed_features_info = Yaml::decode(file_get_contents($features_info_filename));
     $this->assertEqual(['required' => ['user.settings', 'user.role.authenticated']], $parsed_features_info);
     $this->drupalGet('admin/modules');
     $edit = ['modules[Other][' . $feature_name . '][enable]' => TRUE];
     $this->drupalPostForm(NULL, $edit, $this->t('Install'));
     // Check that the feature is listed as installed.
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertEqual('Installed', (string) $tr->children()[5]);
     // Check that a config change results in a feature marked as changed.
     \Drupal::configFactory()->getEditable('user.settings')->set('anonymous', 'Anonymous giraffe')->save();
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertTrue(strpos($tr->children()[6]->asXml(), 'Changed') !== FALSE);
     // Uninstall module.
     $this->drupalPostForm('admin/modules/uninstall', ['uninstall[' . $feature_name . ']' => TRUE], $this->t('Uninstall'));
     $this->drupalPostForm(NULL, [], $this->t('Uninstall'));
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertTrue(strpos($tr->children()[6]->asXml(), 'Changed') !== FALSE);
     $this->clickLink($this->t('Changed'));
     $this->assertRaw('<td class="diff-context diff-deletedline">anonymous : Anonymous <span class="diffchange">giraffe</span></td>');
     $this->assertRaw('<td class="diff-context diff-addedline">anonymous : Anonymous</td>');
     $this->drupalGet('admin/modules');
     $edit = ['modules[Other][' . $feature_name . '][enable]' => TRUE];
     $this->drupalPostForm(NULL, $edit, $this->t('Install'));
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertEqual('Installed', (string) $tr->children()[5]);
     // Ensure that the changed config got overridden.
     $this->assertEqual('Anonymous', \Drupal::config('user.settings')->get('anonymous'));
     // Change the value, export and ensure that its not shown as changed.
     \Drupal::configFactory()->getEditable('user.settings')->set('anonymous', 'Anonymous giraffe')->save();
     // Ensure that exporting this change will result in an unchanged feature.
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertTrue(strpos($tr->children()[6]->asXml(), 'Changed') !== FALSE);
     $this->clickLink('Test feature');
     $this->drupalPostForm(NULL, [], $this->t('Write'));
     $this->drupalGet('admin/config/development/features');
     $tr = $this->xpath('//table[contains(@class, "features-listing")]/tbody/tr[td[3] = "' . $feature_name . '"]')[0];
     $this->assertEqual('Installed', (string) $tr->children()[5]);
 }