diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
index f2803c2399474b3419f528e6653e0f01b0e24d44..98d5182bbadb2a1cb5f4e5944fa5d61f3d0fcd58 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
@@ -5,79 +5,95 @@
  * See COPYING.txt for license details.
  */
 
-// @codingStandardsIgnoreFile
-
 namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute;
 
+use Magento\Backend\App\Action\Context;
+use Magento\Backend\Model\View\Result\Redirect;
+use Magento\Catalog\Controller\Adminhtml\Product\Attribute;
+use Magento\Catalog\Model\Product\AttributeSet\BuildFactory;
+use Magento\Catalog\Helper\Product;
+use Magento\Catalog\Api\Data\ProductAttributeInterface;
+use Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory;
+use Magento\Eav\Model\Entity\Attribute\Set;
+use Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\Validator;
+use Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\ValidatorFactory;
+use Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory;
+use Magento\Framework\Cache\FrontendInterface;
 use Magento\Framework\Controller\ResultFactory;
+use Magento\Framework\Controller\Result\Json;
 use Magento\Framework\Exception\AlreadyExistsException;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Filter\FilterManager;
+use Magento\Framework\Registry;
+use Magento\Framework\View\LayoutFactory;
+use Magento\Framework\View\Result\PageFactory;
 
 /**
  * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
-class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
+class Save extends Attribute
 {
     /**
-     * @var \Magento\Catalog\Model\Product\AttributeSet\BuildFactory
+     * @var BuildFactory
      */
     protected $buildFactory;
 
     /**
-     * @var \Magento\Framework\Filter\FilterManager
+     * @var FilterManager
      */
     protected $filterManager;
 
     /**
-     * @var \Magento\Catalog\Helper\Product
+     * @var Product
      */
     protected $productHelper;
 
     /**
-     * @var \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory
+     * @var AttributeFactory
      */
     protected $attributeFactory;
 
     /**
-     * @var \Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\ValidatorFactory
+     * @var ValidatorFactory
      */
     protected $validatorFactory;
 
     /**
-     * @var \Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory
+     * @var CollectionFactory
      */
     protected $groupCollectionFactory;
 
     /**
-     * @var \Magento\Framework\View\LayoutFactory
+     * @var LayoutFactory
      */
     private $layoutFactory;
 
     /**
-     * @param \Magento\Backend\App\Action\Context $context
-     * @param \Magento\Framework\Cache\FrontendInterface $attributeLabelCache
-     * @param \Magento\Framework\Registry $coreRegistry
-     * @param \Magento\Catalog\Model\Product\AttributeSet\BuildFactory $buildFactory
-     * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
-     * @param \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory $attributeFactory
-     * @param \Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\ValidatorFactory $validatorFactory
-     * @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory $groupCollectionFactory
-     * @param \Magento\Framework\Filter\FilterManager $filterManager
-     * @param \Magento\Catalog\Helper\Product $productHelper
-     * @param \Magento\Framework\View\LayoutFactory $layoutFactory
+     * @param Context $context
+     * @param FrontendInterface $attributeLabelCache
+     * @param Registry $coreRegistry
+     * @param BuildFactory $buildFactory
+     * @param PageFactory $resultPageFactory
+     * @param AttributeFactory $attributeFactory
+     * @param ValidatorFactory $validatorFactory
+     * @param CollectionFactory $groupCollectionFactory
+     * @param FilterManager $filterManager
+     * @param Product $productHelper
+     * @param LayoutFactory $layoutFactory
      * @SuppressWarnings(PHPMD.ExcessiveParameterList)
      */
     public function __construct(
-        \Magento\Backend\App\Action\Context $context,
-        \Magento\Framework\Cache\FrontendInterface $attributeLabelCache,
-        \Magento\Framework\Registry $coreRegistry,
-        \Magento\Framework\View\Result\PageFactory $resultPageFactory,
-        \Magento\Catalog\Model\Product\AttributeSet\BuildFactory $buildFactory,
-        \Magento\Catalog\Model\ResourceModel\Eav\AttributeFactory $attributeFactory,
-        \Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\ValidatorFactory $validatorFactory,
-        \Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory $groupCollectionFactory,
-        \Magento\Framework\Filter\FilterManager $filterManager,
-        \Magento\Catalog\Helper\Product $productHelper,
-        \Magento\Framework\View\LayoutFactory $layoutFactory
+        Context $context,
+        FrontendInterface $attributeLabelCache,
+        Registry $coreRegistry,
+        PageFactory $resultPageFactory,
+        BuildFactory $buildFactory,
+        AttributeFactory $attributeFactory,
+        ValidatorFactory $validatorFactory,
+        CollectionFactory $groupCollectionFactory,
+        FilterManager $filterManager,
+        Product $productHelper,
+        LayoutFactory $layoutFactory
     ) {
         parent::__construct($context, $attributeLabelCache, $coreRegistry, $resultPageFactory);
         $this->buildFactory = $buildFactory;
@@ -90,7 +106,7 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
     }
 
     /**
-     * @return \Magento\Backend\Model\View\Result\Redirect
+     * @return Redirect
      * @SuppressWarnings(PHPMD.CyclomaticComplexity)
      * @SuppressWarnings(PHPMD.NPathComplexity)
      * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
@@ -107,36 +123,51 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
                 $name = trim($name);
 
                 try {
-                    /** @var $attributeSet \Magento\Eav\Model\Entity\Attribute\Set */
+                    /** @var $attributeSet Set */
                     $attributeSet = $this->buildFactory->create()
                         ->setEntityTypeId($this->_entityTypeId)
                         ->setSkeletonId($setId)
                         ->setName($name)
                         ->getAttributeSet();
                 } catch (AlreadyExistsException $alreadyExists) {
-                    $this->messageManager->addError(__('An attribute set named \'%1\' already exists.', $name));
+                    $this->messageManager->addErrorMessage(__('An attribute set named \'%1\' already exists.', $name));
                     $this->_session->setAttributeData($data);
+
                     return $this->returnResult('catalog/*/edit', ['_current' => true], ['error' => true]);
-                } catch (\Magento\Framework\Exception\LocalizedException $e) {
-                    $this->messageManager->addError($e->getMessage());
+                } catch (LocalizedException $e) {
+                    $this->messageManager->addErrorMessage($e->getMessage());
                 } catch (\Exception $e) {
-                    $this->messageManager->addException($e, __('Something went wrong while saving the attribute.'));
+                    $this->messageManager->addExceptionMessage(
+                        $e,
+                        __('Something went wrong while saving the attribute.')
+                    );
                 }
             }
 
             $attributeId = $this->getRequest()->getParam('attribute_id');
-            $attributeCode = $this->getRequest()->getParam('attribute_code')
-                ?: $this->generateCode($this->getRequest()->getParam('frontend_label')[0]);
+
+            /** @var $model ProductAttributeInterface */
+            $model = $this->attributeFactory->create();
+            if ($attributeId) {
+                $model->load($attributeId);
+            }
+            $attributeCode = $model && $model->getId()
+                ? $model->getAttributeCode()
+                : $this->getRequest()->getParam('attribute_code');
+            $attributeCode = $attributeCode ?: $this->generateCode($this->getRequest()->getParam('frontend_label')[0]);
             if (strlen($attributeCode) > 0) {
-                $validatorAttrCode = new \Zend_Validate_Regex(['pattern' => '/^[a-z\x{600}-\x{6FF}][a-z\x{600}-\x{6FF}_0-9]{0,30}$/u']);
+                $validatorAttrCode = new \Zend_Validate_Regex(
+                    ['pattern' => '/^[a-z\x{600}-\x{6FF}][a-z\x{600}-\x{6FF}_0-9]{0,30}$/u']
+                );
                 if (!$validatorAttrCode->isValid($attributeCode)) {
-                    $this->messageManager->addError(
+                    $this->messageManager->addErrorMessage(
                         __(
                             'Attribute code "%1" is invalid. Please use only letters (a-z), ' .
                             'numbers (0-9) or underscore(_) in this field, first character should be a letter.',
                             $attributeCode
                         )
                     );
+
                     return $this->returnResult(
                         'catalog/*/edit',
                         ['attribute_id' => $attributeId, '_current' => true],
@@ -148,12 +179,13 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
 
             //validate frontend_input
             if (isset($data['frontend_input'])) {
-                /** @var $inputType \Magento\Eav\Model\Adminhtml\System\Config\Source\Inputtype\Validator */
+                /** @var $inputType Validator */
                 $inputType = $this->validatorFactory->create();
                 if (!$inputType->isValid($data['frontend_input'])) {
                     foreach ($inputType->getMessages() as $message) {
-                        $this->messageManager->addError($message);
+                        $this->messageManager->addErrorMessage($message);
                     }
+
                     return $this->returnResult(
                         'catalog/*/edit',
                         ['attribute_id' => $attributeId, '_current' => true],
@@ -162,19 +194,17 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
                 }
             }
 
-            /* @var $model \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
-            $model = $this->attributeFactory->create();
-
             if ($attributeId) {
-                $model->load($attributeId);
                 if (!$model->getId()) {
-                    $this->messageManager->addError(__('This attribute no longer exists.'));
+                    $this->messageManager->addErrorMessage(__('This attribute no longer exists.'));
+
                     return $this->returnResult('catalog/*/', [], ['error' => true]);
                 }
                 // entity type check
                 if ($model->getEntityTypeId() != $this->_entityTypeId) {
-                    $this->messageManager->addError(__('We can\'t update the attribute.'));
+                    $this->messageManager->addErrorMessage(__('We can\'t update the attribute.'));
                     $this->_session->setAttributeData($data);
+
                     return $this->returnResult('catalog/*/', [], ['error' => true]);
                 }
 
@@ -195,7 +225,7 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
 
             $data += ['is_filterable' => 0, 'is_filterable_in_search' => 0];
 
-            if (is_null($model->getIsUserDefined()) || $model->getIsUserDefined() != 0) {
+            if ($model->getIsUserDefined() === null || $model->getIsUserDefined() != 0) {
                 $data['backend_type'] = $model->getBackendTypeByInput($data['frontend_input']);
             }
 
@@ -241,7 +271,7 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
 
             try {
                 $model->save();
-                $this->messageManager->addSuccess(__('You saved the product attribute.'));
+                $this->messageManager->addSuccessMessage(__('You saved the product attribute.'));
 
                 $this->_attributeLabelCache->clean();
                 $this->_session->setAttributeData(false);
@@ -252,9 +282,10 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
                         '_current' => true,
                         'product_tab' => $this->getRequest()->getParam('product_tab'),
                     ];
-                    if (!is_null($attributeSet)) {
+                    if ($attributeSet !== null) {
                         $requestParams['new_attribute_set_id'] = $attributeSet->getId();
                     }
+
                     return $this->returnResult('catalog/product/addAttribute', $requestParams, ['error' => false]);
                 } elseif ($this->getRequest()->getParam('back', false)) {
                     return $this->returnResult(
@@ -263,10 +294,12 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
                         ['error' => false]
                     );
                 }
+
                 return $this->returnResult('catalog/*/', [], ['error' => false]);
             } catch (\Exception $e) {
-                $this->messageManager->addError($e->getMessage());
+                $this->messageManager->addErrorMessage($e->getMessage());
                 $this->_session->setAttributeData($data);
+
                 return $this->returnResult(
                     'catalog/*/edit',
                     ['attribute_id' => $attributeId, '_current' => true],
@@ -274,6 +307,7 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
                 );
             }
         }
+
         return $this->returnResult('catalog/*/', [], ['error' => true]);
     }
 
@@ -281,7 +315,7 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
      * @param string $path
      * @param array $params
      * @param array $response
-     * @return \Magento\Framework\Controller\Result\Json|\Magento\Backend\Model\View\Result\Redirect
+     * @return Json|Redirect
      */
     private function returnResult($path = '', array $params = [], array $response = [])
     {
@@ -291,8 +325,10 @@ class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Attribute
 
             $response['messages'] = [$layout->getMessagesBlock()->getGroupedHtml()];
             $response['params'] = $params;
+
             return $this->resultFactory->create(ResultFactory::TYPE_JSON)->setData($response);
         }
+
         return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath($path, $params);
     }
 
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/general.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/general.phtml
index b1e46776af46526d57fd233d395605ebe220e159..a2b91a5eeb99fb40378989c623111397ca1464c4 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/general.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/general.phtml
@@ -9,7 +9,7 @@
 /** @var $block \Magento\Catalog\Block\Product\View */
 ?>
 
-<meta property="og:type" content="og:product" />
+<meta property="og:type" content="product" />
 <meta property="og:title" content="<?= $block->escapeHtmlAttr($block->stripTags($block->getProduct()->getName())) ?>" />
 <meta property="og:image" content="<?= $block->escapeUrl($block->getImage($block->getProduct(), 'product_base_image')->getImageUrl()) ?>" />
 <meta property="og:description" content="<?= $block->escapeHtmlAttr($block->stripTags($block->getProduct()->getShortDescription())) ?>" />
diff --git a/app/code/Magento/SampleData/Console/Command/SampleDataDeployCommand.php b/app/code/Magento/SampleData/Console/Command/SampleDataDeployCommand.php
index 158c588d113582756a03114fee0fbd24737ee0ff..88df47283133a2ceacf371b0d65cf730da1f346e 100644
--- a/app/code/Magento/SampleData/Console/Command/SampleDataDeployCommand.php
+++ b/app/code/Magento/SampleData/Console/Command/SampleDataDeployCommand.php
@@ -12,6 +12,7 @@ use Magento\Setup\Model\PackagesAuth;
 use Symfony\Component\Console\Command\Command;
 use Symfony\Component\Console\Input\ArrayInput;
 use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
 
 /**
@@ -19,6 +20,8 @@ use Symfony\Component\Console\Output\OutputInterface;
  */
 class SampleDataDeployCommand extends Command
 {
+    const OPTION_NO_UPDATE = 'no-update';
+
     /**
      * @var \Magento\Framework\Filesystem
      */
@@ -66,6 +69,12 @@ class SampleDataDeployCommand extends Command
     {
         $this->setName('sampledata:deploy')
             ->setDescription('Deploy sample data modules');
+        $this->addOption(
+            self::OPTION_NO_UPDATE,
+            null,
+            InputOption::VALUE_NONE,
+            'Update composer.json without executing composer update'
+        );
         parent::configure();
     }
 
@@ -80,6 +89,9 @@ class SampleDataDeployCommand extends Command
         if (!empty($sampleDataPackages)) {
             $baseDir = $this->filesystem->getDirectoryRead(DirectoryList::ROOT)->getAbsolutePath();
             $commonArgs = ['--working-dir' => $baseDir, '--no-progress' => 1];
+            if ($input->getOption(self::OPTION_NO_UPDATE)) {
+                $commonArgs['--no-update'] = 1;
+            }
             $packages = [];
             foreach ($sampleDataPackages as $name => $version) {
                 $packages[] = "$name:$version";
diff --git a/app/code/Magento/SampleData/Console/Command/SampleDataRemoveCommand.php b/app/code/Magento/SampleData/Console/Command/SampleDataRemoveCommand.php
index 36f5c591bedc34e80ff895d02b9b869fd3f744f3..5e10b6c6e59302aad9c0994cbe88000d81547692 100644
--- a/app/code/Magento/SampleData/Console/Command/SampleDataRemoveCommand.php
+++ b/app/code/Magento/SampleData/Console/Command/SampleDataRemoveCommand.php
@@ -8,6 +8,7 @@ namespace Magento\SampleData\Console\Command;
 
 use Symfony\Component\Console\Command\Command;
 use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
 use Symfony\Component\Console\Output\OutputInterface;
 use Magento\SampleData\Model\Dependency;
 use Symfony\Component\Console\Input\ArrayInput;
@@ -22,6 +23,8 @@ use Composer\Console\ApplicationFactory;
  */
 class SampleDataRemoveCommand extends Command
 {
+    const OPTION_NO_UPDATE = 'no-update';
+
     /**
      * @var Filesystem
      */
@@ -69,6 +72,12 @@ class SampleDataRemoveCommand extends Command
     {
         $this->setName('sampledata:remove')
             ->setDescription('Remove all sample data packages from composer.json');
+        $this->addOption(
+            self::OPTION_NO_UPDATE,
+            null,
+            InputOption::VALUE_NONE,
+            'Update composer.json without executing composer update'
+        );
         parent::configure();
     }
 
@@ -81,6 +90,9 @@ class SampleDataRemoveCommand extends Command
         if (!empty($sampleDataPackages)) {
             $baseDir = $this->filesystem->getDirectoryRead(DirectoryList::ROOT)->getAbsolutePath();
             $commonArgs = ['--working-dir' => $baseDir, '--no-interaction' => 1, '--no-progress' => 1];
+            if ($input->getOption(self::OPTION_NO_UPDATE)) {
+                $commonArgs['--no-update'] = 1;
+            }
             $packages = array_keys($sampleDataPackages);
             $arguments = array_merge(['command' => 'remove', 'packages' => $packages], $commonArgs);
             $commandInput = new ArrayInput($arguments);
diff --git a/app/code/Magento/SampleData/Test/Unit/Console/Command/AbstractSampleDataCommandTest.php b/app/code/Magento/SampleData/Test/Unit/Console/Command/AbstractSampleDataCommandTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..090bb4256f80723cf95a2bf3ffa31f3d76372ec9
--- /dev/null
+++ b/app/code/Magento/SampleData/Test/Unit/Console/Command/AbstractSampleDataCommandTest.php
@@ -0,0 +1,130 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\SampleData\Test\Unit\Console\Command;
+
+use Composer\Console\Application;
+use Composer\Console\ApplicationFactory;
+use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\Framework\Filesystem;
+use Magento\Framework\Filesystem\Directory\ReadInterface;
+use Magento\Framework\Filesystem\Directory\WriteInterface;
+use Magento\SampleData\Model\Dependency;
+use PHPUnit\Framework\TestCase;
+use Symfony\Component\Console\Input\ArrayInput;
+use Symfony\Component\Console\Input\ArrayInputFactory;
+
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+abstract class AbstractSampleDataCommandTest extends TestCase
+{
+    /**
+     * @var ReadInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $directoryReadMock;
+
+    /**
+     * @var WriteInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $directoryWriteMock;
+
+    /**
+     * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $filesystemMock;
+
+    /**
+     * @var Dependency|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $sampleDataDependencyMock;
+
+    /**
+     * @var ArrayInputFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $arrayInputFactoryMock;
+
+    /**
+     * @var Application|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $applicationMock;
+
+    /**
+     * @var ApplicationFactory|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $applicationFactoryMock;
+
+    /**
+     * @return void
+     */
+    protected function setUp()
+    {
+        $this->directoryReadMock = $this->createMock(ReadInterface::class);
+        $this->directoryWriteMock = $this->createMock(WriteInterface::class);
+        $this->filesystemMock = $this->createMock(Filesystem::class);
+        $this->sampleDataDependencyMock = $this->createMock(Dependency::class);
+        $this->arrayInputFactoryMock = $this->createMock(ArrayInputFactory::class);
+        $this->applicationMock = $this->createMock(Application::class);
+        $this->applicationFactoryMock = $this->createPartialMock(ApplicationFactory::class, ['create']);
+    }
+
+    /**
+     * @param array $sampleDataPackages     Array in form [package_name => version_constraint]
+     * @param string $pathToComposerJson    Fake path to composer.json
+     * @param int $appRunResult             Composer exit code
+     * @param array $additionalComposerArgs Additional arguments that composer expects
+     */
+    protected function setupMocks(
+        $sampleDataPackages,
+        $pathToComposerJson,
+        $appRunResult,
+        $additionalComposerArgs = []
+    ) {
+        $this->directoryReadMock->expects($this->any())->method('getAbsolutePath')->willReturn($pathToComposerJson);
+        $this->filesystemMock->expects($this->any())->method('getDirectoryRead')->with(DirectoryList::ROOT)->willReturn(
+            $this->directoryReadMock
+        );
+        $this->sampleDataDependencyMock->expects($this->any())->method('getSampleDataPackages')->willReturn(
+            $sampleDataPackages
+        );
+        $this->arrayInputFactoryMock->expects($this->never())->method('create');
+
+        $this->applicationMock->expects($this->any())
+            ->method('run')
+            ->with(
+                new ArrayInput(
+                    array_merge(
+                        $this->expectedComposerArguments(
+                            $sampleDataPackages,
+                            $pathToComposerJson
+                        ),
+                        $additionalComposerArgs
+                    )
+                ),
+                $this->anything()
+            )
+            ->willReturn($appRunResult);
+
+        if (($appRunResult !== 0) && !empty($sampleDataPackages)) {
+            $this->applicationMock->expects($this->once())->method('resetComposer')->willReturnSelf();
+        }
+
+        $this->applicationFactoryMock->expects($this->any())
+            ->method('create')
+            ->willReturn($this->applicationMock);
+    }
+
+    /**
+     * Expected arguments for composer based on sample data packages and composer.json path
+     *
+     * @param array $sampleDataPackages
+     * @param string $pathToComposerJson
+     * @return array
+     */
+    abstract protected function expectedComposerArguments(
+        array $sampleDataPackages,
+        string $pathToComposerJson
+    ) : array;
+}
diff --git a/app/code/Magento/SampleData/Test/Unit/Console/Command/SampleDataDeployCommandTest.php b/app/code/Magento/SampleData/Test/Unit/Console/Command/SampleDataDeployCommandTest.php
index 464a6c9ccd832921c8e781225c306efa55ab6d5a..450b2d8798f52491bfd8976f609c343fda18037d 100644
--- a/app/code/Magento/SampleData/Test/Unit/Console/Command/SampleDataDeployCommandTest.php
+++ b/app/code/Magento/SampleData/Test/Unit/Console/Command/SampleDataDeployCommandTest.php
@@ -9,66 +9,26 @@ use Magento\Framework\App\Filesystem\DirectoryList;
 use Magento\SampleData\Console\Command\SampleDataDeployCommand;
 use Magento\Setup\Model\PackagesAuth;
 use Symfony\Component\Console\Tester\CommandTester;
-use Magento\Framework\Filesystem;
-use Magento\Framework\Filesystem\Directory\ReadInterface;
-use Magento\Framework\Filesystem\Directory\WriteInterface;
-use Magento\SampleData\Model\Dependency;
-use Symfony\Component\Console\Input\ArrayInputFactory;
-use Composer\Console\ApplicationFactory;
-use Composer\Console\Application;
 
-/**
- * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
- */
-class SampleDataDeployCommandTest extends \PHPUnit\Framework\TestCase
+class SampleDataDeployCommandTest extends AbstractSampleDataCommandTest
 {
     /**
-     * @var ReadInterface|\PHPUnit_Framework_MockObject_MockObject
-     */
-    private $directoryReadMock;
-
-    /**
-     * @var WriteInterface|\PHPUnit_Framework_MockObject_MockObject
-     */
-    private $directoryWriteMock;
-
-    /**
-     * @var Filesystem|\PHPUnit_Framework_MockObject_MockObject
-     */
-    private $filesystemMock;
-
-    /**
-     * @var Dependency|\PHPUnit_Framework_MockObject_MockObject
-     */
-    private $sampleDataDependencyMock;
-
-    /**
-     * @var ArrayInputFactory|\PHPUnit_Framework_MockObject_MockObject
-     */
-    private $arrayInputFactoryMock;
-
-    /**
-     * @var Application|\PHPUnit_Framework_MockObject_MockObject
-     */
-    private $applicationMock;
-
-    /**
-     * @var ApplicationFactory|\PHPUnit_Framework_MockObject_MockObject
+     * @param bool $authExist               True to test with existing auth.json, false without
      */
-    private $applicationFactoryMock;
-
-    /**
-     * @return void
-     */
-    protected function setUp()
+    protected function setupMocksForAuthFile($authExist)
     {
-        $this->directoryReadMock = $this->createMock(ReadInterface::class);
-        $this->directoryWriteMock = $this->createMock(WriteInterface::class);
-        $this->filesystemMock = $this->createMock(Filesystem::class);
-        $this->sampleDataDependencyMock = $this->createMock(Dependency::class);
-        $this->arrayInputFactoryMock = $this->createMock(ArrayInputFactory::class);
-        $this->applicationMock = $this->createMock(Application::class);
-        $this->applicationFactoryMock = $this->createPartialMock(ApplicationFactory::class, ['create']);
+        $this->directoryWriteMock->expects($this->once())
+            ->method('isExist')
+            ->with(PackagesAuth::PATH_TO_AUTH_FILE)
+            ->willReturn($authExist);
+        $this->directoryWriteMock->expects($authExist ? $this->never() : $this->once())->method('writeFile')->with(
+            PackagesAuth::PATH_TO_AUTH_FILE,
+            '{}'
+        );
+        $this->filesystemMock->expects($this->once())
+            ->method('getDirectoryWrite')
+            ->with(DirectoryList::COMPOSER_HOME)
+            ->willReturn($this->directoryWriteMock);
     }
 
     /**
@@ -82,68 +42,36 @@ class SampleDataDeployCommandTest extends \PHPUnit\Framework\TestCase
      */
     public function testExecute(array $sampleDataPackages, $appRunResult, $expectedMsg, $authExist)
     {
-        $pathToComposerJson = '/path/to/composer.json';
-
-        $this->directoryReadMock->expects($this->any())
-            ->method('getAbsolutePath')
-            ->willReturn($pathToComposerJson);
-        $this->directoryWriteMock->expects($this->once())
-            ->method('isExist')
-            ->with(PackagesAuth::PATH_TO_AUTH_FILE)
-            ->willReturn($authExist);
-        $this->directoryWriteMock->expects($authExist ? $this->never() : $this->once())
-            ->method('writeFile')
-            ->with(PackagesAuth::PATH_TO_AUTH_FILE, '{}');
-        $this->filesystemMock->expects($this->any())
-            ->method('getDirectoryRead')
-            ->with(DirectoryList::ROOT)
-            ->willReturn($this->directoryReadMock);
-        $this->filesystemMock->expects($this->once())
-            ->method('getDirectoryWrite')
-            ->with(DirectoryList::COMPOSER_HOME)
-            ->willReturn($this->directoryWriteMock);
-        $this->sampleDataDependencyMock->expects($this->any())
-            ->method('getSampleDataPackages')
-            ->willReturn($sampleDataPackages);
-        $this->arrayInputFactoryMock->expects($this->never())
-            ->method('create');
-
-        array_walk($sampleDataPackages, function (&$v, $k) {
-            $v = "$k:$v";
-        });
-
-        $packages = array_values($sampleDataPackages);
-
-        $requireArgs = [
-            'command'       => 'require',
-            '--working-dir' => $pathToComposerJson,
-            '--no-progress' => 1,
-            'packages'      => $packages,
-        ];
-        $commandInput = new \Symfony\Component\Console\Input\ArrayInput($requireArgs);
-
-        $this->applicationMock->expects($this->any())
-            ->method('run')
-            ->with($commandInput, $this->anything())
-            ->willReturn($appRunResult);
-
-        if (($appRunResult !== 0) && !empty($sampleDataPackages)) {
-            $this->applicationMock->expects($this->once())->method('resetComposer')->willReturnSelf();
-        }
+        $this->setupMocks($sampleDataPackages, '/path/to/composer.json', $appRunResult);
+        $this->setupMocksForAuthFile($authExist);
+        $commandTester = $this->createCommandTester();
+        $commandTester->execute([]);
 
-        $this->applicationFactoryMock->expects($this->any())
-            ->method('create')
-            ->willReturn($this->applicationMock);
+        $this->assertEquals($expectedMsg, $commandTester->getDisplay());
+    }
 
-        $commandTester = new CommandTester(
-            new SampleDataDeployCommand(
-                $this->filesystemMock,
-                $this->sampleDataDependencyMock,
-                $this->arrayInputFactoryMock,
-                $this->applicationFactoryMock
-            )
+    /**
+     * @param array     $sampleDataPackages
+     * @param int       $appRunResult - int 0 if everything went fine, or an error code
+     * @param string    $expectedMsg
+     * @param bool      $authExist
+     * @return          void
+     *
+     * @dataProvider processDataProvider
+     */
+    public function testExecuteWithNoUpdate(array $sampleDataPackages, $appRunResult, $expectedMsg, $authExist)
+    {
+        $this->setupMocks(
+            $sampleDataPackages,
+            '/path/to/composer.json',
+            $appRunResult,
+            ['--no-update' => 1]
         );
-        $commandTester->execute([]);
+        $this->setupMocksForAuthFile($authExist);
+        $commandInput = ['--no-update' => 1];
+
+        $commandTester = $this->createCommandTester();
+        $commandTester->execute($commandInput);
 
         $this->assertEquals($expectedMsg, $commandTester->getDisplay());
     }
@@ -154,13 +82,13 @@ class SampleDataDeployCommandTest extends \PHPUnit\Framework\TestCase
     public function processDataProvider()
     {
         return [
-            [
+            'No sample data found' => [
                 'sampleDataPackages' => [],
                 'appRunResult' => 1,
                 'expectedMsg' => 'There is no sample data for current set of modules.' . PHP_EOL,
                 'authExist' => true,
             ],
-            [
+            'No auth.json found' => [
                 'sampleDataPackages' => [
                     'magento/module-cms-sample-data' => '1.0.0-beta',
                 ],
@@ -169,7 +97,7 @@ class SampleDataDeployCommandTest extends \PHPUnit\Framework\TestCase
                     . PHP_EOL,
                 'authExist' => false,
             ],
-            [
+            'Successful sample data installation' => [
                 'sampleDataPackages' => [
                     'magento/module-cms-sample-data' => '1.0.0-beta',
                 ],
@@ -204,6 +132,14 @@ class SampleDataDeployCommandTest extends \PHPUnit\Framework\TestCase
             ->with(DirectoryList::COMPOSER_HOME)
             ->willReturn($this->directoryWriteMock);
 
+        $this->createCommandTester()->execute([]);
+    }
+
+    /**
+     * @return CommandTester
+     */
+    private function createCommandTester(): CommandTester
+    {
         $commandTester = new CommandTester(
             new SampleDataDeployCommand(
                 $this->filesystemMock,
@@ -212,6 +148,36 @@ class SampleDataDeployCommandTest extends \PHPUnit\Framework\TestCase
                 $this->applicationFactoryMock
             )
         );
-        $commandTester->execute([]);
+        return $commandTester;
+    }
+
+    /**
+     * @param $sampleDataPackages
+     * @param $pathToComposerJson
+     * @return array
+     */
+    protected function expectedComposerArguments(
+        array $sampleDataPackages,
+        string $pathToComposerJson
+    ) : array {
+        return [
+            'command' => 'require',
+            '--working-dir' => $pathToComposerJson,
+            '--no-progress' => 1,
+            'packages' => $this->packageVersionStrings($sampleDataPackages),
+        ];
+    }
+
+    /**
+     * @param array $sampleDataPackages
+     * @return array
+     */
+    private function packageVersionStrings(array $sampleDataPackages): array
+    {
+        array_walk($sampleDataPackages, function (&$v, $k) {
+            $v = "$k:$v";
+        });
+
+        return array_values($sampleDataPackages);
     }
 }
diff --git a/app/code/Magento/SampleData/Test/Unit/Console/Command/SampleDataRemoveCommandTest.php b/app/code/Magento/SampleData/Test/Unit/Console/Command/SampleDataRemoveCommandTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..7fce70fd9c3764acaf37c6106519d2e10add7832
--- /dev/null
+++ b/app/code/Magento/SampleData/Test/Unit/Console/Command/SampleDataRemoveCommandTest.php
@@ -0,0 +1,109 @@
+<?php
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\SampleData\Test\Unit\Console\Command;
+
+use Magento\SampleData\Console\Command\SampleDataRemoveCommand;
+use Symfony\Component\Console\Tester\CommandTester;
+
+class SampleDataRemoveCommandTest extends AbstractSampleDataCommandTest
+{
+
+    /**
+     * @param array     $sampleDataPackages
+     * @param int       $appRunResult - int 0 if everything went fine, or an error code
+     * @param string    $expectedMsg
+     * @return          void
+     *
+     * @dataProvider processDataProvider
+     */
+    public function testExecute(array $sampleDataPackages, $appRunResult, $expectedMsg)
+    {
+        $this->setupMocks($sampleDataPackages, '/path/to/composer.json', $appRunResult);
+        $commandTester = $this->createCommandTester();
+        $commandTester->execute([]);
+
+        $this->assertEquals($expectedMsg, $commandTester->getDisplay());
+    }
+
+    /**
+     * @param array     $sampleDataPackages
+     * @param int       $appRunResult - int 0 if everything went fine, or an error code
+     * @param string    $expectedMsg
+     * @return          void
+     *
+     * @dataProvider processDataProvider
+     */
+    public function testExecuteWithNoUpdate(array $sampleDataPackages, $appRunResult, $expectedMsg)
+    {
+        $this->setupMocks(
+            $sampleDataPackages,
+            '/path/to/composer.json',
+            $appRunResult,
+            ['--no-update' => 1]
+        );
+        $commandInput = ['--no-update' => 1];
+
+        $commandTester = $this->createCommandTester();
+        $commandTester->execute($commandInput);
+
+        $this->assertEquals($expectedMsg, $commandTester->getDisplay());
+    }
+
+    /**
+     * @return array
+     */
+    public function processDataProvider()
+    {
+        return [
+            'No sample data found' => [
+                'sampleDataPackages' => [],
+                'appRunResult' => 1,
+                'expectedMsg' => 'There is no sample data for current set of modules.' . PHP_EOL,
+            ],
+            'Successful sample data installation' => [
+                'sampleDataPackages' => [
+                    'magento/module-cms-sample-data' => '1.0.0-beta',
+                ],
+                'appRunResult' => 0,
+                'expectedMsg' => '',
+            ],
+        ];
+    }
+
+    /**
+     * @return CommandTester
+     */
+    private function createCommandTester(): CommandTester
+    {
+        $commandTester = new CommandTester(
+            new SampleDataRemoveCommand(
+                $this->filesystemMock,
+                $this->sampleDataDependencyMock,
+                $this->arrayInputFactoryMock,
+                $this->applicationFactoryMock
+            )
+        );
+        return $commandTester;
+    }
+
+    /**
+     * @param $sampleDataPackages
+     * @param $pathToComposerJson
+     * @return array
+     */
+    protected function expectedComposerArguments(
+        array $sampleDataPackages,
+        string $pathToComposerJson
+    ) : array {
+        return [
+            'command' => 'remove',
+            '--working-dir' => $pathToComposerJson,
+            '--no-interaction' => 1,
+            '--no-progress' => 1,
+            'packages' => array_keys($sampleDataPackages),
+        ];
+    }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php
index 48928b4c5c9acde96a2ce697e16ef5984ec40749..6e93f61c1eb3aaa38c72977323b94ddabfe6a2fd 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/AttributeTest.php
@@ -128,12 +128,12 @@ class AttributeTest extends \Magento\TestFramework\TestCase\AbstractBackendContr
      */
     public function testWrongAttributeCode()
     {
-        $postData = $this->_getAttributeData() + ['attribute_id' => '2', 'attribute_code' => '_()&&&?'];
+        $postData = $this->_getAttributeData() + ['attribute_code' => '_()&&&?'];
         $this->getRequest()->setPostValue($postData);
         $this->dispatch('backend/catalog/product_attribute/save');
         $this->assertEquals(302, $this->getResponse()->getHttpResponseCode());
         $this->assertContains(
-            'catalog/product_attribute/edit/attribute_id/2',
+            'catalog/product_attribute/edit',
             $this->getResponse()->getHeader('Location')->getFieldValue()
         );
         /** @var \Magento\Framework\Message\Collection $messages */
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Session/ConfigTest.php b/dev/tests/integration/testsuite/Magento/Framework/Session/ConfigTest.php
index 629089ae4d99ee3ca310aa1f2586305e7194710d..573531cff960acb65c038b2b49ce8b28ea5b5390 100644
--- a/dev/tests/integration/testsuite/Magento/Framework/Session/ConfigTest.php
+++ b/dev/tests/integration/testsuite/Magento/Framework/Session/ConfigTest.php
@@ -36,15 +36,18 @@ class ConfigTest extends \PHPUnit\Framework\TestCase
             $sessionManager->writeClose();
         }
         $this->deploymentConfigMock = $this->createMock(\Magento\Framework\App\DeploymentConfig::class);
-
-        $this->deploymentConfigMock->expects($this->at(0))
-            ->method('get')
-            ->with(Config::PARAM_SESSION_SAVE_PATH)
-            ->will($this->returnValue(null));
-        $this->deploymentConfigMock->expects($this->at(1))
+        $this->deploymentConfigMock
             ->method('get')
-            ->with(Config::PARAM_SESSION_CACHE_LIMITER)
-            ->will($this->returnValue($this->_cacheLimiter));
+            ->willReturnCallback(function ($configPath) {
+                switch ($configPath) {
+                    case Config::PARAM_SESSION_SAVE_METHOD:
+                        return 'files';
+                    case Config::PARAM_SESSION_CACHE_LIMITER:
+                        return $this->_cacheLimiter;
+                    default:
+                        return null;
+                }
+            });
 
         $this->_model = $this->_objectManager->create(
             \Magento\Framework\Session\Config::class,
@@ -83,15 +86,6 @@ class ConfigTest extends \PHPUnit\Framework\TestCase
         $this->assertEquals($this->_model->getSavePath(), $this->_model->getOption('save_path'));
     }
 
-    /**
-     * Unable to add integration tests for testGetLifetimePathNonDefault
-     *
-     * Error: Cannot modify header information - headers already sent
-     */
-    public function testGetLifetimePathNonDefault()
-    {
-    }
-
     public function testSetOptionsInvalidValue()
     {
         $preValue = $this->_model->getOptions();
@@ -280,16 +274,27 @@ class ConfigTest extends \PHPUnit\Framework\TestCase
             $this->markTestSkipped('Cannot set session.save_path with ini_set');
         }
 
-        $this->deploymentConfigMock->expects($this->at(0))
+        $deploymentConfigMock = $this->createMock(\Magento\Framework\App\DeploymentConfig::class);
+        $deploymentConfigMock
             ->method('get')
-            ->with(Config::PARAM_SESSION_SAVE_PATH)
-            ->will($this->returnValue($given));
-
-        $this->_model = $this->_objectManager->create(
+            ->willReturnCallback(function ($configPath) use ($given) {
+                switch ($configPath) {
+                    case Config::PARAM_SESSION_SAVE_METHOD:
+                        return 'files';
+                    case Config::PARAM_SESSION_CACHE_LIMITER:
+                        return $this->_cacheLimiter;
+                    case Config::PARAM_SESSION_SAVE_PATH:
+                        return $given;
+                    default:
+                        return null;
+                }
+            });
+
+        $model = $this->_objectManager->create(
             \Magento\Framework\Session\Config::class,
-            ['deploymentConfig' => $this->deploymentConfigMock]
+            ['deploymentConfig' => $deploymentConfigMock]
         );
-        $this->assertEquals($expected, $this->_model->getOption('save_path'));
+        $this->assertEquals($expected, $model->getOption('save_path'));
 
         if ($sessionSavePath != ini_get('session.save_path')) {
             ini_set('session.save_path', $sessionSavePath);
diff --git a/lib/internal/Magento/Framework/Session/Config.php b/lib/internal/Magento/Framework/Session/Config.php
index 26ae1635f18f196ed9d09dd87d1ae961d895388a..053bd3e7fd6b9e81371b39dc49d2bf6449992888 100644
--- a/lib/internal/Magento/Framework/Session/Config.php
+++ b/lib/internal/Magento/Framework/Session/Config.php
@@ -134,6 +134,14 @@ class Config implements ConfigInterface
             $this->setSavePath($savePath);
         }
 
+        /**
+        * Session save handler - memcache, files, etc
+        */
+        $saveHandler = $deploymentConfig->get(self::PARAM_SESSION_SAVE_METHOD);
+        if ($saveHandler) {
+            $this->setOption('session.save_handler', $saveHandler);
+        }
+
         /**
          * Session cache limiter
          */
diff --git a/lib/internal/Magento/Framework/Session/Test/Unit/ConfigTest.php b/lib/internal/Magento/Framework/Session/Test/Unit/ConfigTest.php
index 66fc12b493090713bd62afba4dfe50c0c0e7b5e3..12e28cdb3970dfa22aa90da377a7eb74c46a8817 100644
--- a/lib/internal/Magento/Framework/Session/Test/Unit/ConfigTest.php
+++ b/lib/internal/Magento/Framework/Session/Test/Unit/ConfigTest.php
@@ -350,33 +350,36 @@ class ConfigTest extends \PHPUnit\Framework\TestCase
                 true,
                 true,
                 [
-                    'session.cache_limiter' => 'files',
+                    'session.cache_limiter' => 'private_no_expire',
                     'session.cookie_lifetime' => 7200,
                     'session.cookie_path' => '/',
                     'session.cookie_domain' => 'init.host',
                     'session.cookie_httponly' => false,
                     'session.cookie_secure' => false,
+                    'session.save_handler' => 'files'
                 ],
             ],
             'all invalid' => [
                 true,
                 false,
                 [
-                    'session.cache_limiter' => 'files',
+                    'session.cache_limiter' => 'private_no_expire',
                     'session.cookie_httponly' => false,
                     'session.cookie_secure' => false,
+                    'session.save_handler' => 'files'
                 ],
             ],
             'invalid_valid' => [
                 false,
                 true,
                 [
-                    'session.cache_limiter' => 'files',
+                    'session.cache_limiter' => 'private_no_expire',
                     'session.cookie_lifetime' => 3600,
                     'session.cookie_path' => '/',
                     'session.cookie_domain' => 'init.host',
                     'session.cookie_httponly' => false,
                     'session.cookie_secure' => false,
+                    'session.save_handler' => 'files'
                 ],
             ],
         ];
@@ -429,14 +432,18 @@ class ConfigTest extends \PHPUnit\Framework\TestCase
             ->will($this->returnValue($dirMock));
 
         $deploymentConfigMock = $this->createMock(\Magento\Framework\App\DeploymentConfig::class);
-        $deploymentConfigMock->expects($this->at(0))
+        $deploymentConfigMock
             ->method('get')
-            ->with(Config::PARAM_SESSION_SAVE_PATH)
-            ->will($this->returnValue(null));
-        $deploymentConfigMock->expects($this->at(1))
-            ->method('get')
-            ->with(Config::PARAM_SESSION_CACHE_LIMITER)
-            ->will($this->returnValue('files'));
+            ->willReturnCallback(function ($configPath) {
+                switch ($configPath) {
+                    case Config::PARAM_SESSION_SAVE_METHOD:
+                        return 'files';
+                    case Config::PARAM_SESSION_CACHE_LIMITER:
+                        return 'private_no_expire';
+                    default:
+                        return null;
+                }
+            });
 
         $this->config = $this->helper->getObject(
             \Magento\Framework\Session\Config::class,