diff --git a/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php b/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php
index 5c8f89c8982d9d0e4cfbc55ca1d67ab15337aa31..227a7d0fc9f0c3b002daf25be16bd0912e70082b 100644
--- a/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php
+++ b/app/code/Magento/BundleImportExport/Model/Export/RowCustomizer.php
@@ -10,6 +10,7 @@ use Magento\CatalogImportExport\Model\Export\RowCustomizerInterface;
 use Magento\CatalogImportExport\Model\Import\Product as ImportProductModel;
 use Magento\Bundle\Model\ResourceModel\Selection\Collection as SelectionCollection;
 use Magento\ImportExport\Controller\Adminhtml\Import;
+use Magento\ImportExport\Model\Import as ImportModel;
 
 /**
  * Class RowCustomizer
@@ -90,12 +91,13 @@ class RowCustomizer implements RowCustomizerInterface
      * Prepare data for export
      *
      * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $collection
-     * @param int $productIds
+     * @param int[] $productIds
      * @return $this
      */
     public function prepareData($collection, $productIds)
     {
-        $collection->addAttributeToFilter(
+        $productCollection = clone $collection;
+        $productCollection->addAttributeToFilter(
             'entity_id',
             ['in' => $productIds]
         )->addAttributeToFilter(
@@ -103,7 +105,7 @@ class RowCustomizer implements RowCustomizerInterface
             ['eq' => \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE]
         );
 
-        return $this->populateBundleData($collection);
+        return $this->populateBundleData($productCollection);
     }
 
     /**
@@ -214,9 +216,9 @@ class RowCustomizer implements RowCustomizerInterface
                 'price_type' => $this->getPriceTypeValue($selection->getSelectionPriceType())
             ];
             $bundleData .= $optionValues
-                . ImportProductModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
+                . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
                 . implode(
-                    ImportProductModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR,
+                    ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR,
                     array_map(
                         function ($value, $key) {
                             return $key . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR . $value;
@@ -240,9 +242,9 @@ class RowCustomizer implements RowCustomizerInterface
     protected function getFormattedOptionValues($option)
     {
         return 'name' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR
-        . $option->getTitle() . ImportProductModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
+        . $option->getTitle() . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
         . 'type' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR
-        . $option->getType() . ImportProductModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
+        . $option->getType() . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
         . 'required' . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR
         . $option->getRequired();
     }
@@ -290,7 +292,7 @@ class RowCustomizer implements RowCustomizerInterface
     {
         if (!empty($dataRow['additional_attributes'])) {
             $additionalAttributes = explode(
-                ImportProductModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR,
+                ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR,
                 $dataRow['additional_attributes']
             );
             $dataRow['additional_attributes'] = $this->getNotBundleAttributes($additionalAttributes);
@@ -314,10 +316,10 @@ class RowCustomizer implements RowCustomizerInterface
                 $cleanedAdditionalAttributes .= $attributeCode
                     . ImportProductModel::PAIR_NAME_VALUE_SEPARATOR
                     . $attributeValue
-                    . ImportProductModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR;
+                    . ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR;
             }
         }
 
-        return rtrim($cleanedAdditionalAttributes, ImportProductModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR);
+        return rtrim($cleanedAdditionalAttributes, ImportModel::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR);
     }
 }
diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php
index 5ba8ed055cd25afb032289d36d94856909e3ff07..30451fbbf94e7e0ed0bfb604bcf7ecda7f5efba9 100644
--- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php
@@ -5,6 +5,7 @@
  */
 namespace Magento\CatalogImportExport\Model\Export;
 
+use Magento\ImportExport\Model\Import;
 use \Magento\Store\Model\Store;
 use \Magento\CatalogImportExport\Model\Import\Product as ImportProduct;
 
@@ -638,7 +639,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
             }
             $categories[] = $categoryPath;
         }
-        $dataRow[self::COL_CATEGORY] = implode(ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR, $categories);
+        $dataRow[self::COL_CATEGORY] = implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $categories);
         unset($rowCategories[$productId]);
 
         return true;
@@ -672,7 +673,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
                     self::COL_ATTR_SET,
                     self::COL_TYPE,
                     self::COL_CATEGORY,
-                    '_product_websites',
+                    self::COL_PRODUCT_WEBSITES,
                 ],
                 $this->_getExportMainAttrCodes(),
                 [self::COL_ADDITIONAL_ATTRIBUTES],
@@ -683,7 +684,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
                     'crosssell_skus',
                     'upsell_skus',
                 ],
-                ['additional_images', 'additional_image_labels']
+                ['additional_images', 'additional_image_labels', 'hide_from_product_page']
             );
             // have we merge custom options columns
             if ($customOptionsData) {
@@ -901,7 +902,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
 
                 if (!empty($additionalAttributes)) {
                     $data[$itemId][$storeId][self::COL_ADDITIONAL_ATTRIBUTES] =
-                        implode(ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalAttributes);
+                        implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalAttributes);
                 } else {
                     unset($data[$itemId][$storeId][self::COL_ADDITIONAL_ATTRIBUTES]);
                 }
@@ -981,7 +982,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
     protected function collectMultiselectValues($item, $attrCode, $storeId)
     {
         $attrValue = $item->getData($attrCode);
-        $optionIds = explode(ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $attrValue);
+        $optionIds = explode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $attrValue);
         $options = array_intersect_key(
             $this->_attributeValues[$attrCode],
             array_flip($optionIds)
@@ -1040,21 +1041,28 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
                 foreach ($multiRawData['rowWebsites'][$productId] as $productWebsite) {
                     $websiteCodes[] = $this->_websiteIdToCode[$productWebsite];
                 }
-                $dataRow['_product_websites'] =
-                    implode(ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $websiteCodes);
+                $dataRow[self::COL_PRODUCT_WEBSITES] =
+                    implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $websiteCodes);
                 $multiRawData['rowWebsites'][$productId] = [];
             }
             if (!empty($multiRawData['mediaGalery'][$productId])) {
                 $additionalImages = [];
                 $additionalImageLabels = [];
+                $additionalImageIsDisabled = [];
                 foreach ($multiRawData['mediaGalery'][$productId] as $mediaItem) {
                     $additionalImages[] = $mediaItem['_media_image'];
                     $additionalImageLabels[] = $mediaItem['_media_label'];
+
+                    if ($mediaItem['_media_is_disabled'] == true) {
+                        $additionalImageIsDisabled[] = $mediaItem['_media_image'];
+                    }
                 }
                 $dataRow['additional_images'] =
-                    implode(ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalImages);
+                    implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalImages);
                 $dataRow['additional_image_labels'] =
-                    implode(ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalImageLabels);
+                    implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalImageLabels);
+                $dataRow['hide_from_product_page'] =
+                    implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $additionalImageIsDisabled);
                 $multiRawData['mediaGalery'][$productId] = [];
             }
             foreach ($this->_linkTypeProvider->getLinkTypes() as $linkTypeName => $linkId) {
@@ -1074,7 +1082,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
                     $multiRawData['linksRows'][$productId][$linkId] = [];
                     asort($associations);
                     $dataRow[$colPrefix . 'skus'] =
-                        implode(ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, array_keys($associations));
+                        implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, array_keys($associations));
                 }
             }
             $dataRow = $this->rowCustomizer->addData($dataRow, $productId);
@@ -1085,7 +1093,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
             foreach (array_keys($this->collectedMultiselectsData[$storeId][$productId]) as $attrKey) {
                 if (!empty($this->collectedMultiselectsData[$storeId][$productId][$attrKey])) {
                     $dataRow[$attrKey] = implode(
-                        ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR,
+                        Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR,
                         $this->collectedMultiselectsData[$storeId][$productId][$attrKey]
                     );
                 }
@@ -1161,7 +1169,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
             $result[] = $key . ImportProduct::PAIR_NAME_VALUE_SEPARATOR . $value;
         }
 
-        return implode(ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $result);
+        return implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $result);
     }
 
     /**
diff --git a/app/code/Magento/CatalogImportExport/Model/Export/RowCustomizer/Composite.php b/app/code/Magento/CatalogImportExport/Model/Export/RowCustomizer/Composite.php
index 1997d39b47efcb9b81c712a22180ab86d97d84a0..ab96bf9f3545f53accd86477dffe605b1cd0814a 100644
--- a/app/code/Magento/CatalogImportExport/Model/Export/RowCustomizer/Composite.php
+++ b/app/code/Magento/CatalogImportExport/Model/Export/RowCustomizer/Composite.php
@@ -34,7 +34,7 @@ class Composite implements RowCustomizerInterface
      * Prepare data for export
      *
      * @param mixed $collection
-     * @param int $productIds
+     * @param int[] $productIds
      * @return mixed|void
      */
     public function prepareData($collection, $productIds)
diff --git a/app/code/Magento/CatalogImportExport/Model/Export/RowCustomizerInterface.php b/app/code/Magento/CatalogImportExport/Model/Export/RowCustomizerInterface.php
index 6723dbc24afe1d86083ad3a759e359c309c956f8..240284ed32018edae5d3e653ada920d2a0523cb2 100644
--- a/app/code/Magento/CatalogImportExport/Model/Export/RowCustomizerInterface.php
+++ b/app/code/Magento/CatalogImportExport/Model/Export/RowCustomizerInterface.php
@@ -14,7 +14,7 @@ interface RowCustomizerInterface
      * Prepare data for export
      *
      * @param mixed $collection
-     * @param int $productIds
+     * @param int[] $productIds
      * @return mixed
      */
     public function prepareData($collection, $productIds);
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index 24475736833f8cbcbda2528614e5f3d3573b2e53..066e0cf166ee184f43dbea1d6125c1a3393a73bf 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -13,6 +13,7 @@ use Magento\CatalogImportExport\Model\Import\Product\RowValidatorInterface as Va
 use Magento\Framework\Model\ResourceModel\Db\TransactionManagerInterface;
 use Magento\Framework\Model\ResourceModel\Db\ObjectRelationProcessor;
 use Magento\Framework\Stdlib\DateTime;
+use Magento\ImportExport\Model\Import;
 use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingError;
 use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
 
@@ -36,11 +37,6 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
      */
     const ATTRIBUTE_DELETE_BUNCH = 1000;
 
-    /**
-     * default delimiter for several values in one cell
-     */
-    const DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR = ',';
-
     /**
      * Pseudo multi line separator in one cell.
      *
@@ -228,7 +224,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
         ValidatorInterface::ERROR_SUPER_PRODUCTS_SKU_NOT_FOUND => 'Product with specified super products SKU not found',
         ValidatorInterface::ERROR_MEDIA_DATA_INCOMPLETE => 'Media data is incomplete',
         ValidatorInterface::ERROR_EXCEEDED_MAX_LENGTH => 'Attribute %s exceeded max length',
-        ValidatorInterface::ERROR_INVALID_ATTRIBUTE_TYPE => 'Value for \'%s\' attribute contains incorrect value, acceptable values are in %s format',
+        ValidatorInterface::ERROR_INVALID_ATTRIBUTE_TYPE => 'Value for \'%s\' attribute contains incorrect value',
         ValidatorInterface::ERROR_ABSENT_REQUIRED_ATTRIBUTE => 'Attribute %s is required',
         ValidatorInterface::ERROR_INVALID_ATTRIBUTE_OPTION => 'Value for \'%s\' attribute contains incorrect value, see acceptable values on settings specified for Admin',
         ValidatorInterface::ERROR_DUPLICATE_UNIQUE_ATTRIBUTE => 'Duplicated unique attribute',
@@ -247,12 +243,11 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
     protected $_fieldsMap = [
         'image' => 'base_image',
         'image_label' => "base_image_label",
-        'image' => 'base_image',
-        'image_label' => 'base_image_label',
         'thumbnail' => 'thumbnail_image',
         'thumbnail_label' => 'thumbnail_image_label',
         self::COL_MEDIA_IMAGE => 'additional_images',
         '_media_image_label' => 'additional_image_labels',
+        '_media_is_disabled' => 'hide_from_product_page',
         Product::COL_STORE => 'store_view_code',
         Product::COL_ATTR_SET => 'attribute_set_code',
         Product::COL_TYPE => 'product_type',
@@ -332,7 +327,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
         self::COL_MEDIA_IMAGE,
         '_media_label',
         '_media_position',
-        '_media_is_disabled',
+        '_media_is_disabled'
     ];
 
     /**
@@ -711,26 +706,26 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
     }
 
     /**
-     * Retrieve instance of product custom options import entity
      *
-     * @return \Magento\CatalogImportExport\Model\Import\Product\Option
+     * Multiple value separator getter.
+     * @return string
      */
-    public function getOptionEntity()
+    public function getMultipleValueSeparator()
     {
-        return $this->_optionEntity;
+        if (!empty($this->_parameters[Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR])) {
+            return $this->_parameters[Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR];
+        }
+        return Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR;
     }
 
     /**
-     * Multiple value separator getter.
+     * Retrieve instance of product custom options import entity
      *
-     * @return string
+     * @return \Magento\CatalogImportExport\Model\Import\Product\Option
      */
-    public function getMultipleValueSeparator()
+    public function getOptionEntity()
     {
-        if (!empty($this->_parameters[\Magento\ImportExport\Model\Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR])) {
-            return $this->_parameters[\Magento\ImportExport\Model\Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR];
-        }
-        return self::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR;
+        return $this->_optionEntity;
     }
 
     /**
@@ -781,7 +776,10 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
      */
     public function deleteProductsForReplacement()
     {
-        $this->setParameters(array('behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_DELETE));
+        $this->setParameters(array_merge(
+            $this->getParameters(),
+            ['behavior' => Import::BEHAVIOR_DELETE]
+        ));
         $this->_deleteProducts();
 
         return $this;
@@ -837,9 +835,9 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
     protected function _importData()
     {
         $this->_validatedRows = null;
-        if (\Magento\ImportExport\Model\Import::BEHAVIOR_DELETE == $this->getBehavior()) {
+        if (Import::BEHAVIOR_DELETE == $this->getBehavior()) {
             $this->_deleteProducts();
-        } elseif (\Magento\ImportExport\Model\Import::BEHAVIOR_REPLACE == $this->getBehavior()) {
+        } elseif (Import::BEHAVIOR_REPLACE == $this->getBehavior()) {
             $this->_replaceFlag = true;
             $this->_replaceProducts();
         } else {
@@ -859,7 +857,10 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
         $this->deleteProductsForReplacement();
         $this->_oldSku = $this->skuProcessor->reloadOldSkus()->getOldSkus();
         $this->_validatedRows = null;
-        $this->setParameters(array('behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND));
+        $this->setParameters(array_merge(
+            $this->getParameters(),
+            ['behavior' => Import::BEHAVIOR_APPEND]
+        ));
         $this->_saveProductsData();
 
         return $this;
@@ -975,7 +976,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
 
         static $lastSku = null;
 
-        if (\Magento\ImportExport\Model\Import::BEHAVIOR_DELETE == $this->getBehavior()) {
+        if (Import::BEHAVIOR_DELETE == $this->getBehavior()) {
             return $rowData;
         }
 
@@ -1092,7 +1093,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
                     }
                 }
             }
-            if (\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND != $this->getBehavior() && $productIds) {
+            if (Import::BEHAVIOR_APPEND != $this->getBehavior() && $productIds) {
                 $this->_connection->delete(
                     $mainTable,
                     $this->_connection->quoteInto('product_id IN (?)', array_unique($productIds))
@@ -1186,7 +1187,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
                     $categoriesIn[] = ['product_id' => $productId, 'category_id' => $categoryId, 'position' => 1];
                 }
             }
-            if (\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND != $this->getBehavior()) {
+            if (Import::BEHAVIOR_APPEND != $this->getBehavior()) {
                 $this->_connection->delete(
                     $tableName,
                     $this->_connection->quoteInto('product_id IN (?)', $delProductId)
@@ -1241,39 +1242,43 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
     /**
      * Retrieving images from all columns and rows
      *
-     * @param $bunch
+     * @param array $bunch
      * @return array
      */
-    protected function _getAllBunchImages($bunch)
+    protected function getBunchImages($bunch)
     {
-        $allImagesFromBunch = [];
-        foreach ($bunch as $rowData) {
-            $rowData = $this->_customFieldsMapping($rowData);
-            foreach ($this->_imagesArrayKeys as $image) {
-                if (empty($rowData[$image])) {
+        $images = [];
+        foreach ($bunch as $row) {
+            $row = $this->_customFieldsMapping($row);
+            foreach ($this->_imagesArrayKeys as $imageColumn) {
+                if (empty($row[$imageColumn])) {
                     continue;
                 }
-                $dispersionPath =
-                    \Magento\Framework\File\Uploader::getDispretionPath($rowData[$image]);
-                $importImages = explode($this->getMultipleValueSeparator(), $rowData[$image]);
-                foreach ($importImages as $importImage) {
-                    $imageSting = mb_strtolower(
-                        $dispersionPath . '/' . preg_replace('/[^a-z0-9\._-]+/i', '', $importImage)
-                    );
-                    $allImagesFromBunch[$importImage] = $imageSting;
+
+                $rowImages = explode($this->getMultipleValueSeparator(), $row[$imageColumn]);
+                foreach ($rowImages as $rowImage) {
+                    $destinationPath = str_replace('\\', '/', $rowImage);
+                    $destinationPath = explode('/', $destinationPath);
+                    $destinationPath = array_pop($destinationPath);
+                    $destinationPath = preg_replace('/[^a-z0-9\._-]+/i', '', $destinationPath);
+
+                    $dispersion = \Magento\Framework\File\Uploader::getDispretionPath($destinationPath);
+                    $destinationPath = mb_strtolower($dispersion . '/' . $destinationPath);
+
+                    $images[$rowImage] = $destinationPath;
                 }
             }
         }
-        return $allImagesFromBunch;
+        return $images;
     }
 
     /**
      * Prepare all media files
      *
-     * @param $allImagesFromBunch
+     * @param array $images
      * @return array
      */
-    protected function _prepareAllMediaFiles($allImagesFromBunch)
+    protected function getExistingImages($images)
     {
         static $productMediaGalleryTableName = null;
         static $resource = null;
@@ -1292,22 +1297,54 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
             ['entity_id' => 'mgvte.entity_id']
         )->where(
             'mg.value IN(?)',
-            $allImagesFromBunch
+            $images
         );
         $allMedia = $this->_connection->fetchAll($select);
-        $result = array();
+        $result = [];
         foreach ($allMedia as $image) {
-            $result[$image['value']] = [];
+            if (!isset($result[$image['value']])){
+                $result[$image['value']] = [];
+            }
             foreach ($this->_oldSku as $sku => $oldSkuData) {
-                if ($oldSkuData['entity_id'] != $image['entity_id']) {
-                    continue;
+                if ($oldSkuData['entity_id'] == $image['entity_id']) {
+                    $result[$image['value']][$image['entity_id']] = $sku;
                 }
-                $result[$image['value']][] = $sku;
             }
         }
         return $result;
     }
 
+    /**
+     * @param array $rowData
+     * @return array
+     */
+    public function getImagesFromRow(array $rowData)
+    {
+        $images = [];
+        $labels = [];
+        foreach ($this->_imagesArrayKeys as $column) {
+            $images[$column] = [];
+            $labels[$column] = [];
+            if (!empty($rowData[$column])) {
+                $images[$column] = array_unique(
+                    explode($this->getMultipleValueSeparator(), $rowData[$column])
+                );
+            }
+
+            if (!empty($rowData[$column . '_label'])) {
+                $labels[$column] = explode($this->getMultipleValueSeparator(), $rowData[$column . '_label']);
+            }
+
+            if (count($labels[$column]) > count($images[$column])) {
+                $labels[$column] = array_slice($labels[$column], 0, count($images[$column]));
+            } elseif (count($labels[$column]) < count($images[$column])) {
+                $labels[$column] = array_pad($labels[$column], count($images[$column]), '');
+            }
+        }
+
+        return [$images, $labels];
+    }
+
     /**
      * Gather and save information about product entities.
      *
@@ -1319,8 +1356,6 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
      */
     protected function _saveProducts()
     {
-        /** @var $resource \Magento\CatalogImportExport\Model\Import\Proxy\Product\ResourceModel */
-        $resource = $this->_resourceFactory->create();
         $priceIsGlobal = $this->_catalogData->isPriceGlobal();
         $productLimit = null;
         $productsQty = null;
@@ -1333,11 +1368,11 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
             $this->categoriesCache = [];
             $tierPrices = [];
             $mediaGallery = [];
-            $uploadedGalleryFiles = [];
+            $uploadedImages = [];
             $previousType = null;
             $prevAttributeSet = null;
-            $allImagesFromBunch = $this->_getAllBunchImages($bunch);
-            $existingImages = $this->_prepareAllMediaFiles($allImagesFromBunch);
+            $bunchImages = $this->getBunchImages($bunch);
+            $existingImages = $this->getExistingImages($bunchImages);
 
             foreach ($bunch as $rowNum => $rowData) {
                 if (!$this->validateRow($rowData, $rowNum)) {
@@ -1387,7 +1422,9 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
                     }
                 }
 
-                $this->websitesCache[$rowSku] = [];
+                if (!array_key_exists($rowSku, $this->websitesCache)) {
+                    $this->websitesCache[$rowSku] = [];
+                }
                 // 2. Product-to-Website phase
                 if (!empty($rowData[self::COL_PRODUCT_WEBSITES])) {
                     $websiteCodes = explode($this->getMultipleValueSeparator(), $rowData[self::COL_PRODUCT_WEBSITES]);
@@ -1398,12 +1435,12 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
                 }
 
                 // 3. Categories phase
-                $categoriesString = empty($rowData[self::COL_CATEGORY]) ? '' : $rowData[self::COL_CATEGORY];
-                $this->categoriesCache[$rowSku] = [];
-                if (!empty($categoriesString)) {
-                    foreach ($this->categoryProcessor->upsertCategories($categoriesString) as $categoryId) {
-                        $this->categoriesCache[$rowSku][$categoryId] = true;
-                    }
+                if (!array_key_exists($rowSku, $this->categoriesCache)) {
+                    $this->categoriesCache[$rowSku] = [];
+                }
+                $categoryIds = $this->processRowCategories($rowData);
+                foreach ($categoryIds as $id) {
+                    $this->categoriesCache[$rowSku][$id] = true;
                 }
 
                 // 4.1. Tier prices phase
@@ -1424,95 +1461,52 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
                 }
 
                 // 5. Media gallery phase
-                $mediaGalleryImages = array();
-                $mediaGalleryLabels = array();
-                if (!empty($rowData[self::COL_MEDIA_IMAGE])) {
-                    $mediaGalleryImages =
-                        explode($this->getMultipleValueSeparator(), $rowData[self::COL_MEDIA_IMAGE]);
-                    if (isset($rowData['_media_image_label'])) {
-                        $mediaGalleryLabels =
-                            explode($this->getMultipleValueSeparator(), $rowData['_media_image_label']);
-                    } else {
-                        $mediaGalleryLabels = [];
-                    }
-                    if (count($mediaGalleryLabels) > count($mediaGalleryImages)) {
-                        $mediaGalleryLabels = array_slice($mediaGalleryLabels, 0, count($mediaGalleryImages));
-                    } elseif (count($mediaGalleryLabels) < count($mediaGalleryImages)) {
-                        $mediaGalleryLabels = array_pad($mediaGalleryLabels, count($mediaGalleryImages), '');
-                    }
-                }
-
-                foreach ($this->_imagesArrayKeys as $imageCol) {
-                    if (!empty($rowData[$imageCol])
-                        && ($imageCol != self::COL_MEDIA_IMAGE)
-                        && !in_array($rowData[$imageCol], $mediaGalleryImages)
-                    ) {
-                        $mediaGalleryImages[] = $rowData[$imageCol];
-                        if (isset($mediaGalleryLabels)) {
-                            $mediaGalleryLabels[] = isset($rowData[$imageCol . '_label'])
-                                ? $rowData[$imageCol . '_label']
-                                : '';
-                        } else {
-                            $mediaGalleryLabels[] = '';
-                        }
-                    }
+                $disabledImages = [];
+                list($rowImages, $rowLabels) = $this->getImagesFromRow($rowData);
+                if (isset($rowData['_media_is_disabled'])) {
+                    $disabledImages = array_flip(
+                        explode($this->getMultipleValueSeparator(), $rowData['_media_is_disabled'])
+                    );
                 }
-                $rowData[self::COL_MEDIA_IMAGE] = array();
-                foreach ($mediaGalleryImages as $mediaImage) {
-                    $imagePath = $allImagesFromBunch[$mediaImage];
-                    if (isset($existingImages[$imagePath]) && in_array($rowSku, $existingImages[$imagePath])) {
-                        if (!array_key_exists($mediaImage, $uploadedGalleryFiles)) {
-                            $uploadedFile = $this->_uploadMediaFiles(
-                                trim($mediaImage),
-                                true
-                            );
+                $rowData[self::COL_MEDIA_IMAGE] = [];
+                foreach ($rowImages as $column => $columnImages) {
+                    foreach ($columnImages as $position => $columnImage) {
+                        if (!isset($uploadedImages[$columnImage])) {
+                            $uploadedFile = $this->uploadMediaFiles(trim($columnImage), true);
                             if ($uploadedFile) {
-                                $uploadedGalleryFiles[$mediaImage] = $uploadedFile;
+                                $uploadedImages[$columnImage] = $uploadedFile;
                             } else {
-                                $this->addRowError(ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE, $rowNum, null, null, ProcessingError::ERROR_LEVEL_NOT_CRITICAL);
+                                $this->addRowError(
+                                    ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE,
+                                    $rowNum,
+                                    null,
+                                    null,
+                                    ProcessingError::ERROR_LEVEL_NOT_CRITICAL
+                                );
                             }
+                        } else {
+                            $uploadedFile = $uploadedImages[$columnImage];
                         }
-                    } elseif (!isset($existingImages[$imagePath])) {
-                        if (!array_key_exists($mediaImage, $uploadedGalleryFiles)) {
-                            $uploadedFile = $this->_uploadMediaFiles(
-                                trim($mediaImage),
-                                true
-                            );
-                            if ($uploadedFile) {
-                                $uploadedGalleryFiles[$mediaImage] = $uploadedFile;
-                                $newImagePath = $uploadedGalleryFiles[$mediaImage];
-                                $existingImages[$newImagePath][] = $rowSku;
-                            } else {
-                                $this->addRowError(ValidatorInterface::ERROR_MEDIA_URL_NOT_ACCESSIBLE, $rowNum, null, null, ProcessingError::ERROR_LEVEL_NOT_CRITICAL);
-                            }
+
+                        if ($uploadedFile && $column !== self::COL_MEDIA_IMAGE) {
+                            $rowData[$column] = $uploadedFile;
                         }
-                        if (isset($uploadedGalleryFiles[$mediaImage])) {
-                            $rowData[self::COL_MEDIA_IMAGE][] = $uploadedGalleryFiles[$mediaImage];
-                            if (!empty($rowData[self::COL_MEDIA_IMAGE]) && is_array($rowData[self::COL_MEDIA_IMAGE])) {
-                                $position = array_search($mediaImage, $mediaGalleryImages);
-                                foreach ($rowData[self::COL_MEDIA_IMAGE] as $mediaImage) {
-                                    $mediaGallery[$rowSku][] = [
-                                        'attribute_id' => $this->getMediaGalleryAttributeId(),
-                                        'label' => isset($mediaGalleryLabels[$position]) ? $mediaGalleryLabels[$position] : '',
-                                        'position' => $position,
-                                        'disabled' => '',
-                                        'value' => $mediaImage,
-                                    ];
-                                }
+
+                        $imageNotAssigned = !isset($existingImages[$uploadedFile])
+                            || !in_array($rowSku, $existingImages[$uploadedFile]);
+
+                        if ($uploadedFile && $imageNotAssigned) {
+                            if ($column == self::COL_MEDIA_IMAGE) {
+                                $rowData[$column][] = $uploadedFile;
                             }
-                        }
-                    }
-                    foreach ($this->_imagesArrayKeys as $imageCol) {
-                        if (empty($rowData[$imageCol]) || ($imageCol == self::COL_MEDIA_IMAGE)) {
-                            continue;
-                        }
-                        if (isset($existingImages[$imagePath])
-                            && !in_array($rowSku, $existingImages[$imagePath])
-                            && (($rowData[$imageCol] == $imagePath) || ($rowData[$imageCol] == $mediaImage))
-                        ) {
-                            unset($rowData[$imageCol]);
-                        } elseif (isset($uploadedGalleryFiles[$rowData[$imageCol]])) {
-                            $rowData[$imageCol] = $uploadedGalleryFiles[$rowData[$imageCol]];
+                            $mediaGallery[$rowSku][] = [
+                                'attribute_id' => $this->getMediaGalleryAttributeId(),
+                                'label' => isset($rowLabels[$column][$position]) ? $rowLabels[$column][$position] : '',
+                                'position' => $position + 1,
+                                'disabled' => isset($disabledImages[$columnImage]) ? '1' : '0',
+                                'value' => $uploadedFile,
+                            ];
+                            $existingImages[$uploadedFile][] = $rowSku;
                         }
                     }
                 }
@@ -1547,7 +1541,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
                         $this->taxClassProcessor->upsertTaxClass($rowData['tax_class_name'], $productTypeModel);
                 }
 
-                if ($this->getBehavior() == \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND ||
+                if ($this->getBehavior() == Import::BEHAVIOR_APPEND ||
                     empty($rowData[self::COL_SKU])
                 ) {
                     $rowData = $productTypeModel->clearEmptyData($rowData);
@@ -1592,7 +1586,9 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
                         }
                     }
                     foreach ($storeIds as $storeId) {
-                        $attributes[$attrTable][$rowSku][$attrId][$storeId] = $attrValue;
+                        if (!isset($attributes[$attrTable][$rowSku][$attrId][$storeId])) {
+                            $attributes[$attrTable][$rowSku][$attrId][$storeId] = $attrValue;
+                        }
                     }
                     // restore 'backend_model' to avoid 'default' setting
                     $attribute->setBackendModel($backModel);
@@ -1623,7 +1619,24 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
     }
 
     /**
-     * @param $productSku
+     * @param array $rowData
+     * @return array
+     */
+    protected function processRowCategories($rowData)
+    {
+        $categoriesString = empty($rowData[self::COL_CATEGORY]) ? '' : $rowData[self::COL_CATEGORY];
+        $categoryIds = [];
+        if (!empty($categoriesString)) {
+            $categoryIds = $this->categoryProcessor->upsertCategories(
+                $categoriesString,
+                $this->getMultipleValueSeparator()
+            );
+        }
+        return $categoryIds;
+    }
+
+    /**
+     * @param string $productSku
      * @return array
      */
     public function getProductWebsites($productSku)
@@ -1632,7 +1645,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
     }
 
     /**
-     * @param $productSku
+     * @param string $productSku
      * @return array
      */
     public function getProductCategories($productSku)
@@ -1641,7 +1654,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
     }
 
     /**
-     * @param $storeCode
+     * @param string $storeCode
      * @return array|int|null|string
      */
     public function getStoreIdByCode($storeCode)
@@ -1678,7 +1691,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
                     $tierPriceIn[] = $row;
                 }
             }
-            if (\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND != $this->getBehavior()) {
+            if (Import::BEHAVIOR_APPEND != $this->getBehavior()) {
                 $this->_connection->delete(
                     $tableName,
                     $this->_connection->quoteInto('entity_id IN (?)', $delProductId)
@@ -1709,8 +1722,8 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
 
             $DS = DIRECTORY_SEPARATOR;
 
-            if (!empty($this->_parameters[\Magento\ImportExport\Model\Import::FIELD_NAME_IMG_FILE_DIR])) {
-                $tmpPath = $this->_parameters[\Magento\ImportExport\Model\Import::FIELD_NAME_IMG_FILE_DIR];
+            if (!empty($this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR])) {
+                $tmpPath = $this->_parameters[Import::FIELD_NAME_IMG_FILE_DIR];
             } else {
                 $tmpPath = $dirAddon . $DS . $this->_mediaDirectory->getRelativePath('import');
             }
@@ -1733,6 +1746,15 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
         return $this->_fileUploader;
     }
 
+    /**
+     * @return Uploader
+     * @throws \Magento\Framework\Exception\LocalizedException
+     */
+    public function getUploader()
+    {
+        return $this->_getUploader();
+    }
+
     /**
      * Uploading files into the "catalog/product" media folder.
      * Return a new file name if the same file is already exists.
@@ -1740,7 +1762,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
      * @param string $fileName
      * @return string
      */
-    protected function _uploadMediaFiles($fileName, $renameFileOff = false)
+    protected function uploadMediaFiles($fileName, $renameFileOff = false)
     {
         try {
             $res = $this->_getUploader()->move($fileName, $renameFileOff);
@@ -1766,22 +1788,15 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
         static $mediaGalleryTableName = null;
         static $mediaValueTableName = null;
         static $mediaEntityToValueTableName = null;
-        static $productId = null;
-        if (!$mediaGalleryTableName) {
-            $mediaGalleryTableName = $this->_resourceFactory->create()->getTable(
-                'catalog_product_entity_media_gallery'
-            );
-        }
-        if (!$mediaValueTableName) {
-            $mediaValueTableName = $this->_resourceFactory->create()->getTable(
-                'catalog_product_entity_media_gallery_value'
-            );
-        }
-        if (!$mediaEntityToValueTableName) {
-            $mediaEntityToValueTableName = $this->_resourceFactory->create()->getTable(
-                'catalog_product_entity_media_gallery_value_to_entity'
-            );
-        }
+        $mediaGalleryTableName = $mediaGalleryTableName ?: $this->_resourceFactory->create()->getTable(
+            'catalog_product_entity_media_gallery'
+        );
+        $mediaValueTableName = $mediaValueTableName ?: $this->_resourceFactory->create()->getTable(
+            'catalog_product_entity_media_gallery_value'
+        );
+        $mediaEntityToValueTableName = $mediaEntityToValueTableName ?: $this->_resourceFactory->create()->getTable(
+            'catalog_product_entity_media_gallery_value_to_entity'
+        );
         $productIds = [];
         $imageNames = [];
         $multiInsertData = [];
@@ -1790,7 +1805,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
             $productId = $this->skuProcessor->getNewSku($productSku)['entity_id'];
             $productIds[] = $productId;
             $insertedGalleryImgs = [];
-            if (\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND != $this->getBehavior()) {
+            if (Import::BEHAVIOR_APPEND != $this->getBehavior()) {
                 $this->_connection->delete(
                     $mediaGalleryTableName,
                     $this->_connection->quoteInto('entity_id IN (?)', $productId)
@@ -1802,26 +1817,32 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
                         'attribute_id' => $insertValue['attribute_id'],
                         'value' => $insertValue['value'],
                     ];
-                    $valueToProductId[$insertValue['value']] = $productId;
+                    $valueToProductId[$insertValue['value']][] = $productId;
                     $imageNames[] = $insertValue['value'];
                     $multiInsertData[] = $valueArr;
                     $insertedGalleryImgs[] = $insertValue['value'];
                 }
             }
         }
-        $this->_connection->insertOnDuplicate($mediaGalleryTableName, $multiInsertData, []);
-        $multiInsertData = [];
-        $dataForSkinnyTable = [];
-        $newMediaValues = $this->_connection->fetchAssoc(
+        $oldMediaValues = $this->_connection->fetchAssoc(
             $this->_connection->select()->from($mediaGalleryTableName, ['value_id', 'value'])
                 ->where('value IN (?)', $imageNames)
         );
+        $this->_connection->insertOnDuplicate($mediaGalleryTableName, $multiInsertData, []);
+        $multiInsertData = [];
+        $newMediaSelect = $this->_connection->select()->from($mediaGalleryTableName, ['value_id', 'value'])
+            ->where('value IN (?)', $imageNames);
+        if (array_keys($oldMediaValues)) {
+            $newMediaSelect->where('value_id NOT IN (?)', array_keys($oldMediaValues));
+        }
+
+        $newMediaValues = $this->_connection->fetchAssoc($newMediaSelect);
         foreach ($mediaGalleryData as $productSku => $mediaGalleryRows) {
             foreach ($mediaGalleryRows as $insertValue) {
                 foreach ($newMediaValues as $value_id => $values) {
                     if ($values['value'] == $insertValue['value']) {
                         $insertValue['value_id'] = $value_id;
-                        $insertValue['entity_id'] = $valueToProductId[$values['value']];
+                        $insertValue['entity_id'] = array_shift($valueToProductId[$values['value']]);
                         unset($newMediaValues[$value_id]);
                         break;
                     }
@@ -1844,7 +1865,11 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
             }
         }
         try {
-            $this->_connection->insertOnDuplicate($mediaValueTableName, $multiInsertData, ['value_id']);
+            $this->_connection->insertOnDuplicate(
+                $mediaValueTableName,
+                $multiInsertData,
+                ['value_id', 'store_id', 'entity_id', 'label', 'position', 'disabled']
+            );
             $this->_connection->insertOnDuplicate($mediaEntityToValueTableName, $dataForSkinnyTable, ['value_id']);
         } catch (\Exception $e) {
             $this->_connection->delete(
@@ -1881,7 +1906,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
                     $websitesData[] = ['product_id' => $productId, 'website_id' => $websiteId];
                 }
             }
-            if (\Magento\ImportExport\Model\Import::BEHAVIOR_APPEND != $this->getBehavior()) {
+            if (Import::BEHAVIOR_APPEND != $this->getBehavior()) {
                 $this->_connection->delete(
                     $tableName,
                     $this->_connection->quoteInto('product_id IN (?)', $delProductId)
@@ -1945,12 +1970,14 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
                 } else {
                     $row['qty'] = 0;
                 }
-                $stockData[] = $row;
+                if (!isset($stockData[$rowData[self::COL_SKU]])) {
+                    $stockData[$rowData[self::COL_SKU]] = $row;
+                }
             }
 
             // Insert rows
             if (!empty($stockData)) {
-                $this->_connection->insertOnDuplicate($entityTable, $stockData);
+                $this->_connection->insertOnDuplicate($entityTable, array_values($stockData));
             }
 
             if ($productIdsToReindex) {
@@ -2084,7 +2111,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
         $rowScope = $this->getRowScope($rowData);
 
         // BEHAVIOR_DELETE use specific validation logic
-        if (\Magento\ImportExport\Model\Import::BEHAVIOR_DELETE == $this->getBehavior()) {
+        if (Import::BEHAVIOR_DELETE == $this->getBehavior()) {
             if (self::SCOPE_DEFAULT == $rowScope && !isset($this->_oldSku[$rowData[self::COL_SKU]])) {
                 $this->addRowError(ValidatorInterface::ERROR_SKU_NOT_FOUND_FOR_DELETE, $rowNum);
                 return false;
@@ -2233,7 +2260,7 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
     private function _customFieldsMapping($rowData)
     {
         foreach ($this->_fieldsMap as $systemFieldName => $fileFieldName) {
-            if (isset($rowData[$fileFieldName])) {
+            if (array_key_exists($fileFieldName, $rowData)) {
                 $rowData[$systemFieldName] = $rowData[$fileFieldName];
             }
         }
@@ -2241,10 +2268,12 @@ class Product extends \Magento\ImportExport\Model\Import\Entity\AbstractEntity
         $rowData = $this->_parseAdditionalAttributes($rowData);
 
         $rowData = $this->_setStockUseConfigFieldsValues($rowData);
-        if (isset($rowData['status'])) {
-            if (($rowData['status'] == \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED) || $rowData['status'] == 'yes') {
+        if (array_key_exists('status', $rowData)
+            && $rowData['status'] != \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED
+        ) {
+            if ($rowData['status'] == 'yes') {
                 $rowData['status'] = \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED;
-            } else {
+            } elseif (!empty($rowData['status']) || $this->getRowScope($rowData) == self::SCOPE_DEFAULT) {
                 $rowData['status'] = \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED;
             }
         }
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php
index 6d4ba7ee1833580f4dc503ac782566516a68f9bc..c442b1957954d29afb380354c947060b941cd742 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/CategoryProcessor.php
@@ -7,11 +7,6 @@ namespace Magento\CatalogImportExport\Model\Import\Product;
 
 class CategoryProcessor
 {
-    /**
-     * Delimiter in import file between categories.
-     */
-    const DELIMITER_CATEGORIES = '|';
-
     /**
      * Delimiter in category path.
      */
@@ -144,13 +139,14 @@ class CategoryProcessor
      * Returns IDs of categories by string path creating nonexistent ones.
      *
      * @param string $categoriesString
+     * @param string $categoriesSeparator
      *
      * @return array
      */
-    public function upsertCategories($categoriesString)
+    public function upsertCategories($categoriesString, $categoriesSeparator)
     {
         $categoriesIds = [];
-        $categories = explode(self::DELIMITER_CATEGORIES, $categoriesString);
+        $categories = explode($categoriesSeparator, $categoriesString);
 
         foreach ($categories as $category) {
             $categoriesIds[] = $this->upsertCategory($category);
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php
index 89230e1da346389b6618d4db55ba1b48c28a2182..cee5e53e97978da309bf01c7605b575d961a6fb1 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/Validator.php
@@ -93,6 +93,26 @@ class Validator extends AbstractValidator implements RowValidatorInterface
         return $valid;
     }
 
+    /**
+     * @param string $attrCode
+     * @param array $attributeParams
+     * @param array $rowData
+     * @return bool
+     */
+    public function isRequiredAttributeValid($attrCode, array $attributeParams, array $rowData)
+    {
+        $doCheck = false;
+        if ($attrCode == Product::COL_SKU) {
+            $doCheck = true;
+        } elseif ($attributeParams['is_required'] && $this->getRowScope($rowData) == Product::SCOPE_DEFAULT
+            && $this->context->getBehavior() != \Magento\ImportExport\Model\Import::BEHAVIOR_DELETE
+        ) {
+            $doCheck = true;
+        }
+
+        return $doCheck ? isset($rowData[$attrCode]) && strlen(trim($rowData[$attrCode])) : true;
+    }
+
     /**
      * @param string $attrCode
      * @param array $attrParams
@@ -107,25 +127,20 @@ class Validator extends AbstractValidator implements RowValidatorInterface
         if (!empty($attrParams['apply_to']) && !in_array($rowData['product_type'], $attrParams['apply_to'])) {
             return true;
         }
-        if ($attrCode == Product::COL_SKU || $attrParams['is_required']
-            && ($this->context->getBehavior() == \Magento\ImportExport\Model\Import::BEHAVIOR_REPLACE
-                || ($this->context->getBehavior() == \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND
-                    && !isset($this->context->getOldSku()[$rowData[$attrCode]])))
-        ) {
-            if (!isset($rowData[$attrCode]) || !strlen(trim($rowData[$attrCode]))) {
-                $valid = false;
-                $this->_addMessages(
-                    [
-                        sprintf(
-                            $this->context->retrieveMessageTemplate(
-                                RowValidatorInterface::ERROR_VALUE_IS_REQUIRED
-                            ),
-                            $attrCode
-                        )
-                    ]
-                );
-                return $valid;
-            }
+
+        if (!$this->isRequiredAttributeValid($attrCode, $attrParams, $rowData)) {
+            $valid = false;
+            $this->_addMessages(
+                [
+                    sprintf(
+                        $this->context->retrieveMessageTemplate(
+                            RowValidatorInterface::ERROR_VALUE_IS_REQUIRED
+                        ),
+                        $attrCode
+                    )
+                ]
+            );
+            return $valid;
         }
 
         if (!strlen(trim($rowData[$attrCode]))) {
@@ -146,7 +161,7 @@ class Validator extends AbstractValidator implements RowValidatorInterface
                 $values = explode(Product::PSEUDO_MULTI_LINE_SEPARATOR, $rowData[$attrCode]);
                 $valid = true;
                 foreach ($values as $value) {
-                    $valid = $valid || isset($attrParams['options'][strtolower($value)]);
+                    $valid = $valid && isset($attrParams['options'][strtolower($value)]);
                 }
                 if (!$valid) {
                     $this->_addMessages(
@@ -227,6 +242,20 @@ class Validator extends AbstractValidator implements RowValidatorInterface
         return $returnValue;
     }
 
+    /**
+     * Obtain scope of the row from row data.
+     *
+     * @param array $rowData
+     * @return int
+     */
+    public function getRowScope(array $rowData)
+    {
+        if (empty($rowData[Product::COL_STORE])) {
+            return Product::SCOPE_DEFAULT;
+        }
+        return Product::SCOPE_STORE;
+    }
+
     /**
      * @param \Magento\CatalogImportExport\Model\Import\Product $context
      * @return $this
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php
index dc0263e99d4f093bb65634c7f3eaeb9d12447caa..f22d910ccd45e6d386e7483a6b1373b75d9b4af7 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Uploader.php
@@ -223,7 +223,7 @@ class Uploader extends \Magento\MediaStorage\Model\File\Uploader
                 && method_exists($params['object'], $params['method'])
                 && is_callable([$params['object'], $params['method']])
             ) {
-                $params['object']->{$params['method']}($filePath);
+                $params['object']->{$params['method']}($this->_directory->getAbsolutePath($filePath));
             }
         }
     }
diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/CategoryProcessorTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/CategoryProcessorTest.php
index c6b5fd5570d265f89e344fa6eebf7cc1a0e47f20..3e281ba84afd0a769c29ef813e848250dc61c1bc 100644
--- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/CategoryProcessorTest.php
+++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/CategoryProcessorTest.php
@@ -110,7 +110,8 @@ class CategoryProcessorTest extends \PHPUnit_Framework_TestCase
 
     public function testUpsertCategories()
     {
-        $categoryIds = $this->categoryProcessor->upsertCategories(self::CHILD_CATEGORY_NAME);
+        $categoriesSeparator = ',';
+        $categoryIds = $this->categoryProcessor->upsertCategories(self::CHILD_CATEGORY_NAME, $categoriesSeparator);
         $this->assertArrayHasKey(self::CHILD_CATEGORY_ID, array_flip($categoryIds));
     }
 
diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php
index 3541ccbb99338202ea5c1fdd73f1dea54d63bfab..b7fd1e3604b675d49510a4cdd94696e0e15c1904 100644
--- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php
+++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/ValidatorTest.php
@@ -5,8 +5,10 @@
  */
 namespace Magento\CatalogImportExport\Test\Unit\Model\Import\Product;
 
+use Magento\CatalogImportExport\Model\Import\Product;
 use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
 use Magento\CatalogImportExport\Model\Import\Product\Validator;
+use Magento\ImportExport\Model\Import;
 
 class ValidatorTest extends \PHPUnit_Framework_TestCase
 {
@@ -46,7 +48,7 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase
             false
         );
         $this->context->expects($this->any())->method('retrieveProductTypeByName')->willReturn($entityTypeModel);
-        $this->context->expects($this->any())->method('retrieveMessageTemplate')->willReturn('');
+        $this->context->expects($this->any())->method('retrieveMessageTemplate')->willReturn('error message');
 
         $this->validatorOne = $this->getMock(
             'Magento\CatalogImportExport\Model\Import\Product\Validator\Media',
@@ -72,23 +74,123 @@ class ValidatorTest extends \PHPUnit_Framework_TestCase
         $this->validator->init($this->context);
     }
 
-    public function testIsBooleanAttributeValid()
+    /**
+     * @param string $behavior
+     * @param array $attrParams
+     * @param array $rowData
+     * @param bool $isValid
+     * @param string $attrCode
+     * @dataProvider attributeValidationProvider
+     */
+    public function testAttributeValidation($behavior, $attrParams, $rowData, $isValid, $attrCode = 'attribute_code')
     {
-        $this->context->expects($this->any())->method('getBehavior')
-            ->willReturn(\Magento\ImportExport\Model\Import::BEHAVIOR_REPLACE);
+        $this->context->expects($this->any())->method('getBehavior')->willReturn($behavior);
         $result = $this->validator->isAttributeValid(
-            'boolean_attribute',
+            $attrCode,
+            $attrParams,
+            $rowData
+        );
+        $this->assertEquals($isValid, $result);
+        if (!$isValid) {
+            $this->assertTrue($this->validator->hasMessages());
+        }
+    }
+
+    /**
+     * @return array
+     */
+    public function attributeValidationProvider()
+    {
+        return [
+            [
+                'any_behavior',
+                ['apply_to' => ['expected_product_type']],
+                ['product_type' => 'not_expected_product_type'],
+                true, //validation skipped in such case, so it means attribute is valid
+                'any_attibute_code',
+            ],
+            [
+                'any_behavior',
+                [],
+                ['product_type' => 'any'],
+                false,
+                Product::COL_SKU
+            ],
+            [
+                'any_behavior',
+                ['type' => 'varchar'],
+                ['product_type' => 'any', 'sku' => 'sku_value'],
+                true,
+                Product::COL_SKU
+            ],
+            [
+                'any_behavior',
+                ['is_required' => true, 'type' => 'varchar'],
+                ['product_type' => 'any', 'attribute_code' => 'value'],
+                true
+            ],
+            [
+                Import::BEHAVIOR_APPEND,
+                ['is_required' => true, 'type' => 'varchar'],
+                ['product_type' => 'any', 'attribute_code' => ''],
+                false
+            ],
+            [
+                Import::BEHAVIOR_APPEND,
+                ['is_required' => true, 'type' => 'int'],
+                ['product_type' => 'any', 'attribute_code' => 'not-int'],
+                false
+            ],
+            [
+                Import::BEHAVIOR_APPEND,
+                ['is_required' => true, 'type' => 'int'],
+                ['product_type' => 'any', 'attribute_code' => '1'],
+                true
+            ],
+            [
+                Import::BEHAVIOR_APPEND,
+                ['is_required' => true, 'type' => 'boolean', 'options' => ['yes' => 0, 'no' => 1]],
+                ['product_type' => 'any', 'attribute_code' => 'some-value'],
+                false
+            ],
+            [
+                Import::BEHAVIOR_APPEND,
+                ['is_required' => true, 'type' => 'boolean', 'options' => ['yes' => 0, 'no' => 1]],
+                ['product_type' => 'any', 'attribute_code' => 'Yes'],
+                true
+            ],
             [
-                'type' => 'boolean',
-                'apply_to' => ['simple'],
-                'is_required' => false
+                Import::BEHAVIOR_APPEND,
+                ['is_required' => true, 'type' => 'multiselect', 'options' => ['option 1' => 0, 'option 2' => 1]],
+                ['product_type' => 'any', 'attribute_code' => 'Option 1|Option 2|Option 3'],
+                false
             ],
             [
-                'product_type' => 'simple',
-                'boolean_attribute' => 'Yes'
+                Import::BEHAVIOR_APPEND,
+                ['is_required' => true, 'type' => 'multiselect', 'options' => ['option 1' => 0, 'option 2' => 1]],
+                ['product_type' => 'any', 'attribute_code' => 'Option 1|Option 2'],
+                true
+            ],
+            [
+                Import::BEHAVIOR_APPEND,
+                ['is_required' => true, 'type' => 'datetime'],
+                ['product_type' => 'any', 'attribute_code' => '1/1/15 12am'],
+                true
+            ],
+            [
+                Import::BEHAVIOR_APPEND,
+                ['is_required' => true, 'type' => 'datetime'],
+                ['product_type' => 'any', 'attribute_code' => '1/1/15 13am'],
+                false
+            ],
+            [
+                Import::BEHAVIOR_APPEND,
+                ['is_required' => true, 'type' => 'varchar', 'is_unique' => true],
+                ['product_type' => 'any', 'unique_attribute' => 'unique-value', Product::COL_SKU => 'sku-0'],
+                true,
+                'unique_attribute'
             ]
-        );
-        $this->assertTrue($result);
+        ];
     }
 
     public function testIsValidCorrect()
diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php
index d5fbfaf8c9e8c693c690a26a6985b5525a166508..78580aee7c92428e19dcb8907a28e4f91cc32258 100644
--- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php
+++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/ProductTest.php
@@ -8,6 +8,7 @@ namespace Magento\CatalogImportExport\Test\Unit\Model\Import;
 use Magento\CatalogImportExport\Model\Import\Product;
 use Magento\Framework\App\Filesystem\DirectoryList;
 use Magento\Framework\Stdlib\DateTime;
+use Magento\ImportExport\Model\Import;
 
 /**
  * Class ProductTest
@@ -567,7 +568,7 @@ class ProductTest extends \Magento\ImportExport\Test\Unit\Model\Import\AbstractI
     {
         $this->setPropertyValue($this->importProduct, '_parameters', null);
         $this->assertEquals(
-            \Magento\CatalogImportExport\Model\Import\Product::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR,
+            Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR,
             $this->importProduct->getMultipleValueSeparator()
         );
     }
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
index fa7a1b0b386988fac65e504512e8843c3e52d14c..855d800fcfdcbb9c6a72f13599560a591d9ea7bc 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
@@ -190,6 +190,7 @@ class AfterImportDataObserver implements ObserverInterface
                 $product->setData($field, $rowData[$field]);
             }
         }
+
         $this->categoryCache[$rowData['entity_id']] = $this->import->getProductCategories($rowData['sku']);
         $this->websiteCache[$rowData['entity_id']] = $this->import->getProductWebsites($rowData['sku']);
         foreach ($this->websiteCache[$rowData['entity_id']] as $websiteId) {
@@ -197,11 +198,9 @@ class AfterImportDataObserver implements ObserverInterface
                 $this->websitesToStoreIds[$websiteId] = $this->storeManager->getWebsite($websiteId)->getStoreIds();
             }
         }
-        if (!empty($rowData[ImportProduct::COL_STORE])
-            && ($storeId = $this->import->getStoreIdByCode($rowData[ImportProduct::COL_STORE]))
-        ) {
-            $product->setStoreId($storeId);
-        }
+
+        $this->setStoreToProduct($product, $rowData);
+
         if ($this->isGlobalScope($product->getStoreId())) {
             $this->populateGlobalProduct($product);
         } else {
@@ -210,10 +209,26 @@ class AfterImportDataObserver implements ObserverInterface
         return $this;
     }
 
+    /**
+     * @param \Magento\Catalog\Model\Product $product
+     * @param array $rowData
+     * @return void
+     */
+    protected function setStoreToProduct(\Magento\Catalog\Model\Product $product, array $rowData)
+    {
+        if (!empty($rowData[ImportProduct::COL_STORE])
+            && ($storeId = $this->import->getStoreIdByCode($rowData[ImportProduct::COL_STORE]))
+        ) {
+            $product->setStoreId($storeId);
+        } elseif (!$product->hasData(\Magento\Catalog\Api\Data\ProductInterface::STORE_ID)) {
+            $product->setStoreId(Store::DEFAULT_STORE_ID);
+        }
+    }
+
     /**
      * Add product to import
      *
-     * @param \Magento\CatalogImportExport\Model\Import\Product $product
+     * @param \Magento\Catalog\Model\Product $product
      * @param string $storeId
      * @return $this
      */
@@ -221,15 +236,15 @@ class AfterImportDataObserver implements ObserverInterface
     {
         if (!isset($this->products[$product->getId()])) {
             $this->products[$product->getId()] = [];
-            $this->products[$product->getId()][$storeId] = $product;
         }
+        $this->products[$product->getId()][$storeId] = $product;
         return $this;
     }
 
     /**
      * Populate global product
      *
-     * @param \Magento\CatalogImportExport\Model\Import\Product $product
+     * @param \Magento\Catalog\Model\Product $product
      * @return $this
      */
     protected function populateGlobalProduct($product)
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
index 5c50b55ecc926f96e3d127cb46d73a1219a3a1a7..fc6636a16bd3fccdf34c8f5337d898f5dc6af465 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
@@ -379,8 +379,7 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
 
     /**
      * Test for afterImportData()
-     * Covers afterImportData() + protected methods used inside except related to generateUrls() ones.
-     * generateUrls will be covered separately.
+     * Covers afterImportData() + protected methods used inside
      *
      * @covers \Magento\CatalogUrlRewrite\Observer\AfterImportDataObserver::afterImportData
      * @covers \Magento\CatalogUrlRewrite\Observer\AfterImportDataObserver::_populateForUrlGeneration
@@ -392,7 +391,7 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
      */
     public function testAfterImportData()
     {
-        $newSku = ['entity_id' => 'value'];
+        $newSku = [['entity_id' => 'value'], ['entity_id' => 'value3']];
         $websiteId = 'websiteId value';
         $productsCount = count($this->products);
         $websiteMock = $this->getMock(
@@ -421,21 +420,24 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
                 [$this->products[0][ImportProduct::COL_SKU]],
                 [$this->products[1][ImportProduct::COL_SKU]]
             )
-            ->willReturn($newSku);
+            ->will($this->onConsecutiveCalls($newSku[0], $newSku[1]));
         $this->importProduct
             ->expects($this->exactly($productsCount))
             ->method('getProductCategories')
             ->withConsecutive(
                 [$this->products[0][ImportProduct::COL_SKU]],
                 [$this->products[1][ImportProduct::COL_SKU]]
-            );
+            )->willReturn([]);
         $getProductWebsitesCallsCount = $productsCount*2;
         $this->importProduct
             ->expects($this->exactly($getProductWebsitesCallsCount))
             ->method('getProductWebsites')
-            ->willReturn([
-                $newSku['entity_id'] => $websiteId,
-            ]);
+            ->willReturnOnConsecutiveCalls(
+                [$newSku[0]['entity_id'] => $websiteId],
+                [$newSku[0]['entity_id'] => $websiteId],
+                [$newSku[1]['entity_id'] => $websiteId],
+                [$newSku[1]['entity_id'] => $websiteId]
+            );
         $map = [
             [$this->products[0][ImportProduct::COL_STORE], $this->products[0][ImportProduct::COL_STORE]],
             [$this->products[1][ImportProduct::COL_STORE], $this->products[1][ImportProduct::COL_STORE]]
@@ -460,11 +462,20 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
         $product
             ->expects($this->exactly($productsCount))
             ->method('setId')
-            ->with($newSku['entity_id']);
+            ->withConsecutive([$newSku[0]['entity_id']], [$newSku[1]['entity_id']]);
         $product
             ->expects($this->any())
             ->method('getId')
-            ->willReturn($newSku['entity_id']);
+            ->willReturnOnConsecutiveCalls(
+                $newSku[0]['entity_id'],
+                $newSku[0]['entity_id'],
+                $newSku[0]['entity_id'],
+                $newSku[0]['entity_id'],
+                $newSku[0]['entity_id'],
+                $newSku[1]['entity_id'],
+                $newSku[1]['entity_id'],
+                $newSku[1]['entity_id']
+            );
         $product
             ->expects($this->exactly($productsCount))
             ->method('getSku')
@@ -480,9 +491,12 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
                 $this->products[1][ImportProduct::COL_STORE]
             ));
         $product
-            ->expects($this->once())
+            ->expects($this->exactly($productsCount))
             ->method('setStoreId')
-            ->with($this->products[1][ImportProduct::COL_STORE]);
+            ->withConsecutive(
+                [$this->products[0][ImportProduct::COL_STORE]],
+                [$this->products[1][ImportProduct::COL_STORE]]
+            );
         $this->catalogProductFactory
             ->expects($this->exactly($productsCount))
             ->method('create')
@@ -497,32 +511,55 @@ class AfterImportDataObserverTest extends \PHPUnit_Framework_TestCase
                 ],
                 [
                     ' AND entity_id = ?)',
-                    $newSku['entity_id'],
+                    $newSku[0]['entity_id'],
+                ],
+                [
+                    '(store_id = ?',
+                    $storeIds[0],
+                ],
+                [
+                    ' AND entity_id = ?)',
+                    $newSku[1]['entity_id'],
                 ]
             );
+        $this->connection
+            ->expects($this->once())
+            ->method('fetchAll')
+            ->willReturn([]);
+        $this->select->expects($this->any())->method('from')->willReturnSelf();
+        $this->select->expects($this->any())->method('where')->willReturnSelf();
+
+        $this->urlFinder->expects($this->any())->method('findAllByData')->willReturn([]);
+
+        $this->productUrlPathGenerator->expects($this->any())->method('getUrlPathWithSuffix')
+            ->willReturn('urlPathWithSuffix');
+        $this->productUrlPathGenerator->expects($this->any())->method('getUrlPath')
+            ->willReturn('urlPath');
+        $this->productUrlPathGenerator->expects($this->any())->method('getCanonicalUrlPath')
+            ->willReturn('canonicalUrlPath');
+
+        $this->urlRewrite->expects($this->any())->method('setStoreId')->willReturnSelf();
+        $this->urlRewrite->expects($this->any())->method('setEntityId')->willReturnSelf();
+        $this->urlRewrite->expects($this->any())->method('setEntityType')->willReturnSelf();
+        $this->urlRewrite->expects($this->any())->method('setRequestPath')->willReturnSelf();
+        $this->urlRewrite->expects($this->any())->method('setTargetPath')->willReturnSelf();
+        $this->urlRewrite->expects($this->any())->method('getTargetPath')->willReturn('targetPath');
+        $this->urlRewrite->expects($this->any())->method('getStoreId')
+            ->willReturnOnConsecutiveCalls(0, 'not global');
+
+        $this->urlRewriteFactory->expects($this->any())->method('create')->willReturn($this->urlRewrite);
 
         $productUrls = [
-            'url 1',
-            'url 2',
+            'targetPath-0' => $this->urlRewrite,
+            'targetPath-not global' => $this->urlRewrite
         ];
 
-        $importMock = $this->getImportMock([
-            'generateUrls',
-            'canonicalUrlRewriteGenerate',
-            'categoriesUrlRewriteGenerate',
-            'currentUrlRewritesRegenerate',
-            'cleanOverriddenUrlKey',
-        ]);
-        $importMock
-            ->expects($this->once())
-            ->method('generateUrls')
-            ->willReturn($productUrls);
         $this->urlPersist
             ->expects($this->once())
             ->method('replace')
             ->with($productUrls);
 
-        $importMock->execute($this->observer);
+        $this->import->execute($this->observer);
     }
 
     /**
diff --git a/app/code/Magento/ConfigurableImportExport/Model/Export/RowCustomizer.php b/app/code/Magento/ConfigurableImportExport/Model/Export/RowCustomizer.php
index 4405feba18426579b23073dcd3e89bfcb1851273..2dcde2898de2d426f6605dc2dd128b00bf72b538 100644
--- a/app/code/Magento/ConfigurableImportExport/Model/Export/RowCustomizer.php
+++ b/app/code/Magento/ConfigurableImportExport/Model/Export/RowCustomizer.php
@@ -7,6 +7,7 @@ namespace Magento\ConfigurableImportExport\Model\Export;
 
 use Magento\CatalogImportExport\Model\Export\RowCustomizerInterface;
 use \Magento\CatalogImportExport\Model\Import\Product as ImportProduct;
+use Magento\ImportExport\Model\Import;
 
 class RowCustomizer implements RowCustomizerInterface
 {
@@ -19,12 +20,13 @@ class RowCustomizer implements RowCustomizerInterface
      * Prepare configurable data for export
      *
      * @param \Magento\Catalog\Model\ResourceModel\Product\Collection $collection
-     * @param int $productIds
+     * @param int[] $productIds
      * @return void
      */
     public function prepareData($collection, $productIds)
     {
-        $collection->addAttributeToFilter(
+        $productCollection = clone $collection;
+        $productCollection->addAttributeToFilter(
             'entity_id',
             ['in' => $productIds]
         )->addAttributeToFilter(
@@ -32,7 +34,7 @@ class RowCustomizer implements RowCustomizerInterface
             ['eq' => \Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE]
         );
 
-        while ($product = $collection->fetchItem()) {
+        while ($product = $productCollection->fetchItem()) {
             $productAttributesOptions = $product->getTypeInstance()->getConfigurableOptions($product);
 
             foreach ($productAttributesOptions as $productAttributeOption) {
@@ -51,11 +53,11 @@ class RowCustomizer implements RowCustomizerInterface
 
                 foreach ($variations as $sku => $values) {
                     $variations[$sku] =
-                        'sku=' . $sku . ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
-                        . implode(ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $values);
+                        'sku=' . $sku . Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
+                        . implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $values);
                 }
                 $variations = implode(ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR, $variations);
-                $variationsLabels = implode(ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $variationsLabels);
+                $variationsLabels = implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $variationsLabels);
 
                 $this->configurableData[$product->getId()] = [
                     'configurable_variations' => $variations,
diff --git a/app/code/Magento/ConfigurableImportExport/Test/Unit/Model/Export/RowCustomizerTest.php b/app/code/Magento/ConfigurableImportExport/Test/Unit/Model/Export/RowCustomizerTest.php
index 65732d3df1df550c4b987a87b4005efd3a5bf223..b067e19c381252aaf7f3fd5eb1c9f79497f1d96f 100644
--- a/app/code/Magento/ConfigurableImportExport/Test/Unit/Model/Export/RowCustomizerTest.php
+++ b/app/code/Magento/ConfigurableImportExport/Test/Unit/Model/Export/RowCustomizerTest.php
@@ -7,6 +7,7 @@
 namespace Magento\ConfigurableImportExport\Test\Unit\Model\Export;
 
 use \Magento\CatalogImportExport\Model\Import\Product as ImportProduct;
+use Magento\ImportExport\Model\Import;
 
 class RowCustomizerTest extends \PHPUnit_Framework_TestCase
 {
@@ -256,17 +257,17 @@ class RowCustomizerTest extends \PHPUnit_Framework_TestCase
         return [
             $this->initiatedProductId => [
                 'configurable_variations' => implode(ImportProduct::PSEUDO_MULTI_LINE_SEPARATOR, [
-                    '_sku_' => 'sku=_sku_'  . ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
-                        . implode(ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, [
+                    '_sku_' => 'sku=_sku_'  . Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
+                        . implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, [
                             'code_of_attribute=Option Title',
                             'code_of_attribute=Option Title',
                         ]),
-                    '_sku_2' => 'sku=_sku_2'  . ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
-                        . implode(ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, [
+                    '_sku_2' => 'sku=_sku_2' . Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR
+                        . implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, [
                             'code_of_attribute_2=Option Title 2',
                         ])
                 ]),
-                'configurable_variation_labels' => implode(ImportProduct::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, [
+                'configurable_variation_labels' => implode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, [
                     'code_of_attribute' => 'code_of_attribute=Super attribute label',
                     'code_of_attribute_2' => 'code_of_attribute_2=Super attribute label 2',
                 ]),
@@ -291,6 +292,7 @@ class RowCustomizerTest extends \PHPUnit_Framework_TestCase
     /**
      * @param $object
      * @param $property
+     * @return mixed
      */
     protected function getPropertyValue(&$object, $property)
     {
diff --git a/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped.php b/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped.php
index 6bccd71831032751861b547a66224fe88c8cf27f..3185df16bdb3bc95e55f46361d911a07e4f7bde7 100644
--- a/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped.php
+++ b/app/code/Magento/GroupedImportExport/Model/Import/Product/Type/Grouped.php
@@ -8,6 +8,7 @@
 namespace Magento\GroupedImportExport\Model\Import\Product\Type;
 
 use Magento\CatalogImportExport\Model\Import\Product;
+use Magento\ImportExport\Model\Import;
 
 class Grouped extends \Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType
 {
@@ -76,7 +77,7 @@ class Grouped extends \Magento\CatalogImportExport\Model\Import\Product\Type\Abs
                 if (!$this->_entityModel->isRowAllowedToImport($rowData, $rowNum) || empty($associatedSkusQty)) {
                     continue;
                 }
-                $associatedSkusAndQtyPairs = explode(Product::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $associatedSkusQty);
+                $associatedSkusAndQtyPairs = explode(Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR, $associatedSkusQty);
                 $position = 0;
                 foreach ($associatedSkusAndQtyPairs as $associatedSkuAndQty) {
                     ++$position;
diff --git a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php
index 71f342b5b585770f34adcd495b10e90090280213..1a8f446b1de5d0c355c2c87ec3d5bc3bddc39869 100644
--- a/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php
+++ b/app/code/Magento/ImportExport/Block/Adminhtml/Import/Edit/Form.php
@@ -5,6 +5,7 @@
  */
 namespace Magento\ImportExport\Block\Adminhtml\Import\Edit;
 
+use Magento\ImportExport\Model\Import;
 use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
 
 /**
@@ -170,7 +171,7 @@ class Form extends \Magento\Backend\Block\Widget\Form\Generic
                     'required' => true,
                     'disabled' => true,
                     'class' => $behaviorCode,
-                    'value' => ',',
+                    'value' => Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR,
                 ]
             );
         }
diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Download.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Download.php
index 240067a4f1ff2585dceb4e1806624e43d786efad..5982658bb5160d60d9c70a64dce7067dd4e1fada 100644
--- a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Download.php
+++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Download.php
@@ -5,6 +5,7 @@
  */
 namespace Magento\ImportExport\Controller\Adminhtml\Import;
 
+use Magento\Framework\Component\ComponentRegistrar;
 use Magento\ImportExport\Controller\Adminhtml\Import as ImportController;
 use Magento\Framework\App\Filesystem\DirectoryList;
 
@@ -26,9 +27,9 @@ class Download extends ImportController
     protected $readFactory;
 
     /**
-     * @var \Magento\Framework\Module\Dir\Reader
+     * @var \Magento\Framework\Component\ComponentRegistrar
      */
-    protected $reader;
+    protected $componentRegistrar;
 
     /**
      * @var \Magento\Framework\App\Response\Http\FileFactory
@@ -42,14 +43,14 @@ class Download extends ImportController
      * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory
      * @param \Magento\Framework\Controller\Result\RawFactory $resultRawFactory
      * @param \Magento\Framework\Filesystem\Directory\ReadFactory $readFactory
-     * @param \Magento\Framework\Module\Dir\Reader $reader
+     * @param ComponentRegistrar $componentRegistrar
      */
     public function __construct(
         \Magento\Backend\App\Action\Context $context,
         \Magento\Framework\App\Response\Http\FileFactory $fileFactory,
         \Magento\Framework\Controller\Result\RawFactory $resultRawFactory,
         \Magento\Framework\Filesystem\Directory\ReadFactory $readFactory,
-        \Magento\Framework\Module\Dir\Reader $reader
+        \Magento\Framework\Component\ComponentRegistrar $componentRegistrar
     ) {
         parent::__construct(
             $context
@@ -57,7 +58,7 @@ class Download extends ImportController
         $this->fileFactory = $fileFactory;
         $this->resultRawFactory = $resultRawFactory;
         $this->readFactory = $readFactory;
-        $this->reader = $reader;
+        $this->componentRegistrar = $componentRegistrar;
     }
 
     /**
@@ -68,8 +69,8 @@ class Download extends ImportController
     public function executeInternal()
     {
         $fileName = $this->getRequest()->getParam('filename') . '.csv';
-        $moduleDir = $this->reader->getModuleDir('', self::SAMPLE_FILES_MODULE);
-        $fileAbsolutePath = $moduleDir . '/' . $fileName;
+        $moduleDir = $this->componentRegistrar->getPath(ComponentRegistrar::MODULE, self::SAMPLE_FILES_MODULE);
+        $fileAbsolutePath = $moduleDir . '/Files/Sample/' . $fileName;
         $directoryRead = $this->readFactory->create($moduleDir);
         $filePath = $directoryRead->getRelativePath($fileAbsolutePath);
 
diff --git a/app/code/Magento/ImportExport/Files/Sample/catalog_product.csv b/app/code/Magento/ImportExport/Files/Sample/catalog_product.csv
index 488c1f0894b2615eceea828215b854b5e3526550..8d9eaaa5e9a41447dd57fd55f82f927ea13ff1ee 100644
--- a/app/code/Magento/ImportExport/Files/Sample/catalog_product.csv
+++ b/app/code/Magento/ImportExport/Files/Sample/catalog_product.csv
@@ -1,8 +1,7 @@
-sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,crosssell_skus,upsell_skus,additional_images,additional_image_labels,custom_options,configurable_variations,configurable_variation_prices,configurable_variation_labels,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values
-24-WG085,,Sprite Yoga Strap,simple,Default Category/Gear|Default Category/Gear/Fitness Equipment,base,Sprite Yoga Strap 6 foot,"<p>The Sprite Yoga Strap is your untiring partner in demanding stretches, holds and alignment routines. The strap's 100% organic cotton fabric is woven tightly to form a soft, textured yet non-slip surface. The plastic clasp buckle is easily adjustable, lightweight and urable under strain.</p><ul><li>100% soft and durable cotton.<li>Plastic cinch buckle is easy to use.<li>Three natural colors made from phthalate and heavy metal free dyes.</ul>",,,1,Taxable Goods,"Catalog, Search",14,,,,sprite-yoga-strap-6-foot,Meta Title,"meta1, meta2, meta3",meta description,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,01.07.2015 15:38,01.07.2015 15:38,,,Block after Info Column,,,,,,,,,,,,,"has_options=0,required_options=0,size_strap=6 foot",100,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,1,"24-WG086,24-WG087","24-WG086,24-WG087","24-WG086,24-WG087","/sample_data/l/u/luma-yoga-strap.jpg,/sample_data/l/u/luma-yoga-strap.jpg","Image,Image","name=Custom Yoga Option,type=drop_down,required=0,price=10.0000,price_type=fixed,sku=,option_title=Gold|name=Custom Yoga Option,type=drop_down,required=0,price=10.0000,price_type=fixed,sku=,option_title=Silver|name=Custom Yoga Option,type=drop_down,required=0,price=10.0000,price_type=fixed,sku=yoga3sku,option_title=Platinum",,,,,,,,
-24-WG086,,Sprite Yoga Strap,simple,Default Category/Gear|Default Category/Gear/Fitness Equipment,base,Sprite Yoga Strap 8 foot,"<p>The Sprite Yoga Strap is your untiring partner in demanding stretches, holds and alignment routines. The strap's 100% organic cotton fabric is woven tightly to form a soft, textured yet non-slip surface. The plastic clasp buckle is easily adjustable, lightweight and durable under strain.</p><ul><li>8' long x 1.0"" wide.<li>100% soft and durable cotton.<li>Plastic cinch buckle is easy to use.<li>Three natural colors made from phthalate and heavy metal free dyes.</ul>",,,1,Taxable Goods,"Catalog, Search",17,,,,sprite-yoga-strap-8-foot,Meta Title,"meta1, meta2, meta4",meta description,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,01.07.2015 15:38,01.07.2015 15:38,,,Block after Info Column,,,,,,,,,,,,,"has_options=0,required_options=0,size_strap=8 foot",100,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,1,"24-WG086,24-WG087","24-WG086,24-WG087","24-WG086,24-WG087","/sample_data/l/u/luma-yoga-strap.jpg,/sample_data/l/u/luma-yoga-strap.jpg","Image,Image",,,,,,,,,
-24-WG087,,Sprite Yoga Strap,simple,Default Category/Gear|Default Category/Gear/Fitness Equipment,base,Sprite Yoga Strap 10 foot,"<p>The Sprite Yoga Strap is your untiring partner in demanding stretches, holds and alignment routines. The strap's 100% organic cotton fabric is woven tightly to form a soft, textured yet non-slip surface. The plastic clasp buckle is easily adjustable, lightweight and durable under strain.</p><ul><li>10' long x 1.0"" wide.<li>100% soft and durable cotton.<li>Plastic cinch buckle is easy to use.<li>Three natural colors made from phthalate and heavy metal free dyes.</ul>",,,1,Taxable Goods,"Catalog, Search",21,,,,sprite-yoga-strap-10-foot,Meta Title,"meta1, meta2, meta5",meta description,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,01.07.2015 15:39,01.07.2015 15:39,,,Block after Info Column,,,,,,,,,,,,,"has_options=0,required_options=0,size_strap=10 foot",100,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,1,"24-WG086,24-WG087","24-WG086,24-WG087","24-WG086,24-WG087","/sample_data/l/u/luma-yoga-strap.jpg,/sample_data/l/u/luma-yoga-strap.jpg","Image,Image",,,,,,,,,
-24-WG085_Group,,Gear,grouped,Default Category/Gear|Default Category/Gear/Fitness Equipment,base,Set of Sprite Yoga Straps,"<p>Great set of Sprite Yoga Straps for every stretch and hold you need. There are three straps in this set: 6', 8' and 10'.</p><ul><li> 100% soft and durable cotton.<li> Plastic cinch buckle is easy to use.<li> Choice of three natural colors made from phthalate and heavy metal free dyes.</ul>",,,1,,"Catalog, Search",,,,,set-of-sprite-yoga-straps,Meta Title,"meta1, meta2, meta6",meta description,/sample_data/l/u/luma-yoga-strap-set.jpg,Image Label,/sample_data/l/u/luma-yoga-strap-set.jpg,Image Label,/sample_data/l/u/luma-yoga-strap-set.jpg,Image Label,01.07.2015 15:39,01.07.2015 15:39,,,Block after Info Column,,,,,,,,,,,,,"has_options=0,required_options=0",0,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,1,"24-WG086,24-WG087","24-WG086,24-WG087","24-WG086,24-WG087",/sample_data/l/u/luma-yoga-strap-set.jpg,"Image,Image",,,,,,,,,
-24-WG085-configurable,,Sprite Yoga Strap,configurable,Default Category/Gear|Default Category/Gear/Fitness Equipment,base,Sprite Yoga Strap,"<p>The Sprite Yoga Strap is your untiring partner in demanding stretches, holds and alignment routines. The strap's 100% organic cotton fabric is woven tightly to form a soft, textured yet non-slip surface. The plastic clasp buckle is easily adjustable, lightweight and durable under strain.</p><ul><li>100% soft and durable cotton.<li>Plastic cinch buckle is easy to use.<li>Three natural colors made from phthalate and heavy metal free dyes.</ul>",,,1,Taxable Goods,"Catalog, Search",14,,,,sprite-yoga-strap1,Meta Title,"meta1, meta2, meta7",meta description,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,01.07.2015 16:15,01.07.2015 16:15,,,Block after Info Column,,,,,,,,,,,,,"has_options=1,required_options=1",0,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,1,"24-WG086,24-WG087","24-WG086,24-WG087","24-WG086,24-WG087",/sample_data/l/u/luma-yoga-strap.jpg,"Image,Image",,"sku=24-WG086,size_strap=8 foot|sku=24-WG087,size_strap=10 foot|sku=24-WG085,size_strap=6 foot","name=size_strap,value=8 foot,price=3.0000,price_type=fixed|name=size_strap,value=10 foot,price=7.0000,price_type=fixed|name=size_strap,value=6 foot,price=,price_type=fixed",size_strap=Size Strap,,,,,
-24-WG085-bundle-dynamic,,Sprite Yoga Strap,bundle,Default Category/Gear|Default Category/Gear/Fitness Equipment,base,Sprite Yoga Strap,"<p>The Sprite Yoga Strap is your untiring partner in demanding stretches, holds and alignment routines. The strap's 100% organic cotton fabric is woven tightly to form a soft, textured yet non-slip surface. The plastic clasp buckle is easily adjustable, lightweight and durable under strain.</p><ul><li>100% soft and durable cotton.<li>Plastic cinch buckle is easy to use.<li>Three natural colors made from phthalate and heavy metal free dyes.</ul>",,,1,Taxable Goods,"Catalog, Search",14,,,,sprite-yoga-strap2,Meta Title,"meta1, meta2, meta8",meta description,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,01.07.2015 16:15,01.07.2015 16:15,,,Block after Info Column,,,,,,,,,,,,,"has_options=1,required_options=1",0,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,1,"24-WG086,24-WG087","24-WG086,24-WG087","24-WG086,24-WG087",/sample_data/l/u/luma-yoga-strap.jpg,"Image,Image",,,,,dynamic,dynamic,Price Range,fixed,"name=Bundle Option One1,type=dropdown,required=1,sku=bunsimplesku30,price=15; price_type=fixed, default_qty=1, is_defaul=0 | name=Bundle Option One1,type=dropdown; required=1, sku=bunsimplesku31,price=10, price_type=fixed, default_qty=1, is_defaul=1"
-24-WG085-bundle-fixed,,Sprite Yoga Strap,bundle,Default Category/Gear|Default Category/Gear/Fitness Equipment,base,Sprite Yoga Strap,"<p>The Sprite Yoga Strap is your untiring partner in demanding stretches, holds and alignment routines. The strap's 100% organic cotton fabric is woven tightly to form a soft, textured yet non-slip surface. The plastic clasp buckle is easily adjustable, lightweight and durable under strain.</p><ul><li>100% soft and durable cotton.<li>Plastic cinch buckle is easy to use.<li>Three natural colors made from phthalate and heavy metal free dyes.</ul>",,,1,Taxable Goods,"Catalog, Search",14,,,,sprite-yoga-strap3,Meta Title,"meta1, meta2, meta9",meta description,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,/sample_data/l/u/luma-yoga-strap.jpg,Image Label,01.07.2015 16:15,01.07.2015 16:15,,,Block after Info Column,,,,,,,,,,,,,"has_options=1,required_options=1",0,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,1,"24-WG086,24-WG087","24-WG086,24-WG087","24-WG086,24-WG087",/sample_data/l/u/luma-yoga-strap.jpg,"Image,Image",,,,,fixed,fixed,Price Range,fixed,"name=Yoga Strap,type=radiobutton,required=1,sku=24-WG086,default_qty=3,default=1 | name=Yoga Strap,type=radiobutton,required=1,sku=24-WG085,default_qty=3,default=0"
+sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,deferred_stock_update,use_config_deferred_stock_update,related_skus,crosssell_skus,upsell_skus,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,associated_skus
+24-WG085,,Default,simple,"Default Category/Gear,Default Category/Gear/Fitness Equipment",base,"Sprite Yoga Strap 6 foot","<p>The Sprite Yoga Strap is your untiring partner in demanding stretches, holds and alignment routines. The strap's 100% organic cotton fabric is woven tightly to form a soft, textured yet non-slip surface. The plastic clasp buckle is easily adjustable, lightweight and urable under strain.</p><ul><li>100% soft and durable cotton.<li>Plastic cinch buckle is easy to use.<li>Three natural colors made from phthalate and heavy metal free dyes.</ul>",,1.0000,1,"Taxable Goods","Catalog, Search",14.0000,,,,sprite-yoga-strap-6-foot,"Meta Title","meta1, meta2, meta3","meta description",/l/u/luma-yoga-strap.jpg,"Image Label",/l/u/luma-yoga-strap.jpg,"Image Label",/l/u/luma-yoga-strap.jpg,"Image Label","2015-10-25 03:34:19","2015-10-25 03:34:20",,,"Block after Info Column",,,,,,,,,,,"Use config",,"has_options=1,is_returnable=Use config,quantity_and_stock_status=In Stock,required_options=0",100.0000,0.0000,1,0,0,1,1.0000,0,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,1,0,1,"24-WG087,24-WG086","24-WG087,24-WG086","24-WG087,24-WG086",/l/u/luma-yoga-strap.jpg,Image,,"name=Custom Yoga Option,type=drop_down,required=0,price=10.0000,price_type=fixed,sku=,option_title=Gold|name=Custom Yoga Option,type=drop_down,required=0,price=10.0000,price_type=fixed,sku=,option_title=Silver|name=Custom Yoga Option,type=drop_down,required=0,price=10.0000,price_type=fixed,sku=yoga3sku,option_title=Platinum",,,,,,
+24-WG086,,Default,simple,"Default Category/Gear,Default Category/Gear/Fitness Equipment",base,"Sprite Yoga Strap 8 foot","<p>The Sprite Yoga Strap is your untiring partner in demanding stretches, holds and alignment routines. The strap's 100% organic cotton fabric is woven tightly to form a soft, textured yet non-slip surface. The plastic clasp buckle is easily adjustable, lightweight and durable under strain.</p><ul><li>8' long x 1.0"" wide.<li>100% soft and durable cotton.<li>Plastic cinch buckle is easy to use.<li>Three natural colors made from phthalate and heavy metal free dyes.</ul>",,1.0000,1,"Taxable Goods","Catalog, Search",17.0000,,,,sprite-yoga-strap-8-foot,"Meta Title","meta1, meta2, meta4","meta description",/l/u/luma-yoga-strap.jpg,"Image Label",/l/u/luma-yoga-strap.jpg,"Image Label",/l/u/luma-yoga-strap.jpg,"Image Label","2015-10-25 03:34:20","2015-10-25 03:34:20",,,"Block after Info Column",,,,,,,,,,,"Use config",,"has_options=0,is_returnable=Use config,quantity_and_stock_status=In Stock,required_options=0",100.0000,0.0000,1,0,0,1,1.0000,0,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,1,0,1,24-WG087,24-WG087,24-WG087,/l/u/luma-yoga-strap.jpg,Image,,,,,,,,
+24-WG087,,Default,simple,"Default Category/Gear,Default Category/Gear/Fitness Equipment",base,"Sprite Yoga Strap 10 foot","<p>The Sprite Yoga Strap is your untiring partner in demanding stretches, holds and alignment routines. The strap's 100% organic cotton fabric is woven tightly to form a soft, textured yet non-slip surface. The plastic clasp buckle is easily adjustable, lightweight and durable under strain.</p><ul><li>10' long x 1.0"" wide.<li>100% soft and durable cotton.<li>Plastic cinch buckle is easy to use.<li>Three natural colors made from phthalate and heavy metal free dyes.</ul>",,1.0000,1,"Taxable Goods","Catalog, Search",21.0000,,,,sprite-yoga-strap-10-foot,"Meta Title","meta1, meta2, meta5","meta description",/l/u/luma-yoga-strap.jpg,"Image Label",/l/u/luma-yoga-strap.jpg,"Image Label",/l/u/luma-yoga-strap.jpg,"Image Label","2015-10-25 03:34:20","2015-10-25 03:34:20",,,"Block after Info Column",,,,,,,,,,,"Use config",,"has_options=0,is_returnable=Use config,quantity_and_stock_status=In Stock,required_options=0",100.0000,0.0000,1,0,0,1,1.0000,0,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,1,0,1,24-WG086,24-WG086,24-WG086,/l/u/luma-yoga-strap.jpg,Image,,,,,,,,
+24-WG085_Group,,Default,grouped,"Default Category/Gear,Default Category/Gear/Fitness Equipment",base,"Set of Sprite Yoga Straps","<p>Great set of Sprite Yoga Straps for every stretch and hold you need. There are three straps in this set: 6', 8' and 10'.</p><ul><li>100% soft and durable cotton.</li><li>Plastic cinch buckle is easy to use.</li><li>Choice of three natural colors made from phthalate and heavy metal free dyes.</li></ul>",,,1,,"Catalog, Search",,,,,set-of-sprite-yoga-straps,"Meta Title","meta1, meta2, meta6","meta description",/l/u/luma-yoga-strap-set.jpg,Image,/l/u/luma-yoga-strap-set.jpg,Image,/l/u/luma-yoga-strap-set.jpg,Image,"2015-10-25 03:34:20","2015-10-25 03:36:31",,,"Block after Info Column",,,,,,,,,,,,,"has_options=0,is_returnable=Use config,quantity_and_stock_status=In Stock,required_options=0",0.0000,0.0000,1,0,0,1,1.0000,0,0.0000,1,1,,1,1,1,1,0.0000,1,0,0,1,0,1,"24-WG087,24-WG086","24-WG087,24-WG086","24-WG087,24-WG086",/l/u/luma-yoga-strap-set.jpg,Image,,,,,,,,"24-WG085=5.0000,24-WG086=5.0000"
+24-WG085-bundle-dynamic,,Default,bundle,"Default Category/Gear,Default Category/Gear/Fitness Equipment",base,"Sprite Yoga Strap Dynamic Bundle","<p>The Sprite Yoga Strap is your untiring partner in demanding stretches, holds and alignment routines. The strap's 100% organic cotton fabric is woven tightly to form a soft, textured yet non-slip surface. The plastic clasp buckle is easily adjustable, lightweight and durable under strain.</p><ul><li>100% soft and durable cotton.<li>Plastic cinch buckle is easy to use.<li>Three natural colors made from phthalate and heavy metal free dyes.</ul>",,1.0000,1,"Taxable Goods","Catalog, Search",14.0000,,,,sprite-yoga-strap2,"Meta Title","meta1, meta2, meta8","meta description",/l/u/luma-yoga-strap.jpg,,/l/u/luma-yoga-strap.jpg,,/l/u/luma-yoga-strap.jpg,,"2015-10-25 03:34:20","2015-10-25 03:34:20",,,"Block after Info Column",,,,,,,,,,,"Use config",,"has_options=1,is_returnable=Use config,quantity_and_stock_status=In Stock,required_options=0",0.0000,0.0000,1,0,0,1,1.0000,0,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,1,0,1,"24-WG087,24-WG086","24-WG087,24-WG086","24-WG087,24-WG086",/l/u/luma-yoga-strap.jpg,Image,,,dynamic,dynamic,"Price range",fixed,"name=Bundle Option One1,type=select,required=1,sku=24-WG085,price=15.0000,default=0,default_qty=1.0000,price_type=fixed|name=Bundle Option One1,type=select,required=1,sku=24-WG086,price=10.0000,default=1,default_qty=1.0000,price_type=fixed",
+24-WG085-bundle-fixed,,Default,bundle,"Default Category/Gear,Default Category/Gear/Fitness Equipment",base,"Sprite Yoga Strap Fixed Bundle","<p>The Sprite Yoga Strap is your untiring partner in demanding stretches, holds and alignment routines. The strap's 100% organic cotton fabric is woven tightly to form a soft, textured yet non-slip surface. The plastic clasp buckle is easily adjustable, lightweight and durable under strain.</p><ul><li>100% soft and durable cotton.<li>Plastic cinch buckle is easy to use.<li>Three natural colors made from phthalate and heavy metal free dyes.</ul>",,1.0000,1,"Taxable Goods","Catalog, Search",14.0000,,,,sprite-yoga-strap3,"Meta Title","meta1, meta2, meta9","meta description",/l/u/luma-yoga-strap.jpg,,/l/u/luma-yoga-strap.jpg,,/l/u/luma-yoga-strap.jpg,,"2015-10-25 03:34:20","2015-10-25 03:34:20",,,"Block after Info Column",,,,,,,,,,,"Use config",,"has_options=1,is_returnable=Use config,quantity_and_stock_status=In Stock,required_options=0",0.0000,0.0000,1,0,0,1,1.0000,0,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,1,0,1,"24-WG087,24-WG086","24-WG087,24-WG086","24-WG087,24-WG086",/l/u/luma-yoga-strap.jpg,Image,,,fixed,fixed,"Price range",fixed,"name=Yoga Strap,type=radio,required=1,sku=24-WG086,price=0.0000,default=1,default_qty=3.0000,price_type=percent|name=Yoga Strap,type=radio,required=1,sku=24-WG085,price=0.0000,default=0,default_qty=3.0000,price_type=percent",
diff --git a/app/code/Magento/ImportExport/Model/Import.php b/app/code/Magento/ImportExport/Model/Import.php
index 3742d5e54f3e3120ce59ab2cfd23c722dd3d9d7c..1d9984df7944d48824226beed2fb1973f5e3d173 100644
--- a/app/code/Magento/ImportExport/Model/Import.php
+++ b/app/code/Magento/ImportExport/Model/Import.php
@@ -80,6 +80,11 @@ class Import extends \Magento\ImportExport\Model\AbstractModel
 
     /**#@-*/
 
+    /**
+     * default delimiter for several values in one cell as default for FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR
+     */
+    const DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR = ',';
+
     /**#@+
      * Import constants
      */
diff --git a/app/code/Magento/ImportExport/Model/Import/AbstractEntity.php b/app/code/Magento/ImportExport/Model/Import/AbstractEntity.php
index 085c9261e341229fb19bebc078874f87e9dff561..5edac8699319617b7fa169e5c51ce86dc25139d5 100644
--- a/app/code/Magento/ImportExport/Model/Import/AbstractEntity.php
+++ b/app/code/Magento/ImportExport/Model/Import/AbstractEntity.php
@@ -69,7 +69,7 @@ abstract class AbstractEntity
         self::ERROR_CODE_COLUMNS_NUMBER => "Number of columns does not correspond to the number of rows in the header",
         self::ERROR_EXCEEDED_MAX_LENGTH => 'Attribute %s exceeded max length',
         self::ERROR_INVALID_ATTRIBUTE_TYPE =>
-            'Value for \'%s\' attribute contains incorrect value, acceptable values are in %s format',
+            'Value for \'%s\' attribute contains incorrect value',
         self::ERROR_INVALID_ATTRIBUTE_OPTION =>
             "Value for %s attribute contains incorrect value, see acceptable values on settings specified for Admin",
     ];
diff --git a/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php b/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php
index acb19a9847212e0459c5c7a937044ab3fb3bbc8a..e6df28446fbf0e74026d2c8d120e06ed78dd20a3 100644
--- a/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php
+++ b/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php
@@ -81,7 +81,9 @@ class ProcessingErrorAggregator implements ProcessingErrorAggregatorInterface
             return $this;
         }
         $this->processErrorStatistics($errorLevel);
-        $this->processInvalidRow($rowNumber);
+        if ($errorLevel == ProcessingError::ERROR_LEVEL_CRITICAL) {
+            $this->processInvalidRow($rowNumber);
+        }
         $errorMessage = $this->getErrorMessage($errorCode, $errorMessage, $columnName);
 
         /** @var ProcessingError $newError */
diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/ErrorProcessing/ProcessingErrorAggregatorTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/ErrorProcessing/ProcessingErrorAggregatorTest.php
index 63b82e3693d2d7a1ee524f5aaa33c77a9e7950ae..090370a728a68b97d6b64499fe5569d7ccf5fe8c 100644
--- a/app/code/Magento/ImportExport/Test/Unit/Model/Import/ErrorProcessing/ProcessingErrorAggregatorTest.php
+++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/ErrorProcessing/ProcessingErrorAggregatorTest.php
@@ -5,6 +5,8 @@
  */
 namespace Magento\ImportExport\Test\Unit\Model\Import\ErrorProcessing;
 
+use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingError;
+
 class ProcessingErrorAggregatorTest extends \PHPUnit_Framework_TestCase
 {
     /**
@@ -124,22 +126,26 @@ class ProcessingErrorAggregatorTest extends \PHPUnit_Framework_TestCase
 
     /**
      * Test for method isRowInvalid. Expected true result.
+     * @dataProvider isRowInvalidDataProvider
      */
-    public function testIsRowInvalidTrue()
+    public function testIsRowInvalid($errorLevel, $rowNumber, $isValid)
     {
-        $this->model->addError('systemException', 'critical', 7, 'Some column name', 'Message', 'Description');
-        $result = $this->model->isRowInvalid(7);
-        $this->assertTrue($result);
+        $this->model->addError('systemException', $errorLevel, $rowNumber, 'Column name', 'Message', 'Description');
+        $result = $this->model->isRowInvalid($rowNumber);
+        $this->assertEquals($isValid, $result);
     }
 
     /**
-     * Test for method isRowInvalid. Expected false result.
+     * @return array
      */
-    public function testIsRowInvalidFalse()
+    public function isRowInvalidDataProvider()
     {
-        $this->model->addError('systemException');
-        $result = $this->model->isRowInvalid(8);
-        $this->assertFalse($result);
+        return [
+            [ProcessingError::ERROR_LEVEL_CRITICAL, 7, true],
+            [ProcessingError::ERROR_LEVEL_NOT_CRITICAL, 8, false],
+            [ProcessingError::ERROR_LEVEL_NOTICE, 9, false],
+            [ProcessingError::ERROR_LEVEL_WARNING, 10, false]
+        ];
     }
 
     /**
diff --git a/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/after.phtml b/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/after.phtml
index 73eacb73e7869479b1e1c88bf37ae481d5313c4c..e363b6fe0d017d628ed7019044dd8e0fbbc23e56 100644
--- a/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/after.phtml
+++ b/app/code/Magento/ImportExport/view/adminhtml/templates/import/form/after.phtml
@@ -14,7 +14,7 @@
     <div id="import_validation_messages" class="fieldset"><!-- --></div>
 </div>
 <script>
-require(['jquery', 'prototype'], function(jQuery){
+require(['jquery', 'Magento_Ui/js/modal/alert', 'prototype'], function(jQuery){
 //<![CDATA[
     varienImport.resetSelectIndex('entity'); // forced resetting entity selector after page refresh
 //]]>
diff --git a/app/code/Magento/PageCache/Model/Controller/Result/BuiltinPlugin.php b/app/code/Magento/PageCache/Model/Controller/Result/BuiltinPlugin.php
index 667da81dd04e1494ffb875185b796fc6880a74f9..022824faafbd41faf61b1539f2939ee0ba0277bd 100644
--- a/app/code/Magento/PageCache/Model/Controller/Result/BuiltinPlugin.php
+++ b/app/code/Magento/PageCache/Model/Controller/Result/BuiltinPlugin.php
@@ -66,8 +66,9 @@ class BuiltinPlugin
     ) {
         $result = $proceed($response);
         $usePlugin = $this->registry->registry('use_page_cache_plugin');
-        if (!$this->config->isEnabled() || $this->config->getType() != \Magento\PageCache\Model\Config::BUILT_IN
-            || !$usePlugin) {
+        if (!$usePlugin || !$this->config->isEnabled()
+            || $this->config->getType() != \Magento\PageCache\Model\Config::BUILT_IN
+        ) {
             return $result;
         }
 
@@ -76,6 +77,16 @@ class BuiltinPlugin
             $response->setHeader('X-Magento-Cache-Control', $cacheControl);
             $response->setHeader('X-Magento-Cache-Debug', 'MISS', true);
         }
+
+        $tagsHeader = $response->getHeader('X-Magento-Tags');
+        $tags = [];
+        if ($tagsHeader) {
+            $tags = explode(',', $tagsHeader->getFieldValue());
+            $response->clearHeader('X-Magento-Tags');
+        }
+        $tags = array_unique(array_merge($tags, [\Magento\PageCache\Model\Cache\Type::CACHE_TAG]));
+        $response->setHeader('X-Magento-Tags', implode(',', $tags));
+
         $this->kernel->process($response);
         return $result;
     }
diff --git a/app/code/Magento/PageCache/Test/Unit/Model/Controller/Result/BuiltinPluginTest.php b/app/code/Magento/PageCache/Test/Unit/Model/Controller/Result/BuiltinPluginTest.php
index a3542bdc708cf1c7d7dcbfc9fb8d91c6bb626f41..2ae5fa1f9f1e54491c0f00648bab3ed303d5cded 100644
--- a/app/code/Magento/PageCache/Test/Unit/Model/Controller/Result/BuiltinPluginTest.php
+++ b/app/code/Magento/PageCache/Test/Unit/Model/Controller/Result/BuiltinPluginTest.php
@@ -11,83 +11,133 @@ namespace Magento\PageCache\Test\Unit\Model\Controller\Result;
 class BuiltinPluginTest extends \PHPUnit_Framework_TestCase
 {
     /**
-     * @param bool $usePlugin
-     * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $getHeaderCount
-     * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $setCacheControlHeaderCount
-     * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $setCacheDebugHeaderCount
-     * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $getModeCount
-     * @param \PHPUnit_Framework_MockObject_Matcher_InvokedCount $processCount
-     * @dataProvider dataProvider
+     * @var \Magento\PageCache\Model\Controller\Result\BuiltinPlugin
      */
-    public function testAroundResult(
-        $usePlugin, $getHeaderCount, $setCacheControlHeaderCount, $setCacheDebugHeaderCount, $getModeCount,
-        $processCount
-    ) {
-        $cacheControl = 'test';
-
-        $header = $this->getMockBuilder('Zend\Http\Header\HeaderInterface')
-            ->getMockForAbstractClass();
-        $header->expects($this->any())->method('getFieldValue')
-            ->willReturn($cacheControl);
-
-        $response = $this->getMock('Magento\Framework\App\Response\Http', [], [], '', false);
-        $response->expects($getHeaderCount)
-            ->method('getHeader')
-            ->with('Cache-Control')
-            ->willReturn($header);
-        $response->expects($setCacheControlHeaderCount)->method('setHeader')
-                ->with('X-Magento-Cache-Control', $cacheControl);
-        $response->expects($setCacheDebugHeaderCount)->method('setHeader')
-                ->with('X-Magento-Cache-Debug', 'MISS', true);
-
-        /** @var \Magento\Framework\Controller\ResultInterface $result */
+    protected $plugin;
+
+    /**
+     * @var \Magento\Framework\Controller\ResultInterface
+     */
+    protected $subject;
+
+    /**
+     * @var \Closure
+     */
+    protected $closure;
+
+    /**
+     * @var \Magento\Framework\App\Response\Http|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $response;
+
+    /**
+     * @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $registry;
+
+    /**
+     * @var \Magento\Framework\App\State|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $state;
+
+    /**
+     * @var \Zend\Http\Header\HeaderInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $header;
+
+    /**
+     * @var \Magento\Framework\App\PageCache\Kernel|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $kernel;
+
+    protected function setUp()
+    {
         $result = $this->getMock('Magento\Framework\Controller\ResultInterface', [], [], '', false);
-        $closure = function () use ($result) {
+        $this->closure = function() use ($result) {
             return $result;
         };
 
-        /** @var \Magento\Framework\Registry|\PHPUnit_Framework_MockObject_MockObject $registry */
-        $registry = $this->getMock('Magento\Framework\Registry', [], [], '', false);
-        $registry->expects($this->once())->method('registry')->with('use_page_cache_plugin')
-            ->will($this->returnValue($usePlugin));
-
-        /** @var \Magento\PageCache\Model\Config|\PHPUnit_Framework_MockObject_MockObject $config */
-        $config = $this->getMock('Magento\PageCache\Model\Config', [], [], '', false);
-        $config->expects($this->once())->method('isEnabled')->will($this->returnValue(true));
-        $config->expects($this->once())->method('getType')
-            ->will($this->returnValue(\Magento\PageCache\Model\Config::BUILT_IN));
+        $this->header = $this->getMock('Zend\Http\Header\HeaderInterface', [], [], '', false);
+        $this->subject = $this->getMock('Magento\Framework\Controller\ResultInterface', [], [], '', false);
+        $this->response = $this->getMock(
+            'Magento\Framework\App\Response\Http',
+            ['getHeader', 'clearHeader', 'setHeader'],
+            [],
+            '',
+            false
+        );
+        $this->response->expects($this->any())->method('getHeader')->willReturnMap(
+            [
+                ['X-Magento-Tags', $this->header],
+                ['Cache-Control', $this->header]
+            ]
+        );
 
-        /** @var \Magento\Framework\App\State|\PHPUnit_Framework_MockObject_MockObject $state */
-        $state = $this->getMock('Magento\Framework\App\State', [], [], '', false);
-        $state->expects($getModeCount)->method('getMode')
-            ->will($this->returnValue(\Magento\Framework\App\State::MODE_DEVELOPER));
+        $this->registry = $this->getMock('Magento\Framework\Registry', [], [], '', false);
 
-        $kernel = $this->getMock('Magento\Framework\App\PageCache\Kernel', [], [], '', false);
-        $kernel->expects($processCount)->method('process')->with($response);
+        $config = $this->getMock('Magento\PageCache\Model\Config', ['isEnabled', 'getType'], [], '', false);
+        $config->expects($this->any())->method('isEnabled')->willReturn(true);
+        $config->expects($this->any())->method('getType')->willReturn(\Magento\PageCache\Model\Config::BUILT_IN);
 
-        $subject = $this->getMock('Magento\Framework\Controller\ResultInterface', [], [], '', false);
+        $this->kernel = $this->getMock('Magento\Framework\App\PageCache\Kernel', [], [], '', false);
 
-        /** @var \Magento\PageCache\Model\Controller\Result\BuiltinPlugin $plugin */
-        $plugin = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject(
+        $this->state = $this->getMock('Magento\Framework\App\State', [], [], '', false);
+        $this->plugin = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject(
             'Magento\PageCache\Model\Controller\Result\BuiltinPlugin',
             [
-                'registry' => $registry,
+                'registry' => $this->registry,
                 'config' => $config,
-                'kernel' => $kernel,
-                'state' => $state
+                'kernel' => $this->kernel,
+                'state' => $this->state
             ]
         );
-        $this->assertSame($result, $plugin->aroundRenderResult($subject, $closure, $response));
     }
 
-    /**
-     * @return array
-     */
-    public function dataProvider()
+    public function testAroundResultWithoutPlugin()
     {
-        return [
-            [true, $this->once(), $this->at(1), $this->at(2), $this->once(), $this->once()],
-            [false, $this->never(), $this->never(), $this->never(), $this->never(), $this->never()]
-        ];
+        $this->registry->expects($this->once())->method('registry')->with('use_page_cache_plugin')->willReturn(false);
+        $this->kernel->expects($this->never())->method('process')->with($this->response);
+        $this->assertSame(
+            call_user_func($this->closure),
+            $this->plugin->aroundRenderResult($this->subject, $this->closure, $this->response)
+        );
+    }
+
+    public function testAroundResultWithPlugin()
+    {
+        $this->registry->expects($this->once())->method('registry')->with('use_page_cache_plugin')->willReturn(true);
+        $this->state->expects($this->once())->method('getMode')->willReturn(null);
+        $this->header->expects($this->any())->method('getFieldValue')->willReturn('tag,tag');
+        $this->response->expects($this->once())->method('clearHeader')->with('X-Magento-Tags');
+        $this->response->expects($this->once())->method('setHeader')->with(
+            'X-Magento-Tags',
+            'tag,' . \Magento\PageCache\Model\Cache\Type::CACHE_TAG
+        );
+        $this->kernel->expects($this->once())->method('process')->with($this->response);
+        $result = call_user_func($this->closure);
+        $this->assertSame($result, $this->plugin->aroundRenderResult($this->subject, $this->closure, $this->response));
+    }
+
+    public function testAroundResultWithPluginDeveloperMode()
+    {
+        $this->registry->expects($this->once())->method('registry')->with('use_page_cache_plugin')->willReturn(true);
+        $this->state->expects($this->once())->method('getMode')
+            ->willReturn(\Magento\Framework\App\State::MODE_DEVELOPER);
+
+        $this->header->expects($this->any())->method('getFieldValue')->willReturnOnConsecutiveCalls('test', 'tag,tag2');
+
+        $this->response->expects($this->any())->method('setHeader')->withConsecutive(
+            ['X-Magento-Cache-Control', 'test'],
+            ['X-Magento-Cache-Debug', 'MISS', true],
+            ['X-Magento-Tags', 'tag,tag2,' . \Magento\PageCache\Model\Cache\Type::CACHE_TAG]
+        );
+
+        $this->response->expects($this->once())->method('clearHeader')->with('X-Magento-Tags');
+        $this->registry->expects($this->once())->method('registry')->with('use_page_cache_plugin')
+            ->will($this->returnValue(true));
+        $this->kernel->expects($this->once())->method('process')->with($this->response);
+
+        $result = call_user_func($this->closure);
+        $this->assertSame($result, $this->plugin->aroundRenderResult($this->subject, $this->closure, $this->response));
     }
 }
diff --git a/app/code/Magento/Reports/Model/ResourceModel/Review/Product/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Review/Product/Collection.php
index 982219690499708d18323152f87485215b55f02d..662047abeca66f983f40e48debae4209e72fe2f9 100644
--- a/app/code/Magento/Reports/Model/ResourceModel/Review/Product/Collection.php
+++ b/app/code/Magento/Reports/Model/ResourceModel/Review/Product/Collection.php
@@ -58,8 +58,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
             $this->getConnection()->quoteInto('table_rating.store_id > ?', 0),
         ];
 
-        $percentField = $this->getConnection()->quoteIdentifier('table_rating.percent');
-        $sumPercentField = new \Zend_Db_Expr("SUM({$percentField})");
+        $sumPercentField = new \Zend_Db_Expr("SUM(table_rating.percent)");
         $sumPercentApproved = new \Zend_Db_Expr('SUM(table_rating.percent_approved)');
         $countRatingId = new \Zend_Db_Expr('COUNT(table_rating.rating_id)');
 
@@ -67,8 +66,8 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
             ['table_rating' => $this->getTable('rating_option_vote_aggregated')],
             implode(' AND ', $joinCondition),
             [
-                'avg_rating' => sprintf('%s/%s', $sumPercentField, $countRatingId),
-                'avg_rating_approved' => sprintf('%s/%s', $sumPercentApproved, $countRatingId)
+                'avg_rating' => new \Zend_Db_Expr(sprintf('%s/%s', $sumPercentField, $countRatingId)),
+                'avg_rating_approved' => new \Zend_Db_Expr(sprintf('%s/%s', $sumPercentApproved, $countRatingId))
             ]
         );
 
diff --git a/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/js.phtml b/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/js.phtml
index 0f911d563f714492a0700f50cdbb1746683f3fbe..c407178e0be985ed528df155a1259b527d584b27 100644
--- a/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/js.phtml
+++ b/app/code/Magento/Swatches/view/adminhtml/templates/catalog/product/attribute/js.phtml
@@ -15,248 +15,260 @@ require([
     'Magento_Ui/js/modal/prompt',
     "collapsable",
     "prototype"
-], function(jQuery, rg, alert, prompt){
-
-function toggleApplyVisibility(select) {
-    if ($(select).value == 1) {
-        $(select).next('select').removeClassName('no-display');
-        $(select).next('select').removeClassName('ignore-validate');
-
-    } else {
-        $(select).next('select').addClassName('no-display');
-        $(select).next('select').addClassName('ignore-validate');
-        var options = $(select).next('select').options;
-        for( var i=0; i < options.length; i++) {
-            options[i].selected = false;
-        }
-    }
-}
-function getFrontTab() {
-    if ($('product_attribute_tabs_front')) {
-        return $('product_attribute_tabs_front').up('li');
-    } else {
-        return $('front_fieldset-wrapper');
-    }
-}
+], function (jQuery, rg, alert, prompt) {
+    var frontendInput = $('frontend_input');
 
-function checkOptionsPanelVisibility(){
-    if($('manage-options-panel')){
-        var panel = $('manage-options-panel').up('.fieldset');
+    function toggleApplyVisibility (select) {
+        if ($(select).value == 1) {
+            $(select).next('select').removeClassName('no-display');
+            $(select).next('select').removeClassName('ignore-validate');
 
-        if($('frontend_input') && ($('frontend_input').value=='select' || $('frontend_input').value=='multiselect')){
-            panel.show();
-            rg.get('manage-options-panel', function() {
-                jQuery('#manage-options-panel').trigger('render');
-            });
-        }
-        else {
-            panel.hide();
-        }
-    }
-    if($('swatch-visual-options-panel')){
-        var panel = $('swatch-visual-options-panel').up('.fieldset');
-
-        if($('frontend_input') && $('frontend_input').value=='swatch_visual') {
-            panel.show();
-            rg.get('swatch-visual-options-panel', function() {
-                jQuery('#swatch-visual-options-panel').trigger('render');
-            });
-        }
-        else {
-            panel.hide();
+        } else {
+            $(select).next('select').addClassName('no-display');
+            $(select).next('select').addClassName('ignore-validate');
+            var options = $(select).next('select').options;
+            for( var i=0; i < options.length; i++) {
+                options[i].selected = false;
+            }
         }
     }
-    if($('swatch-text-options-panel')){
-        var panel = $('swatch-text-options-panel').up('.fieldset');
-
-        if($('frontend_input') && $('frontend_input').value=='swatch_text') {
-            panel.show();
-            rg.get('swatch-text-options-panel', function() {
-                jQuery('#swatch-text-options-panel').trigger('render');
-            });
-        }
-        else {
-            panel.hide();
+    function getFrontTab () {
+        var tabsFront = $('product_attribute_tabs_front');
+        if (tabsFront) {
+            return tabsFront.up('li');
+        } else {
+            return $('front_fieldset-wrapper');
         }
     }
-}
-
-function bindAttributeInputType()
-{
-    checkOptionsPanelVisibility();
-    switchDefaultValueField();
-    if($('frontend_input') && ($('frontend_input').value=='select' || $('frontend_input').value=='multiselect' || $('frontend_input').value=='price'
-        || $('frontend_input').value=='swatch_text' || $('frontend_input').value=='swatch_visual')){
-        if($('is_filterable') && !$('is_filterable').getAttribute('readonly')){
-            $('is_filterable').disabled = false;
+
+    function checkOptionsPanelVisibility () {
+        var optionsPanel = $('manage-options-panel'),
+            visualOptionsPanel = $('swatch-visual-options-panel'),
+            textOptionsPanel = $('swatch-text-options-panel');
+        if (optionsPanel) {
+            var panel = optionsPanel.up('.fieldset');
+
+            if (frontendInput && (frontendInput.value=='select' || frontendInput.value=='multiselect')) {
+                panel.show();
+                rg.get('manage-options-panel', function () {
+                    jQuery('#manage-options-panel').trigger('render');
+                });
+            } else {
+                panel.hide();
+            }
         }
-        if($('is_filterable_in_search') && !$('is_filterable_in_search').getAttribute('readonly')){
-            $('is_filterable_in_search').disabled = false;
+        if (visualOptionsPanel) {
+            var visualPanel = visualOptionsPanel.up('.fieldset');
+
+            if (frontendInput && frontendInput.value=='swatch_visual') {
+                visualPanel.show();
+                rg.get('swatch-visual-options-panel', function () {
+                    jQuery('#swatch-visual-options-panel').trigger('render');
+                });
+            } else {
+                visualPanel.hide();
+            }
         }
-        if($('backend_type') && $('backend_type').options){
-            for(var i=0;i<$('backend_type').options.length;i++){
-                if($('backend_type').options[i].value=='int') $('backend_type').selectedIndex = i;
+        if (textOptionsPanel) {
+            var textPanel = textOptionsPanel.up('.fieldset');
+
+            if (frontendInput && frontendInput.value=='swatch_text') {
+                textPanel.show();
+                rg.get('swatch-text-options-panel', function () {
+                    jQuery('#swatch-text-options-panel').trigger('render');
+                });
+            } else {
+                textPanel.hide();
             }
         }
     }
-    else {
-        if($('is_filterable')){
-            $('is_filterable').selectedIndex=0;
-            $('is_filterable').disabled = true;
-        }
-        if($('is_filterable_in_search')){
-            $('is_filterable_in_search').disabled = true;
+
+    function bindAttributeInputType () {
+        var isFilterable = $('is_filterable'),
+            isFilterableInSearch = $('is_filterable_in_search'),
+            backendType = $('backend_type'),
+            usedForSortBy = $('used_for_sort_by'),
+            frontendClass = $('frontend_class'),
+            selectFields = ['select', 'multiselect', 'price', 'swatch_text', 'swatch_visual'];
+
+        checkOptionsPanelVisibility();
+        switchDefaultValueField();
+        if (frontendInput
+            && jQuery.inArray(frontendInput.value, selectFields) >= 0
+        ) {
+            if (isFilterable && !isFilterable.getAttribute('readonly')) {
+                isFilterable.disabled = false;
+            }
+            if (isFilterableInSearch && !isFilterableInSearch.getAttribute('readonly')) {
+                isFilterableInSearch.disabled = false;
+            }
+            if (backendType && backendType.options) {
+                for (var i=0; i<backendType.options.length; i++) {
+                    if (backendType.options[i].value=='int') {
+                        backendType.selectedIndex = i;
+                    }
+                }
+            }
+        } else {
+            if (isFilterable) {
+                isFilterable.selectedIndex=0;
+                isFilterable.disabled = true;
+            }
+            if (isFilterableInSearch) {
+                isFilterableInSearch.disabled = true;
+            }
         }
-    }
 
-    if ($('frontend_input') && !$('frontend_input').disabled
-        && ($('frontend_input').value=='swatch_text' || $('frontend_input').value=='swatch_visual')
-    ) {
-        $('used_in_product_listing').value = 1;
-        $('is_visible_on_front').value = 1;
-        $('update_product_preview_image').value = 1;
-    }
+        if (frontendInput && !frontendInput.disabled
+            && (frontendInput.value=='swatch_text' || frontendInput.value=='swatch_visual')
+        ) {
+            $('used_in_product_listing').value = 1;
+            $('is_visible_on_front').value = 1;
+            $('update_product_preview_image').value = 1;
+        }
 
-    if ($('frontend_input') && ($('frontend_input').value=='multiselect'
-        || $('frontend_input').value=='gallery'
-        || $('frontend_input').value=='textarea')) {
-        if ($('used_for_sort_by')) {
-            $('used_for_sort_by').disabled = true;
+        if (frontendInput && (frontendInput.value=='multiselect'
+            || frontendInput.value=='gallery'
+            || frontendInput.value=='textarea')) {
+            if (usedForSortBy) {
+                usedForSortBy.disabled = true;
+            }
+        } else {
+            if (usedForSortBy && !usedForSortBy.getAttribute('readonly')) {
+                usedForSortBy.disabled = false;
+            }
         }
-    }
-    else {
-        if ($('used_for_sort_by') && !$('used_for_sort_by').getAttribute('readonly')) {
-            $('used_for_sort_by').disabled = false;
+
+        if (jQuery('#frontend_input').val() == 'swatch_text') {
+            jQuery('.swatch-text-field-0').addClass('required-option');
+        } else {
+            jQuery('.swatch-text-field-0').removeClass('required-option');
         }
-    }
 
-    if (jQuery('#frontend_input').val() == 'swatch_text') {
-        jQuery('.swatch-text-field-0').addClass('required-option');
-    } else {
-        jQuery('.swatch-text-field-0').removeClass('required-option');
-    }
+        setRowVisibility('is_wysiwyg_enabled', false);
+        setRowVisibility('is_html_allowed_on_front', false);
 
-    setRowVisibility('is_wysiwyg_enabled', false);
-    setRowVisibility('is_html_allowed_on_front', false);
+        switch (frontendInput.value) {
+            case 'textarea':
+                setRowVisibility('is_wysiwyg_enabled', true);
+                if ($('is_wysiwyg_enabled').value == '0') {
+                    setRowVisibility('is_html_allowed_on_front', true);
+                    $('is_html_allowed_on_front').disabled = false;
+                }
+                frontendClass.value = '';
+                frontendClass.disabled = true;
+                break;
+            case 'text':
+                setRowVisibility('is_html_allowed_on_front', true);
+                $('is_html_allowed_on_front').disabled = false;
 
-    switch ($('frontend_input').value) {
-        case 'textarea':
-            setRowVisibility('is_wysiwyg_enabled', true);
-            if($('is_wysiwyg_enabled').value == '0'){
+                if (!frontendClass.getAttribute('readonly')) {
+                    frontendClass.disabled = false;
+                }
+                break;
+            case 'select':
+            case 'multiselect':
                 setRowVisibility('is_html_allowed_on_front', true);
                 $('is_html_allowed_on_front').disabled = false;
-            }
-            $('frontend_class').value = '';
-            $('frontend_class').disabled = true;
-            break;
-        case 'text':
-            setRowVisibility('is_html_allowed_on_front', true);
-            $('is_html_allowed_on_front').disabled = false;
+                frontendClass.value = '';
+                frontendClass.disabled = true;
+                break;
+            default:
+                frontendClass.value = '';
+                frontendClass.disabled = true;
+        }
 
-            if (!$('frontend_class').getAttribute('readonly')) {
-                $('frontend_class').disabled = false;
-            }
-            break;
-        case 'select':
-        case 'multiselect':
-            setRowVisibility('is_html_allowed_on_front', true);
-            $('is_html_allowed_on_front').disabled = false;
-            $('frontend_class').value = '';
-            $('frontend_class').disabled = true;
-            break;
-        default:
-            $('frontend_class').value = '';
-            $('frontend_class').disabled = true;
+        switchIsFilterable();
     }
 
-    switchIsFilterable();
-}
-
-function switchIsFilterable()
-{
-    if ($('is_filterable')) {
-        if ($('is_filterable').selectedIndex == 0) {
-            $('position').disabled = true;
-        } else {
-            if (!$('position').getAttribute('readonly')){
-                $('position').disabled = false;
+    function switchIsFilterable () {
+        var isFilterable = $('is_filterable'),
+            position = $('position');
+        if (isFilterable) {
+            if (isFilterable.selectedIndex == 0) {
+                position.disabled = true;
+            } else {
+                if (!position.getAttribute('readonly')) {
+                    position.disabled = false;
+                }
             }
         }
     }
-}
-
-function switchDefaultValueField()
-{
-    if (!$('frontend_input')) {
-        return;
-    }
 
-    var currentValue = $('frontend_input').value;
-
-    var defaultValueTextVisibility = false;
-    var defaultValueTextareaVisibility = false;
-    var defaultValueDateVisibility = false;
-    var defaultValueYesnoVisibility = false;
-    var scopeVisibility = true;
-
-    /* swatch attributes */
-    var useProductImageForSwatch = false;
-    var defaultValueUpdateImage = false;
-
-
-    switch (currentValue) {
-        case 'select':
-            optionDefaultInputType = 'radio';
-            break;
-
-        case 'multiselect':
-            optionDefaultInputType = 'checkbox';
-            break;
-
-        case 'date':
-            defaultValueDateVisibility = true;
-            break;
+    function switchDefaultValueField () {
+        if (!frontendInput) {
+            return;
+        }
 
-        case 'boolean':
-            defaultValueYesnoVisibility = true;
-            break;
+        var currentValue = frontendInput.value;
+
+        var defaultValueTextVisibility = false;
+        var defaultValueTextareaVisibility = false;
+        var defaultValueDateVisibility = false;
+        var defaultValueYesnoVisibility = false;
+        var scopeVisibility = true;
+
+        /* swatch attributes */
+        var useProductImageForSwatch = false;
+        var defaultValueUpdateImage = false;
+
+        var optionDefaultInputType = '';
+
+
+        switch (currentValue) {
+            case 'select':
+                optionDefaultInputType = 'radio';
+                break;
+
+            case 'multiselect':
+                optionDefaultInputType = 'checkbox';
+                break;
+
+            case 'date':
+                defaultValueDateVisibility = true;
+                break;
+
+            case 'boolean':
+                defaultValueYesnoVisibility = true;
+                break;
+
+            case 'textarea':
+                defaultValueTextareaVisibility = true;
+                break;
+
+            case 'media_image':
+                defaultValueTextVisibility = false;
+                break;
+            case 'price':
+                scopeVisibility = false;
+                break;
+            case 'swatch_visual':
+                useProductImageForSwatch = true;
+                defaultValueUpdateImage = true;
+                defaultValueTextVisibility = false;
+                break;
+            case 'swatch_text':
+                useProductImageForSwatch = false;
+                defaultValueUpdateImage = true;
+                defaultValueTextVisibility = false;
+                break;
+            default:
+                defaultValueTextVisibility = true;
+                break;
+        }
 
-        case 'textarea':
-            defaultValueTextareaVisibility = true;
-            break;
+        switch (currentValue) {
+            case 'media_image':
+                getFrontTab().hide();
 
-        case 'media_image':
-            defaultValueTextVisibility = false;
+                setRowVisibility('is_required', false);
+                setRowVisibility('is_unique', false);
+                setRowVisibility('frontend_class', false);
             break;
-        case 'price':
-            scopeVisibility = false;
-        case 'swatch_visual':
-            useProductImageForSwatch = true;
-            defaultValueUpdateImage = true;
-            defaultValueTextVisibility = false;
-            break;
-        case 'swatch_text':
-            useProductImageForSwatch = false;
-            defaultValueUpdateImage = true;
-            defaultValueTextVisibility = false;
-            break;
-        default:
-            defaultValueTextVisibility = true;
-            break;
-    }
-
-    switch (currentValue) {
-        case 'media_image':
-            getFrontTab().hide();
-
-            setRowVisibility('is_required', false);
-            setRowVisibility('is_unique', false);
-            setRowVisibility('frontend_class', false);
-        break;
-
-        <?php foreach ($this->helper('Magento\Catalog\Helper\Data')->getAttributeHiddenFields() as $type => $fields): ?>
+        <?php $hiddenFields = $block->helper('Magento\Catalog\Helper\Data')->getAttributeHiddenFields() ?>
+        <?php foreach ($hiddenFields as $type => $fields): ?>
         <?php if (in_array($type, array('swatch_visual', 'swatch_text'))) continue ?>
-            case '<?php /* @escapeNotVerified */ echo $type; ?>':
+            case '<?php echo $block->escapeHtml($type); ?>':
                 <?php foreach ($fields as $one): ?>
                     <?php if ($one == '_front_fieldset'): ?>
                         getFrontTab().hide();
@@ -268,145 +280,141 @@ function switchDefaultValueField()
                     <?php elseif ($one == '_scope'): ?>
                         scopeVisibility = false;
                     <?php else: ?>
-                        setRowVisibility('<?php /* @escapeNotVerified */ echo $one; ?>', false);
+                        setRowVisibility('<?php echo $block->escapeHtml($one); ?>', false);
                     <?php endif; ?>
                 <?php endforeach; ?>
             break;
         <?php endforeach; ?>
 
-        default:
-            getFrontTab().show();
+            default:
+                getFrontTab().show();
 
-            showDefaultRows();
-        break;
-    }
+                showDefaultRows();
+            break;
+        }
 
-    setRowVisibility('default_value_text', defaultValueTextVisibility);
-    setRowVisibility('default_value_textarea', defaultValueTextareaVisibility);
-    setRowVisibility('default_value_date', defaultValueDateVisibility);
-    setRowVisibility('default_value_yesno', defaultValueYesnoVisibility);
-    setRowVisibility('is_global', scopeVisibility);
+        setRowVisibility('default_value_text', defaultValueTextVisibility);
+        setRowVisibility('default_value_textarea', defaultValueTextareaVisibility);
+        setRowVisibility('default_value_date', defaultValueDateVisibility);
+        setRowVisibility('default_value_yesno', defaultValueYesnoVisibility);
+        setRowVisibility('is_global', scopeVisibility);
 
-    /* swatch attributes */
-    setRowVisibility('use_product_image_for_swatch', useProductImageForSwatch);
-    setRowVisibility('update_product_preview_image', defaultValueUpdateImage);
+        /* swatch attributes */
+        setRowVisibility('use_product_image_for_swatch', useProductImageForSwatch);
+        setRowVisibility('update_product_preview_image', defaultValueUpdateImage);
 
-    var elems = document.getElementsByName('default[]');
-    for (var i = 0; i < elems.length; i++) {
-        elems[i].type = optionDefaultInputType;
-    }
-}
-
-function showDefaultRows()
-{
-    setRowVisibility('is_required', true);
-    setRowVisibility('is_unique', true);
-    setRowVisibility('frontend_class', true);
-}
-
-function setRowVisibility(id, isVisible)
-{
-    if ($(id)) {
-        var td = $(id).parentNode;
-        var tr = $(td.parentNode);
-
-        if (isVisible) {
-            tr.show();
-        } else {
-            tr.blur();
-            tr.hide();
+        var elems = document.getElementsByName('default[]');
+        for (var i = 0; i < elems.length; i++) {
+            elems[i].type = optionDefaultInputType;
         }
     }
-}
 
+    function showDefaultRows () {
+        setRowVisibility('is_required', true);
+        setRowVisibility('is_unique', true);
+        setRowVisibility('frontend_class', true);
+    }
 
-function updateRequriedOptions()
-{
-    if ($F('frontend_input')=='select' && $F('is_required')==1) {
-        $('option-count-check').addClassName('required-options-count');
-    } else {
-        $('option-count-check').removeClassName('required-options-count');
+    function setRowVisibility (id, isVisible) {
+        if ($(id)) {
+            var td = $(id).parentNode;
+            var tr = $(td.parentNode);
+
+            if (isVisible) {
+                tr.show();
+            } else {
+                tr.blur();
+                tr.hide();
+            }
+        }
     }
-}
 
-function saveAttributeInNewSet(promptMessage)
-{
-    var newAttributeSetName;
 
-    prompt({
-        content: promptMessage,
-        actions: {
-            confirm: function(val) {
-                newAttributeSetName = val;
+    function updateRequriedOptions () {
+        if ($F('frontend_input')=='select' && $F('is_required')==1) {
+            $('option-count-check').addClassName('required-options-count');
+        } else {
+            $('option-count-check').removeClassName('required-options-count');
+        }
+    }
 
-                if (!newAttributeSetName) {
-                    return;
-                }
+    function saveAttributeInNewSet (promptMessage) {
+        var newAttributeSetName;
 
-                var rules = ['required-entry', 'validate-no-html-tags'];
-                for (var i = 0; i < rules.length; i++) {
-                    if (!jQuery.validator.methods[rules[i]](newAttributeSetName)) {
-                        alert({
-                            content: jQuery.validator.messages[rules[i]]
-                        });
+        prompt({
+            content: promptMessage,
+            actions: {
+                confirm: function (val) {
+                    newAttributeSetName = val;
 
+                    if (!newAttributeSetName) {
                         return;
                     }
-                }
 
-                var newAttributeSetNameInputId = 'new_attribute_set_name';
-
-                if ($(newAttributeSetNameInputId)) {
-                    $(newAttributeSetNameInputId).value = newAttributeSetName;
-                } else {
-                    $('edit_form').insert({
-                        top: new Element('input', {
-                            type: 'hidden',
-                            id: newAttributeSetNameInputId,
-                            name: 'new_attribute_set_name',
-                            value: newAttributeSetName
-                        })
-                    });
+                    var rules = ['required-entry', 'validate-no-html-tags'];
+                    for (var i = 0; i < rules.length; i++) {
+                        if (!jQuery.validator.methods[rules[i]](newAttributeSetName)) {
+                            alert({
+                                content: jQuery.validator.messages[rules[i]]
+                            });
+
+                            return;
+                        }
+                    }
+
+                    var newAttributeSetNameInputId = 'new_attribute_set_name';
+
+                    if ($(newAttributeSetNameInputId)) {
+                        $(newAttributeSetNameInputId).value = newAttributeSetName;
+                    } else {
+                        $('edit_form').insert({
+                            top: new Element('input', {
+                                type: 'hidden',
+                                id: newAttributeSetNameInputId,
+                                name: 'new_attribute_set_name',
+                                value: newAttributeSetName
+                            })
+                        });
+                    }
+                    // Temporary solution will replaced after refactoring of attributes functionality
+                    jQuery('#edit_form').triggerHandler('save');
                 }
-                // Temporary solution will replaced after refactoring of attributes functionality
-                jQuery('#edit_form').triggerHandler('save');
             }
-        }
+        });
+    }
+
+    if (frontendInput) {
+        Event.observe(frontendInput, 'change', updateRequriedOptions);
+        Event.observe(frontendInput, 'change', bindAttributeInputType);
+    }
+
+    if ($('is_filterable')) {
+        Event.observe($('is_filterable'), 'change', switchIsFilterable);
+    }
+
+    if ($('is_required')) {
+        Event.observe($('is_required'), 'change', updateRequriedOptions);
+    }
+
+    jQuery(function ($) {
+        bindAttributeInputType();
+        // @todo: refactor collapsable component
+        $('.attribute-popup .collapse, [data-role="advanced_fieldset-content"]')
+            .collapsable()
+            .collapse('hide');
     });
-}
-
-if($('frontend_input')){
-    Event.observe($('frontend_input'), 'change', updateRequriedOptions);
-    Event.observe($('frontend_input'), 'change', bindAttributeInputType);
-}
-
-if ($('is_filterable')) {
-    Event.observe($('is_filterable'), 'change', switchIsFilterable);
-}
-
-if ($('is_required')) {
-    Event.observe($('is_required'), 'change', updateRequriedOptions);
-}
-
-jQuery(function($) {
-    bindAttributeInputType();
-    // @todo: refactor collapsable component
-    $('.attribute-popup .collapse, [data-role="advanced_fieldset-content"]')
-        .collapsable()
-        .collapse('hide');
-});
 
-window.saveAttributeInNewSet = saveAttributeInNewSet;
-window.updateRequriedOptions = updateRequriedOptions;
-window.setRowVisibility = setRowVisibility;
-window.showDefaultRows = showDefaultRows;
-window.switchDefaultValueField = switchDefaultValueField;
-window.switchIsFilterable = switchIsFilterable;
-window.switchIsFilterable = switchIsFilterable;
-window.bindAttributeInputType = bindAttributeInputType;
-window.checkOptionsPanelVisibility = checkOptionsPanelVisibility;
-window.getFrontTab = getFrontTab;
-window.toggleApplyVisibility = toggleApplyVisibility;
+    window.saveAttributeInNewSet = saveAttributeInNewSet;
+    window.updateRequriedOptions = updateRequriedOptions;
+    window.setRowVisibility = setRowVisibility;
+    window.showDefaultRows = showDefaultRows;
+    window.switchDefaultValueField = switchDefaultValueField;
+    window.switchIsFilterable = switchIsFilterable;
+    window.switchIsFilterable = switchIsFilterable;
+    window.bindAttributeInputType = bindAttributeInputType;
+    window.checkOptionsPanelVisibility = checkOptionsPanelVisibility;
+    window.getFrontTab = getFrontTab;
+    window.toggleApplyVisibility = toggleApplyVisibility;
 
 });
 </script>
diff --git a/dev/tests/integration/etc/di/preferences/ce.php b/dev/tests/integration/etc/di/preferences/ce.php
index fdf00fa6d59b02c3529babbe44abc54722a3c109..674ebe49afca3f053fe8664ffa14c9c09881aefe 100644
--- a/dev/tests/integration/etc/di/preferences/ce.php
+++ b/dev/tests/integration/etc/di/preferences/ce.php
@@ -19,4 +19,5 @@ return [
     'Magento\Framework\View\LayoutInterface' => 'Magento\TestFramework\View\Layout',
     'Magento\Framework\App\ResourceConnection\ConnectionAdapterInterface' =>
         'Magento\TestFramework\Db\ConnectionAdapter',
+    'Magento\Framework\Filesystem\DriverInterface' => 'Magento\Framework\Filesystem\Driver\File'
 ];
diff --git a/dev/tests/integration/framework/Magento/TestFramework/Application.php b/dev/tests/integration/framework/Magento/TestFramework/Application.php
index 523c4de04e492b3a64d26133a1e4d66ba16122ff..966451fb35855c784a9533829fd319e7ad659082 100644
--- a/dev/tests/integration/framework/Magento/TestFramework/Application.php
+++ b/dev/tests/integration/framework/Magento/TestFramework/Application.php
@@ -441,6 +441,7 @@ class Application
         $dirs = \Magento\Framework\App\Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS;
         $this->_ensureDirExists($this->installDir);
         $this->_ensureDirExists($this->_configDir);
+        $this->_ensureDirExists($this->_initParams[$dirs][DirectoryList::PUB][DirectoryList::PATH]);
         $this->_ensureDirExists($this->_initParams[$dirs][DirectoryList::MEDIA][DirectoryList::PATH]);
         $this->_ensureDirExists($this->_initParams[$dirs][DirectoryList::STATIC_VIEW][DirectoryList::PATH]);
         $this->_ensureDirExists($this->_initParams[$dirs][DirectoryList::VAR_DIR][DirectoryList::PATH]);
@@ -624,14 +625,15 @@ class Application
         $customDirs = [
             DirectoryList::CONFIG => [$path => "{$this->installDir}/etc"],
             DirectoryList::VAR_DIR => [$path => $var],
-            DirectoryList::MEDIA => [$path => "{$this->installDir}/media"],
-            DirectoryList::STATIC_VIEW => [$path => "{$this->installDir}/pub_static"],
+            DirectoryList::MEDIA => [$path => "{$this->installDir}/pub/media"],
+            DirectoryList::STATIC_VIEW => [$path => "{$this->installDir}/pub/static"],
             DirectoryList::GENERATION => [$path => "{$var}/generation"],
             DirectoryList::CACHE => [$path => "{$var}/cache"],
             DirectoryList::LOG => [$path => "{$var}/log"],
             DirectoryList::SESSION => [$path => "{$var}/session"],
             DirectoryList::TMP => [$path => "{$var}/tmp"],
             DirectoryList::UPLOAD => [$path => "{$var}/upload"],
+            DirectoryList::PUB => [$path => "{$this->installDir}/pub"],
         ];
         return $customDirs;
     }
diff --git a/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Export/RowCustomizerTest.php b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Export/RowCustomizerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..c5adb5a6bd03988ab693459abb3c4991bd93fb75
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/BundleImportExport/Model/Export/RowCustomizerTest.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\BundleImportExport\Model\Export;
+
+/**
+ * @magentoAppArea adminhtml
+ */
+class RowCustomizerTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\BundleImportExport\Model\Export\RowCustomizer
+     */
+    private $model;
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    private $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $this->model = $this->objectManager->create(
+            'Magento\BundleImportExport\Model\Export\RowCustomizer'
+        );
+    }
+
+    /**
+     * @magentoDataFixture Magento/Bundle/_files/product.php
+     */
+    public function testPrepareData()
+    {
+        $collection = $this->objectManager->get('Magento\Catalog\Model\ResourceModel\Product\Collection');
+        $select = (string)$collection->getSelect();
+        $this->model->prepareData($collection, [1, 2, 3, 4]);
+        $this->assertEquals($select, (string)$collection->getSelect());
+        $result = $this->model->addData([], 3);
+        $this->assertArrayHasKey('bundle_price_type', $result);
+        $this->assertArrayHasKey('bundle_sku_type', $result);
+        $this->assertArrayHasKey('bundle_price_view', $result);
+        $this->assertArrayHasKey('bundle_weight_type', $result);
+        $this->assertArrayHasKey('bundle_values', $result);
+        $this->assertContains('sku=simple,', $result['bundle_values']);
+    }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php
index b87c30697f2c4b1eb4e7711f5d474e4f84258499..8a48ef56be186487d0720dc91a86fdafddcf7664 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/multiselect_attribute.php
@@ -32,6 +32,7 @@ $attribute->setData(
         'used_for_sort_by' => 0,
         'frontend_label' => ['Multiselect Attribute'],
         'backend_type' => 'varchar',
+        'backend_model' => 'Magento\Eav\Model\Entity\Attribute\Backend\ArrayBackend',
         'option' => [
             'value' => [
                 'option_1' => ['Option 1'],
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..604e317213c9d76357e66200e7b976221abcb9e0
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Export/ProductTest.php
@@ -0,0 +1,174 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\CatalogImportExport\Model\Export;
+
+/**
+ * @magentoDataFixtureBeforeTransaction Magento/Catalog/_files/enable_reindex_schedule.php
+ */
+class ProductTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\CatalogImportExport\Model\Export\Product
+     */
+    protected $_model;
+
+    /**
+     * Stock item attributes which must be exported
+     *
+     * @var array
+     */
+    public static $stockItemAttributes = [
+        'qty',
+        'min_qty',
+        'use_config_min_qty',
+        'is_qty_decimal',
+        'backorders',
+        'use_config_backorders',
+        'min_sale_qty',
+        'use_config_min_sale_qty',
+        'max_sale_qty',
+        'use_config_max_sale_qty',
+        'is_in_stock',
+        'notify_stock_qty',
+        'use_config_notify_stock_qty',
+        'manage_stock',
+        'use_config_manage_stock',
+        'use_config_qty_increments',
+        'qty_increments',
+        'use_config_enable_qty_inc',
+        'enable_qty_increments',
+        'is_decimal_divided',
+    ];
+
+    protected function setUp()
+    {
+        parent::setUp();
+
+        $this->_model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\CatalogImportExport\Model\Export\Product'
+        );
+    }
+
+    /**
+     * @magentoDataFixture Magento/CatalogImportExport/_files/product_export_data.php
+     */
+    public function testExport()
+    {
+        $this->_model->setWriter(
+            \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+                'Magento\ImportExport\Model\Export\Adapter\Csv'
+            )
+        );
+        $this->assertNotEmpty($this->_model->export());
+    }
+
+    /**
+     * Verify that all stock item attribute values are exported (aren't equal to empty string)
+     *
+     * @covers \Magento\CatalogImportExport\Model\Export\Product::export
+     * @magentoDataFixture Magento/CatalogImportExport/_files/product_export_data.php
+     */
+    public function testExportStockItemAttributesAreFilled()
+    {
+        $fileWrite = $this->getMock('Magento\Framework\Filesystem\File\Write', [], [], '', false);
+        $directoryMock = $this->getMock('Magento\Framework\Filesystem\Directory\Write', [], [], '', false);
+        $directoryMock->expects($this->any())->method('getParentDirectory')->will($this->returnValue('some#path'));
+        $directoryMock->expects($this->any())->method('isWritable')->will($this->returnValue(true));
+        $directoryMock->expects($this->any())->method('isFile')->will($this->returnValue(true));
+        $directoryMock->expects(
+            $this->any()
+        )->method(
+            'readFile'
+        )->will(
+            $this->returnValue('some string read from file')
+        );
+        $directoryMock->expects($this->once())->method('openFile')->will($this->returnValue($fileWrite));
+
+        $filesystemMock = $this->getMock('Magento\Framework\Filesystem', [], [], '', false);
+        $filesystemMock->expects($this->once())->method('getDirectoryWrite')->will($this->returnValue($directoryMock));
+
+        $exportAdapter = new \Magento\ImportExport\Model\Export\Adapter\Csv($filesystemMock);
+
+        $this->_model->setWriter($exportAdapter)->export();
+    }
+
+    /**
+     * Verify header columns (that stock item attributes column headers are present)
+     *
+     * @param array $headerColumns
+     */
+    public function verifyHeaderColumns(array $headerColumns)
+    {
+        foreach (self::$stockItemAttributes as $stockItemAttribute) {
+            $this->assertContains(
+                $stockItemAttribute,
+                $headerColumns,
+                "Stock item attribute {$stockItemAttribute} is absent among header columns"
+            );
+        }
+    }
+
+    /**
+     * Verify row data (stock item attribute values)
+     *
+     * @param array $rowData
+     */
+    public function verifyRow(array $rowData)
+    {
+        foreach (self::$stockItemAttributes as $stockItemAttribute) {
+            $this->assertNotSame(
+                '',
+                $rowData[$stockItemAttribute],
+                "Stock item attribute {$stockItemAttribute} value is empty string"
+            );
+        }
+    }
+
+    /**
+     * Verifies if exception processing works properly
+     *
+     * @magentoDataFixture Magento/CatalogImportExport/_files/product_export_data.php
+     */
+    public function testExceptionInGetExportData()
+    {
+        $exception = new \Exception('Error');
+
+        $rowCustomizerMock = $this->getMockBuilder('Magento\CatalogImportExport\Model\Export\RowCustomizerInterface')
+            ->disableOriginalConstructor()
+            ->getMock();
+
+        $loggerMock = $this->getMockBuilder('\Psr\Log\LoggerInterface')->getMock();
+
+        $directoryMock = $this->getMock('Magento\Framework\Filesystem\Directory\Write', [], [], '', false);
+        $directoryMock->expects($this->any())->method('getParentDirectory')->will($this->returnValue('some#path'));
+        $directoryMock->expects($this->any())->method('isWritable')->will($this->returnValue(true));
+
+        $filesystemMock = $this->getMock('Magento\Framework\Filesystem', [], [], '', false);
+        $filesystemMock->expects($this->once())->method('getDirectoryWrite')->will($this->returnValue($directoryMock));
+
+        $exportAdapter = new \Magento\ImportExport\Model\Export\Adapter\Csv($filesystemMock);
+
+        $rowCustomizerMock->expects($this->once())->method('prepareData')->willThrowException($exception);
+        $loggerMock->expects($this->once())->method('critical')->with($exception);
+
+        $collection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            '\Magento\Catalog\Model\ResourceModel\Product\Collection'
+        );
+
+        /** @var \Magento\CatalogImportExport\Model\Export\Product $model */
+        $model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\CatalogImportExport\Model\Export\Product',
+            [
+                'rowCustomizer' => $rowCustomizerMock,
+                'logger' => $loggerMock,
+                'collection' => $collection
+            ]
+        );
+
+        $data = $model->setWriter($exportAdapter)->export();
+        $this->assertEmpty($data);
+    }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..e8205523ec8d5f8a3a6e42b0133a6b2272c2f4e8
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
@@ -0,0 +1,821 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+// @codingStandardsIgnoreFile
+
+/**
+ * Test class for \Magento\CatalogImportExport\Model\Import\Product
+ *
+ * The "CouplingBetweenObjects" warning is caused by tremendous complexity of the original class
+ *
+ */
+namespace Magento\CatalogImportExport\Model\Import;
+
+use Magento\Framework\App\Bootstrap;
+use Magento\Framework\App\Filesystem\DirectoryList;
+use Magento\ImportExport\Model\Import;
+
+/**
+ * Class ProductTest
+ *
+ * @magentoDataFixtureBeforeTransaction Magento/Catalog/_files/enable_reindex_schedule.php
+ */
+class ProductTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\CatalogImportExport\Model\Import\Product
+     */
+    protected $_model;
+
+    /**
+     * @var \Magento\CatalogImportExport\Model\Import\Uploader
+     */
+    protected $_uploader;
+
+    /**
+     * @var \Magento\CatalogImportExport\Model\Import\UploaderFactory
+     */
+    protected $_uploaderFactory;
+
+    /**
+     * @var \Magento\CatalogInventory\Model\Spi\StockStateProviderInterface|\PHPUnit_Framework_MockObject_MockObject
+     */
+    protected $_stockStateProvider;
+
+    protected function setUp()
+    {
+        $this->_model = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\CatalogImportExport\Model\Import\Product'
+        );
+    }
+
+    /**
+     * Options for assertion
+     *
+     * @var array
+     */
+    protected $_assertOptions = [
+        'is_require' => '_custom_option_is_required',
+        'price' => '_custom_option_price',
+        'sku' => '_custom_option_sku',
+        'sort_order' => '_custom_option_sort_order',
+    ];
+
+    /**
+     * Option values for assertion
+     *
+     * @var array
+     */
+    protected $_assertOptionValues = ['title', 'price', 'sku'];
+
+    /**
+     * Test if visibility properly saved after import
+     *
+     * @magentoDataFixture Magento/Catalog/_files/multiple_products.php
+     * @magentoAppIsolation enabled
+     */
+    public function testSaveProductsVisibility()
+    {
+        $existingProductIds = [10, 11, 12];
+        $productsBeforeImport = [];
+        foreach ($existingProductIds as $productId) {
+            $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+                'Magento\Catalog\Model\Product'
+            );
+            $product->load($productId);
+            $productsBeforeImport[] = $product;
+        }
+
+        $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+            ->create('Magento\Framework\Filesystem');
+        $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+
+        $source = new \Magento\ImportExport\Model\Import\Source\Csv(
+            __DIR__ . '/_files/products_to_import.csv',
+            $directory
+        );
+        $errors = $this->_model->setParameters(
+            ['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, 'entity' => 'catalog_product']
+        )->setSource(
+            $source
+        )->validateData();
+
+        $this->assertTrue($errors->getErrorsCount() == 0);
+
+        $this->_model->importData();
+
+        /** @var $productBeforeImport \Magento\Catalog\Model\Product */
+        foreach ($productsBeforeImport as $productBeforeImport) {
+            /** @var $productAfterImport \Magento\Catalog\Model\Product */
+            $productAfterImport = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+                'Magento\Catalog\Model\Product'
+            );
+            $productAfterImport->load($productBeforeImport->getId());
+
+            $this->assertEquals($productBeforeImport->getVisibility(), $productAfterImport->getVisibility());
+            unset($productAfterImport);
+        }
+
+        unset($productsBeforeImport, $product);
+    }
+
+    /**
+     * Test if stock item quantity properly saved after import
+     *
+     * @magentoDataFixture Magento/Catalog/_files/multiple_products.php
+     * @magentoAppIsolation enabled
+     */
+    public function testSaveStockItemQty()
+    {
+        $existingProductIds = [10, 11, 12];
+        $stockItems = [];
+        foreach ($existingProductIds as $productId) {
+            /** @var $stockRegistry \Magento\CatalogInventory\Model\StockRegistry */
+            $stockRegistry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+                'Magento\CatalogInventory\Model\StockRegistry'
+            );
+
+            $stockItem = $stockRegistry->getStockItem($productId, 1);
+            $stockItems[$productId] = $stockItem;
+        }
+
+        $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+            ->create('Magento\Framework\Filesystem');
+        $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+        $source = new \Magento\ImportExport\Model\Import\Source\Csv(
+            __DIR__ . '/_files/products_to_import.csv',
+            $directory
+        );
+        $errors = $this->_model->setParameters(
+            ['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, 'entity' => 'catalog_product']
+        )->setSource(
+            $source
+        )->validateData();
+
+        $this->assertTrue($errors->getErrorsCount() == 0);
+
+        $this->_model->importData();
+
+        /** @var $stockItmBeforeImport \Magento\CatalogInventory\Model\Stock\Item */
+        foreach ($stockItems as $productId => $stockItmBeforeImport) {
+            /** @var $stockRegistry \Magento\CatalogInventory\Model\StockRegistry */
+            $stockRegistry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+                'Magento\CatalogInventory\Model\StockRegistry'
+            );
+
+            $stockItemAfterImport = $stockRegistry->getStockItem($productId, 1);
+
+            $this->assertEquals($stockItmBeforeImport->getQty(), $stockItemAfterImport->getQty());
+            $this->assertEquals(1, $stockItemAfterImport->getIsInStock());
+            unset($stockItemAfterImport);
+        }
+
+        unset($stockItems, $stockItem);
+    }
+
+    /**
+     * Test if stock state properly changed after import
+     *
+     * @magentoDataFixture Magento/Catalog/_files/multiple_products.php
+     * @magentoAppIsolation enabled
+     */
+    public function testStockState()
+    {
+        $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+            ->create('Magento\Framework\Filesystem');
+        $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+        $source = new \Magento\ImportExport\Model\Import\Source\Csv(
+            __DIR__ . '/_files/products_to_import_with_qty.csv',
+            $directory
+        );
+
+        $errors = $this->_model->setParameters(
+            ['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, 'entity' => 'catalog_product']
+        )->setSource(
+            $source
+        )->validateData();
+
+        $this->assertTrue($errors->getErrorsCount() == 0);
+        $this->_model->importData();
+    }
+
+    /**
+     * Tests adding of custom options with existing and new product
+     *
+     * @magentoDataFixture Magento/Catalog/_files/product_simple.php
+     * @dataProvider getBehaviorDataProvider
+     * @param string $importFile
+     * @param string $sku
+     * @magentoAppIsolation enabled
+     */
+    public function testSaveCustomOptions($importFile, $sku)
+    {
+        $pathToFile = __DIR__ . '/_files/' . $importFile;
+        $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+            ->create('Magento\Framework\Filesystem');
+        $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+
+        $source = new \Magento\ImportExport\Model\Import\Source\Csv($pathToFile, $directory);
+        $errors = $this->_model->setParameters(
+            ['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, 'entity' => 'catalog_product']
+        )->setSource(
+            $source
+        )->validateData();
+
+        $this->assertTrue($errors->getErrorsCount() == 0);
+        $this->_model->importData();
+
+        /** @var \Magento\Catalog\Model\Product $productModel */
+        $productModel = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\Catalog\Model\Product'
+        );
+        $product = $productModel->loadByAttribute('sku', $sku);
+
+        $this->assertInstanceOf('Magento\Catalog\Model\Product', $product);
+        $options = $product->getProductOptionsCollection();
+
+        $expectedData = $this->getExpectedOptionsData($pathToFile);
+        $expectedData = $this->mergeWithExistingData($expectedData, $options);
+        $actualData = $this->getActualOptionsData($options);
+
+        // assert of equal type+titles
+        $expectedOptions = $expectedData['options'];
+        // we need to save key values
+        $actualOptions = $actualData['options'];
+        sort($expectedOptions);
+        sort($actualOptions);
+        $this->assertEquals($expectedOptions, $actualOptions);
+
+        // assert of options data
+        $this->assertCount(count($expectedData['data']), $actualData['data']);
+        $this->assertCount(count($expectedData['values']), $actualData['values']);
+        foreach ($expectedData['options'] as $expectedId => $expectedOption) {
+            $elementExist = false;
+            // find value in actual options and values
+            foreach ($actualData['options'] as $actualId => $actualOption) {
+                if ($actualOption == $expectedOption) {
+                    $elementExist = true;
+                    $this->assertEquals($expectedData['data'][$expectedId], $actualData['data'][$actualId]);
+                    if (array_key_exists($expectedId, $expectedData['values'])) {
+                        $this->assertEquals($expectedData['values'][$expectedId], $actualData['values'][$actualId]);
+                    }
+                    unset($actualData['options'][$actualId]);
+                    // remove value in case of duplicating key values
+                    break;
+                }
+            }
+            $this->assertTrue($elementExist, 'Element must exist.');
+        }
+    }
+
+    /**
+     * Data provider for test 'testSaveCustomOptionsDuplicate'
+     *
+     * @return array
+     */
+    public function getBehaviorDataProvider()
+    {
+        return [
+            'Append behavior with existing product' => [
+                '$importFile' => 'product_with_custom_options.csv',
+                '$sku' => 'simple',
+            ],
+            'Append behavior with new product' => [
+                '$importFile' => 'product_with_custom_options_new.csv',
+                '$sku' => 'simple_new',
+            ]
+        ];
+    }
+
+    /**
+     * Test if datetime properly saved after import
+     *
+     * @magentoDataFixture Magento/Catalog/_files/multiple_products.php
+     * @magentoAppIsolation enabled
+     */
+    public function testSaveDatetimeAttribute()
+    {
+        $existingProductIds = [10, 11, 12];
+        $productsBeforeImport = [];
+        foreach ($existingProductIds as $productId) {
+            $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+                'Magento\Catalog\Model\Product'
+            );
+            $product->load($productId);
+            $productsBeforeImport[$product->getSku()] = $product;
+        }
+
+        $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+            ->create('Magento\Framework\Filesystem');
+        $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+
+        $source = new \Magento\ImportExport\Model\Import\Source\Csv(
+            __DIR__ . '/_files/products_to_import_with_datetime.csv',
+            $directory
+        );
+        $errors = $this->_model->setParameters(
+            ['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, 'entity' => 'catalog_product']
+        )->setSource(
+            $source
+        )->validateData();
+
+        $this->assertTrue($errors->getErrorsCount() == 0);
+
+        $this->_model->importData();
+
+        $source->rewind();
+        foreach ($source as $row) {
+            /** @var $productAfterImport \Magento\Catalog\Model\Product */
+            $productBeforeImport = $productsBeforeImport[$row['sku']];
+
+            /** @var $productAfterImport \Magento\Catalog\Model\Product */
+            $productAfterImport = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+                'Magento\Catalog\Model\Product'
+            );
+            $productAfterImport->load($productBeforeImport->getId());
+            $this->assertEquals(
+                @strtotime($row['news_from_date']),
+                @strtotime($productAfterImport->getNewsFromDate())
+            );
+            unset($productAfterImport);
+        }
+        unset($productsBeforeImport, $product);
+    }
+
+    /**
+     * Returns expected product data: current id, options, options data and option values
+     *
+     * @param string $pathToFile
+     * @return array
+     */
+    protected function getExpectedOptionsData($pathToFile)
+    {
+        $productData = $this->csvToArray(file_get_contents($pathToFile));
+        $expectedOptionId = 0;
+        $expectedOptions = [];
+        // array of type and title types, key is element ID
+        $expectedData = [];
+        // array of option data
+        $expectedValues = [];
+        // array of option values data
+        foreach ($productData['data'] as $data) {
+            if (!empty($data['_custom_option_type']) && !empty($data['_custom_option_title'])) {
+                $lastOptionKey = $data['_custom_option_type'] . '|' . $data['_custom_option_title'];
+                $expectedOptionId++;
+                $expectedOptions[$expectedOptionId] = $lastOptionKey;
+                $expectedData[$expectedOptionId] = [];
+                foreach ($this->_assertOptions as $assertKey => $assertFieldName) {
+                    if (array_key_exists($assertFieldName, $data)) {
+                        $expectedData[$expectedOptionId][$assertKey] = $data[$assertFieldName];
+                    }
+                }
+            }
+            if (!empty($data['_custom_option_row_title']) && empty($data['_custom_option_store'])) {
+                $optionData = [];
+                foreach ($this->_assertOptionValues as $assertKey) {
+                    $valueKey = \Magento\CatalogImportExport\Model\Import\Product\Option::COLUMN_PREFIX .
+                        'row_' .
+                        $assertKey;
+                    $optionData[$assertKey] = $data[$valueKey];
+                }
+                $expectedValues[$expectedOptionId][] = $optionData;
+            }
+        }
+
+        return [
+            'id' => $expectedOptionId,
+            'options' => $expectedOptions,
+            'data' => $expectedData,
+            'values' => $expectedValues
+        ];
+    }
+
+    /**
+     * Updates expected options data array with existing unique options data
+     *
+     * @param array $expected
+     * @param \Magento\Catalog\Model\ResourceModel\Product\Option\Collection $options
+     * @return array
+     */
+    protected function mergeWithExistingData(
+        array $expected,
+        \Magento\Catalog\Model\ResourceModel\Product\Option\Collection $options
+    ) {
+        $expectedOptionId = $expected['id'];
+        $expectedOptions = $expected['options'];
+        $expectedData = $expected['data'];
+        $expectedValues = $expected['values'];
+        foreach ($options->getItems() as $option) {
+            $optionKey = $option->getType() . '|' . $option->getTitle();
+            if (!in_array($optionKey, $expectedOptions)) {
+                $expectedOptionId++;
+                $expectedOptions[$expectedOptionId] = $optionKey;
+                $expectedData[$expectedOptionId] = $this->getOptionData($option);
+                if ($optionValues = $this->getOptionValues($option)) {
+                    $expectedValues[$expectedOptionId] = $optionValues;
+                }
+            }
+        }
+
+        return [
+            'id' => $expectedOptionId,
+            'options' => $expectedOptions,
+            'data' => $expectedData,
+            'values' => $expectedValues
+        ];
+    }
+
+    /**
+     *  Returns actual product data: current id, options, options data and option values
+     *
+     * @param \Magento\Catalog\Model\ResourceModel\Product\Option\Collection $options
+     * @return array
+     */
+    protected function getActualOptionsData(\Magento\Catalog\Model\ResourceModel\Product\Option\Collection $options)
+    {
+        $actualOptionId = 0;
+        $actualOptions = [];
+        // array of type and title types, key is element ID
+        $actualData = [];
+        // array of option data
+        $actualValues = [];
+        // array of option values data
+        /** @var $option \Magento\Catalog\Model\Product\Option */
+        foreach ($options->getItems() as $option) {
+            $lastOptionKey = $option->getType() . '|' . $option->getTitle();
+            $actualOptionId++;
+            if (!in_array($lastOptionKey, $actualOptions)) {
+                $actualOptions[$actualOptionId] = $lastOptionKey;
+                $actualData[$actualOptionId] = $this->getOptionData($option);
+                if ($optionValues = $this->getOptionValues($option)) {
+                    $actualValues[$actualOptionId] = $optionValues;
+                }
+            }
+        }
+        return [
+            'id' => $actualOptionId,
+            'options' => $actualOptions,
+            'data' => $actualData,
+            'values' => $actualValues
+        ];
+    }
+
+    /**
+     * Retrieve option data
+     *
+     * @param \Magento\Catalog\Model\Product\Option $option
+     * @return array
+     */
+    protected function getOptionData(\Magento\Catalog\Model\Product\Option $option)
+    {
+        $result = [];
+        foreach (array_keys($this->_assertOptions) as $assertKey) {
+            $result[$assertKey] = $option->getData($assertKey);
+        }
+        return $result;
+    }
+
+    /**
+     * Retrieve option values or false for options which has no values
+     *
+     * @param \Magento\Catalog\Model\Product\Option $option
+     * @return array|bool
+     */
+    protected function getOptionValues(\Magento\Catalog\Model\Product\Option $option)
+    {
+        $values = $option->getValues();
+        if (!empty($values)) {
+            $result = [];
+            /** @var $value \Magento\Catalog\Model\Product\Option\Value */
+            foreach ($values as $value) {
+                $optionData = [];
+                foreach ($this->_assertOptionValues as $assertKey) {
+                    if ($value->hasData($assertKey)) {
+                        $optionData[$assertKey] = $value->getData($assertKey);
+                    }
+                }
+                $result[] = $optionData;
+            }
+            return $result;
+        }
+
+        return false;
+    }
+
+    /**
+     * @magentoDataIsolation enabled
+     * @magentoDataFixture mediaImportImageFixture
+     *
+     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+     */
+    public function testSaveMediaImage()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+            ->create('Magento\Framework\Filesystem');
+        $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+
+        $source = new \Magento\ImportExport\Model\Import\Source\Csv(
+            __DIR__ . '/_files/import_media.csv',
+            $directory
+        );
+        $this->_model->setParameters(
+            [
+                'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND,
+                'entity' => 'catalog_product',
+                'import_images_file_dir' => 'pub/media/import'
+            ]
+        );
+        $appParams = \Magento\TestFramework\Helper\Bootstrap::getInstance()->getBootstrap()->getApplication()->getInitParams()[Bootstrap::INIT_PARAM_FILESYSTEM_DIR_PATHS];
+        $uploader = $this->_model->getUploader();
+
+        $destDir = $directory->getRelativePath($appParams[DirectoryList::MEDIA][DirectoryList::PATH] . '/catalog/product');
+        $tmpDir = $directory->getRelativePath($appParams[DirectoryList::MEDIA][DirectoryList::PATH] . '/import');
+
+        $directory->create($destDir);
+        $this->assertTrue($uploader->setDestDir($destDir));
+        $this->assertTrue($uploader->setTmpDir($tmpDir));
+        $errors = $this->_model->setSource(
+            $source
+        )->validateData();
+
+        $this->assertTrue($errors->getErrorsCount() == 0);
+        $this->_model->importData();
+
+        $resource = $objectManager->get('Magento\Catalog\Model\ResourceModel\Product');
+        $productId = $resource->getIdBySku('simple_new');
+
+        $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\Catalog\Model\Product'
+        );
+        $product->load($productId);
+        $gallery = $product->getMediaGalleryImages();
+        $this->assertInstanceOf('Magento\Framework\Data\Collection', $gallery);
+        $items = $gallery->getItems();
+        $this->assertCount(1, $items);
+        $item = array_pop($items);
+        $this->assertInstanceOf('Magento\Framework\DataObject', $item);
+        $this->assertEquals('/m/a/magento_image.jpg', $item->getFile());
+        $this->assertEquals('Image Label', $item->getLabel());
+    }
+
+    /**
+     * Copy a fixture image into media import directory
+     */
+    public static function mediaImportImageFixture()
+    {
+        /** @var \Magento\Framework\Filesystem\Directory\Write $mediaDirectory */
+        $mediaDirectory = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(
+            'Magento\Framework\Filesystem'
+        )->getDirectoryWrite(
+            DirectoryList::MEDIA
+        );
+        $mediaDirectory->create('import');
+        $dirPath = $mediaDirectory->getAbsolutePath('import');
+        copy(__DIR__ . '/../../../../Magento/Catalog/_files/magento_image.jpg', "{$dirPath}/magento_image.jpg");
+    }
+
+    /**
+     * Cleanup media import and catalog directories
+     */
+    public static function mediaImportImageFixtureRollback()
+    {
+        /** @var \Magento\Framework\Filesystem\Directory\Write $mediaDirectory */
+        $mediaDirectory = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(
+            'Magento\Framework\Filesystem'
+        )->getDirectoryWrite(
+            DirectoryList::MEDIA
+        );
+        $mediaDirectory->delete('import');
+        $mediaDirectory->delete('catalog');
+    }
+
+    /**
+     * Export CSV string to array
+     *
+     * @param string $content
+     * @param mixed $entityId
+     * @return array
+     */
+    protected function csvToArray($content, $entityId = null)
+    {
+        $data = ['header' => [], 'data' => []];
+
+        $lines = str_getcsv($content, "\n");
+        foreach ($lines as $index => $line) {
+            if ($index == 0) {
+                $data['header'] = str_getcsv($line);
+            } else {
+                $row = array_combine($data['header'], str_getcsv($line));
+                if (!is_null($entityId) && !empty($row[$entityId])) {
+                    $data['data'][$row[$entityId]] = $row;
+                } else {
+                    $data['data'][] = $row;
+                }
+            }
+        }
+        return $data;
+    }
+
+    /**
+     * Tests that no products imported if source file contains errors
+     *
+     * In this case, the second product data has an invalid attribute set.
+     *
+     * @magentoDbIsolation enabled
+     */
+    public function testInvalidSkuLink()
+    {
+        // import data from CSV file
+        $pathToFile = __DIR__ . '/_files/products_to_import_invalid_attribute_set.csv';
+        $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Filesystem'
+        );
+        $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+        $source = new \Magento\ImportExport\Model\Import\Source\Csv($pathToFile, $directory);
+        $errors = $this->_model->setParameters(
+            [
+                'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND,
+                'entity' => 'catalog_product'
+            ]
+        )->setSource(
+            $source
+        )->validateData();
+
+        $this->assertTrue($errors->getErrorsCount() == 1);
+        $this->assertEquals(
+            'Invalid value for Attribute Set column (set doesn\'t exist?)',
+            $errors->getErrorByRowNumber(1)[0]->getErrorMessage()
+        );
+        $this->_model->importData();
+
+        $productCollection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\Catalog\Model\ResourceModel\Product\Collection'
+        );
+
+        $products = [];
+        /** @var $product \Magento\Catalog\Model\Product */
+        foreach ($productCollection as $product) {
+            $products[$product->getSku()] = $product;
+        }
+        $this->assertArrayNotHasKey("simple1", $products, "Simple Product should not have been imported");
+        $this->assertArrayNotHasKey("simple3", $products, "Simple Product 3 should not have been imported");
+        $this->assertArrayNotHasKey("simple2", $products, "Simple Product2 should not have been imported");
+    }
+
+    /**
+     * @magentoDataFixture Magento/Catalog/_files/products_with_multiselect_attribute.php
+     * @magentoAppIsolation enabled
+     */
+    public function testValidateInvalidMultiselectValues()
+    {
+        $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Filesystem'
+        );
+        $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+        $source = new \Magento\ImportExport\Model\Import\Source\Csv(
+            __DIR__ . '/_files/products_with_invalid_multiselect_values.csv',
+            $directory
+        );
+        $errors = $this->_model->setParameters(
+            ['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, 'entity' => 'catalog_product']
+        )->setSource(
+            $source
+        )->validateData();
+
+        $this->assertTrue($errors->getErrorsCount() == 1);
+        $this->assertEquals(
+            "Value for 'multiselect_attribute' attribute contains incorrect value, "
+            ."see acceptable values on settings specified for Admin",
+            $errors->getErrorByRowNumber(1)[0]->getErrorMessage()
+        );
+    }
+
+    /**
+     * @magentoDataFixture Magento/Catalog/_files/categories.php
+     * @magentoDataFixture Magento/Store/_files/website.php
+     * @magentoDataFixture Magento/Store/_files/core_fixturestore.php
+     * @magentoDataFixture Magento/Catalog/Model/Layer/Filter/_files/attribute_with_option.php
+     * @magentoDataFixture Magento/ConfigurableProduct/_files/configurable_attribute.php
+     * @magentoAppIsolation enabled
+     */
+    public function testProductsWithMultipleStores()
+    {
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+
+        $filesystem = $objectManager->create('Magento\Framework\Filesystem');
+        $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+
+        $source = new \Magento\ImportExport\Model\Import\Source\Csv(
+            __DIR__ . '/_files/products_multiple_stores.csv',
+            $directory
+        );
+        $errors = $this->_model->setParameters(
+            ['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND, 'entity' => 'catalog_product']
+        )->setSource(
+            $source
+        )->validateData();
+
+        $this->assertTrue($errors->getErrorsCount() == 0);
+
+        $this->_model->importData();
+
+        /** @var \Magento\Catalog\Model\Product $product */
+        $product = $objectManager->create('Magento\Catalog\Model\Product');
+        $id = $product->getIdBySku('Configurable 03');
+        $product->load($id);
+        $this->assertEquals('1', $product->getHasOptions());
+
+        $objectManager->get('Magento\Store\Model\StoreManagerInterface')->setCurrentStore('fixturestore');
+
+        /** @var \Magento\Catalog\Model\Product $simpleProduct */
+        $simpleProduct = $objectManager->create('Magento\Catalog\Model\Product');
+        $id = $simpleProduct->getIdBySku('Configurable 03-Option 1');
+        $simpleProduct->load($id);
+        $this->assertTrue(count($simpleProduct->getWebsiteIds()) == 2);
+        $this->assertEquals('Option Label', $simpleProduct->getAttributeText('attribute_with_option'));
+    }
+
+    /**
+     * @magentoDbIsolation enabled
+     */
+    public function testProductWithInvalidWeight()
+    {
+        // import data from CSV file
+        $pathToFile = __DIR__ . '/_files/product_to_import_invalid_weight.csv';
+        $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Filesystem'
+        );
+
+        $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+        $source = new \Magento\ImportExport\Model\Import\Source\Csv($pathToFile, $directory);
+        $errors = $this->_model->setSource(
+            $source
+        )->setParameters(
+            ['behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND]
+        )->validateData();
+
+        $this->assertTrue($errors->getErrorsCount() == 1);
+        $this->assertEquals(
+            "Value for 'weight' attribute contains incorrect value",
+            $errors->getErrorByRowNumber(1)[0]->getErrorMessage()
+        );
+    }
+
+    /**
+     * @magentoAppArea adminhtml
+     * @dataProvider categoryTestDataProvider
+     * @magentoDbIsolation enabled
+     * @magentoAppIsolation enabled
+     */
+    public function testProductCategories($fixture, $separator)
+    {
+        // import data from CSV file
+        $pathToFile = __DIR__ . '/_files/' . $fixture;
+        $filesystem = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\Framework\Filesystem'
+        );
+
+        $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+        $source = new \Magento\ImportExport\Model\Import\Source\Csv($pathToFile, $directory);
+        $errors = $this->_model->setSource(
+            $source
+        )->setParameters(
+            [
+                'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND,
+                'entity' => 'catalog_product',
+                Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR => $separator
+            ]
+        )->validateData();
+
+        $this->assertTrue($errors->getErrorsCount() == 0);
+        $this->_model->importData();
+
+        $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $resource = $objectManager->get('Magento\Catalog\Model\ResourceModel\Product');
+        $productId = $resource->getIdBySku('simple1');
+        $this->assertTrue(is_numeric($productId));
+        /** @var \Magento\Catalog\Model\Product $product */
+        $product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\Catalog\Model\Product'
+        );
+        $product->load($productId);
+        $this->assertFalse($product->isObjectNew());
+        $categories = $product->getCategoryIds();
+        $this->assertTrue(count($categories) == 2);
+    }
+
+    /**
+     * @return array
+     */
+    public function categoryTestDataProvider()
+    {
+        return [
+            ['import_new_categories_default_separator.csv', ','],
+            ['import_new_categories_custom_separator.csv', '|']
+        ];
+    }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media.csv
new file mode 100644
index 0000000000000000000000000000000000000000..608b7569826e7614b3454054d596077f6e3038d1
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_media.csv
@@ -0,0 +1,2 @@
+sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,crosssell_skus,upsell_skus,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,associated_skus
+simple_new,,Default,simple,,base,New Product,,,,1,Taxable Goods,"Catalog, Search",10,,,,new-product,New Product,New Product,New Product ,magento_image.jpg,,magento_image.jpg,,magento_image.jpg,,10/20/15 07:05,10/20/15 07:05,,,Block after Info Column,,,,,,,,,,,,,"has_options=1,quantity_and_stock_status=In Stock,required_options=1",100,0,1,0,0,1,1,1,10000,1,1,1,1,1,0,1,1,0,0,0,1,,,,magento_image.jpg,Image Label,,,,,,,,
\ No newline at end of file
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_new_categories_custom_separator.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_new_categories_custom_separator.csv
new file mode 100644
index 0000000000000000000000000000000000000000..c0deb9b289fd33ec9dbb2314f4494fd24d13dd65
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_new_categories_custom_separator.csv
@@ -0,0 +1,2 @@
+sku,product_type,store_view_code,name,price,attribute_set_code,categories,product_websites
+simple1,simple,,"simple 2",25,Default,"Default Category/Category 1|Default Category/Category 2",base
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_new_categories_default_separator.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_new_categories_default_separator.csv
new file mode 100644
index 0000000000000000000000000000000000000000..9bf687dec4632f79a42b3859e0297c21f7984030
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/import_new_categories_default_separator.csv
@@ -0,0 +1,2 @@
+sku,product_type,store_view_code,name,price,attribute_set_code,categories,product_websites,url_key
+simple1,simple,,"simple 1",25,Default,"Default Category/Category 1,Default Category/Category 2",base,simple1-ds
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_new.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_new.csv
index 5211dd576ae6a16a71cd9f3e03a8b9b6b000db78..9c4e884646335f655a6995dff184af3091b116ab 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_new.csv
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/product_with_custom_options_new.csv
@@ -1,2 +1,2 @@
-sku,store_view_code,attribute_set_code,product_type,categories,product_websites,color,cost,country_of_manufacture,created_at,custom_design,custom_design_from,custom_design_to,custom_layout_update,description,gallery,gift_message_available,has_options,image,image_label,manufacturer,media_gallery,meta_description,meta_keyword,meta_title,minimal_price,msrp,msrp_display_actual_price_type,msrp_enabled,name,news_from_date,news_to_date,options_container,page_layout,price,required_options,short_description,small_image,small_image_label,special_from_date,special_price,special_to_date,product_online,tax_class_name,thumbnail,thumbnail_label,updated_at,url_key,url_path,visibility,weight,qty,min_qty,use_config_min_qty,is_qty_decimal,backorders,use_config_backorders,min_sale_qty,use_config_min_sale_qty,max_sale_qty,use_config_max_sale_qty,is_in_stock,notify_stock_qty,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,_related_sku,_related_position,_crosssell_sku,_crosssell_position,_upsell_sku,_upsell_position,_associated_sku,_associated_default_qty,_associated_position,_tier_price_website,_tier_price_customer_group,_tier_price_qty,_tier_price_price,_group_price_website,_group_price_customer_group,_group_price_price,_media_attribute_id,_media_image,_media_label,_media_position,_media_is_disabled,custom_options
-simple_new,,Default,simple,,,base,,,,,,,,,,,1,,,,,,,,,,,,New Product,,,Block after Info Column,,10,1,,,,,,,1,Taxable Goods,,,2012-07-13 12:04:17,new-product,new-product.html,"Catalog, Search",,100,0,1,0,0,1,1,1,0,1,1,,1,0,1,1,0,1,0,0,,,,,,,,,,,,,,,,,,,,,,"name=Test Field Title,type=field,required=1;sku=1-text,price=0,price_type=fixed|name=Test Date and Time Title,type=date_time,required=1,price=2,option_title=custom option 1,sku=2-date|name=New Select,type=drop_down,required=1,price=3,option_title=Option 1,sku=3-1-select|name=New Select,type=drop_down,required=1,price=3,option_title=Option 2,sku=3-2-select|name=New Radio,type=radio,required=1,price=3,option_title=Option 1,sku=4-1-radio|name=New Radio,type=radio,required=1,price=3,option_title=Option 2,sku=4-2-radio"
+sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,crosssell_skus,upsell_skus,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,associated_skus
+simple_new,,Default,simple,,base,"New Product",,,,1,"Taxable Goods","Catalog, Search",10.0000,,,,new-product,"New Product","New Product","New Product ",,,,,,,"2015-10-20 07:05:38","2015-10-20 07:05:38",,,"Block after Info Column",,,,,,,,,,,,,"has_options=1,quantity_and_stock_status=In Stock,required_options=1",100.0000,0.0000,1,0,0,1,1.0000,1,10000.0000,1,1,1.0000,1,1,0,1,1.0000,0,0,0,1,,,,,,,"name=New Radio,type=radio,required=1,price=3.0000,price_type=fixed,sku=4-1-radio,option_title=Option 1|name=New Radio,type=radio,required=1,price=3.0000,price_type=fixed,sku=4-2-radio,option_title=Option 2|name=New Select,type=drop_down,required=1,price=3.0000,price_type=fixed,sku=3-1-select,option_title=Option 1|name=New Select,type=drop_down,required=1,price=3.0000,price_type=fixed,sku=3-2-select,option_title=Option2|name=Test Date and Time Title,type=date_time,required=1,price=2.0000,price_type=fixed,sku=2-date|name=Test Field Title,type=field,required=1,price=0.0000,price_type=fixed,sku=1-text",,,,,,
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_multiple_stores.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_multiple_stores.csv
index 10299936a27ea2d3283e4d9b49ca93c9ea8be71f..d7876f612233ccdcaf5c37fe78aded58edc75ad9 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_multiple_stores.csv
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_multiple_stores.csv
@@ -1,5 +1,5 @@
-sku,store_view_code,attribute_set_code,product_type,categories,product_websites,test_configurable,cost,country_of_manufacture,created_at,custom_design,custom_design_from,custom_design_to,custom_layout_update,description,gallery,gift_message_available,gift_wrapping_available,gift_wrapping_price,has_options,image,image_label,is_returnable,manufacturer,meta_description,meta_keyword,meta_title,minimal_price,msrp,msrp_display_actual_price_type,msrp_enabled,name,news_from_date,news_to_date,options_container,page_layout,price,quantity_and_stock_status,related_tgtr_position_behavior,related_tgtr_position_limit,required_options,short_description,attribute_with_option,small_image,small_image_label,special_from_date,special_price,special_to_date,product_online,tax_class_name,thumbnail,thumbnail_label,updated_at,upsell_tgtr_position_behavior,upsell_tgtr_position_limit,url_key,visibility,weight,qty,min_qty,use_config_min_qty,is_qty_decimal,backorders,use_config_backorders,min_sale_qty,use_config_min_sale_qty,max_sale_qty,use_config_max_sale_qty,is_in_stock,notify_stock_qty,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,_related_sku,_related_position,_crosssell_sku,_crosssell_position,_upsell_sku,_upsell_position,configurable_variations,configurable_variation_prices,configurable_variation_labels
-Configurable 03-option_0,,Default,virtual,Default Category/Category 1/Category 1.1,base,Option 1,,,2014-06-13 07:34:02,,,,,,,,,,0,,,Use config,,Configurable 03 ,Configurable 03,Configurable 03,,,Use config,Use config,Configurable 03-option_0,,,Block after Info Column,,10,In Stock,,,0,,Option Label,,,,,,1,Taxable Goods,,,2014-06-13 07:35:59,,,configurable-03-option11,Search,,99999,0,1,0,0,1,1,1,0,1,1,,1,1,1,1,0,1,0,0,,,,,,,,,
-Configurable 03-option_0,fixturestore,Default,virtual,Default Category/Category 1/Category 1.1,base,Option 1,,,2014-06-13 07:34:02,,,,,,,,,,,,,,,,,,,,,,Configurable 03-option_0,,,Block after Info Column,,10,,,,0,,Option Label,,,,,,,,,,,,,,Search,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
-Configurable 03-option_1,,Default,virtual,Default Category/Category 1/Category 1.1,base,Option 2,,,2014-06-13 07:34:05,,,,,,,,,,0,,,Use config,,Configurable 03 ,Configurable 03,Configurable 03,,,Use config,Use config,Configurable 03-option_1,,,Block after Info Column,,10,In Stock,,,0,,,,,,,,1,Taxable Goods,,,2014-06-13 07:34:05,,,configurable-03-option12,Search,,99999,0,1,0,0,1,1,1,0,1,1,,1,1,1,1,0,1,0,0,,,,,,,,,
-Configurable 03,,Default,configurable,Default Category/Category 1/Category 1.1,base,,,,2014-06-13 07:34:07,,,,,,,,,,1,,,Use config,,Configurable 03 ,Configurable 03,Configurable 03,,,Use config,Use config,Configurable 03,,,Block after Info Column,,10,In Stock,,,1,,,,,,,,1,Taxable Goods,,,2014-06-13 07:36:32,,,Configurable-03-11,"Catalog, Search",,0,0,1,0,0,1,1,1,0,1,1,,1,1,1,1,0,1,0,0,,,,,,,"sku=Configurable 03-option_0,test_configurable=Option 1,display=1|sku=Configurable 03-option_1,test_configurable=Option 2,display=1","name=test_configurable,value=Option 1,price=1|name=test_configurable,value=Option 2,price=2",
+sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,crosssell_skus,upsell_skus,additional_images,additional_image_labels,hide_from_product_page,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,configurable_variations,configurable_variation_labels,associated_skus
+"Configurable 03-Option 1",,Default,simple,"Default Category/Category 1/Category 1.1","base,test","Configurable 03-Option 1",,,,1,"Taxable Goods","Not Visible Individually",10.0000,,,,configurable-03-option-1,"Configurable 03","Configurable 03","Configurable 03 ",,,,,,,"2015-10-23 00:35:03","2015-10-23 01:00:25",,,"Block after Info Column",,,,,,,,,,,"Use config",,"attribute_with_option=Option Label,has_options=0,quantity_and_stock_status=In Stock,required_options=0,test_configurable=Option 1",99999.0000,0.0000,0,0,0,1,1.0000,0,0.0000,0,1,,1,1,0,0,1.0000,0,0,0,1,,,,,,,,,,,,,,
+"Configurable 03-Option 1",fixturestore,Default,"simple",,,"Configurable 03-Option 1 fixturestore",,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
+"Configurable 03-Option 2",,Default,simple,"Default Category/Category 1/Category 1.1","base,test","Configurable 03-Option 2",,,,1,"Taxable Goods","Not Visible Individually",10.0000,,,,configurable-03-option-2,"Configurable 03","Configurable 03","Configurable 03 ",,,,,,,"2015-10-23 00:35:03","2015-10-23 00:35:03",,,"Block after Info Column",,,,,,,,,,,"Use config",,"has_options=0,quantity_and_stock_status=In Stock,required_options=0,test_configurable=Option 2",99999.0000,0.0000,0,0,0,1,1.0000,0,0.0000,0,1,,1,1,0,0,1.0000,0,0,0,1,,,,,,,,,,,,,,
+"Configurable 03",,Default,configurable,"Default Category/Category 1/Category 1.1","base,test","Configurable 03",,,,1,"Taxable Goods","Catalog, Search",10.0000,,,,configurable-03,"Configurable 03","Configurable 03","Configurable 03 ",,,,,,,"2015-10-23 00:35:03","2015-10-23 00:35:03",,,"Block after Info Column",,,,,,,,,,,"Use config",,"has_options=1,quantity_and_stock_status=In Stock,required_options=0",0.0000,0.0000,0,0,0,1,1.0000,0,0.0000,0,1,,1,0,0,0,1.0000,0,0,0,1,,,,,,,,,,,,"sku=Configurable 03-Option 1,test_configurable=Option 1|sku=Configurable 03-Option 2,test_configurable=Option 2",test_configurable=test_configurable,
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import.csv
index d8d879193d82e4526a46ee09b9226a5885bd2e45..a107fdff250ec48bcd00ca179f56225df2fbe2c3 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import.csv
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import.csv
@@ -1,5 +1,4 @@
-sku,store_view_code,name,price
-simple1,,"simple 1",25
-simple1,German,"simple 1 German",
-simple2,,"simple 2",34
-simple3,,"simple 3",58
+sku,product_type,store_view_code,name,price,attribute_set_code
+simple1,simple,,"simple 1",25,Default
+simple2,simple,,"simple 2",34,Default
+simple3,simple,,"simple 3",58,Default
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_invalid_attribute_set.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_invalid_attribute_set.csv
index ae16f0735a163d9138cf7124cd5b88d9e078bed6..59b8b7b4895603491648cb1f98beb98bb3c19dc6 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_invalid_attribute_set.csv
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_invalid_attribute_set.csv
@@ -1,4 +1,4 @@
-sku,price,name,product_type,_attribute_set,_upsell_sku,description
+sku,price,name,product_type,attribute_set_code,_upsell_sku,description
 simple1,25,"Simple Product",simple,Default,,description
-simple2,NULL,"Simple Product2",invalid attribute set,Default,,HiThere
+simple2,10,"Simple Product2",simple,invalid attribute set,,description
 simple3,58,"Simple Product 3",simple,Default,simple2,description
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_datetime.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_datetime.csv
index a45a871fab6918889074f01fbeafa142f727eab0..ae7e27dbd95c0984548142c93c5d4e35afa0cc20 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_datetime.csv
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_datetime.csv
@@ -1,4 +1,4 @@
 sku,news_from_date
-simple1,"1/1/2015 20:00 pm"
-simple2,"10/8/2012 23:58 pm"
-simple3,"12/31/1998 17:30 pm"
+simple1,"1/1/2015 20:00"
+simple2,"10/8/2012 23:58"
+simple3,"12/31/1998 17:30"
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_qty.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_qty.csv
index 7872a16727763867dcf4d28723dd26a5d970f6c3..3abf9cecb6ff1e1e34256d25b1eacb8959734575 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_qty.csv
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_qty.csv
@@ -1,2 +1,2 @@
-sku,store_view_code,name,price,qty,product_type,attribute_set_code,manage_stock
-simple1,,"simple 1",25,0.0000,simple,Default,1
+sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,crosssell_skus,upsell_skus,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,associated_skus
+simple_new,,Default,simple,,base,New Product,,,,1,Taxable Goods,"Catalog, Search",10,,,,new-product,New Product,New Product,New Product ,,,,,,,10/20/15 07:05,10/20/15 07:05,,,Block after Info Column,,,,,,,,,,,,,"has_options=1,quantity_and_stock_status=In Stock,required_options=1",100,0,1,0,0,1,1,1,10000,1,1,1,1,1,0,1,1,0,0,0,1,,,,,,,,,,,,,
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_with_invalid_multiselect_values.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_with_invalid_multiselect_values.csv
index b6da8449c9ccd80e53d52aab642c3e7f51fb5184..a7cc848ea4bcfed1f7ed9c8f67de728f49aa40fe 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_with_invalid_multiselect_values.csv
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_with_invalid_multiselect_values.csv
@@ -1,3 +1,3 @@
-sku,store_view_code,product_type,name,price,multiselect_attribute
-simple_ms_1,,simple,"With Multiselect 1",10,Option 1
-simple_ms_2,,simple,"With Multiselect 2",10,"Option 5,Option 2,Option 3"
+sku,store_view_code,product_type,name,price,additional_attributes
+simple_ms_1,,simple,"With Multiselect 1",10,"multiselect_attribute=Option 1"
+simple_ms_2,,simple,"With Multiselect 2",10,"multiselect_attribute=Option 5|Option 2|Option 3"
diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Export/RowCustomizerTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Export/RowCustomizerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..9dc933734fd5ca8447851bb1097246738318867b
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/ConfigurableImportExport/Model/Export/RowCustomizerTest.php
@@ -0,0 +1,48 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\ConfigurableImportExport\Model\Export;
+
+/**
+ * @magentoAppArea adminhtml
+ */
+class RowCustomizerTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\ConfigurableImportExport\Model\Export\RowCustomizer
+     */
+    private $model;
+
+    /**
+     * @var \Magento\Framework\ObjectManagerInterface
+     */
+    private $objectManager;
+
+    protected function setUp()
+    {
+        $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+        $this->model = $this->objectManager->create(
+            'Magento\ConfigurableImportExport\Model\Export\RowCustomizer'
+        );
+    }
+
+    /**
+     * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
+     */
+    public function testPrepareData()
+    {
+        $collection = $this->objectManager->get('Magento\Catalog\Model\ResourceModel\Product\Collection');
+        $select = (string)$collection->getSelect();
+        $this->model->prepareData($collection, [1, 2, 3, 4]);
+        $this->assertEquals($select, (string)$collection->getSelect());
+        $result = $this->model->addData([], 1);
+        $this->assertArrayHasKey('configurable_variations', $result);
+        $this->assertArrayHasKey('configurable_variation_labels', $result);
+        $this->assertEquals(
+            'sku=simple_10,test_configurable=Option 1|sku=simple_20,test_configurable=Option 2',
+            $result['configurable_variations']
+        );
+    }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Reports/Model/ResourceModel/Review/Product/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Reports/Model/ResourceModel/Review/Product/CollectionTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d8c0eddd7ec592ed9902c641b9b8e48f111aa582
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Reports/Model/ResourceModel/Review/Product/CollectionTest.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * Copyright © 2015 Magento. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+namespace Magento\Reports\Model\ResourceModel\Review\Product;
+
+/**
+ * @magentoAppArea adminhtml
+ */
+class CollectionTest extends \PHPUnit_Framework_TestCase
+{
+    /**
+     * @var \Magento\Reports\Model\ResourceModel\Review\Product\Collection
+     */
+    private $_collection;
+
+    protected function setUp()
+    {
+        $this->_collection = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+            'Magento\Reports\Model\ResourceModel\Review\Product\Collection'
+        );
+    }
+
+    public function testGetSelect()
+    {
+        $select = (string)$this->_collection->getSelect();
+        $search = '/SUM\(table_rating.percent\)\/COUNT\(table_rating.rating_id\) AS `avg_rating`'
+            . '[\s\S]+SUM\(table_rating.percent_approved\)\/COUNT\(table_rating.rating_id\) AS `avg_rating_approved`'
+            . '[\s\S]+LEFT JOIN `.*rating_option_vote_aggregated` AS `table_rating`/';
+
+        $this->assertRegExp($search, $select);
+    }
+}
diff --git a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_constants.php b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_constants.php
index 1a0a6503809d5e2717bac49bcf46d39e750a23da..a6c1b3688018ef9e080faa7423bc192c90f7b659 100644
--- a/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_constants.php
+++ b/dev/tests/static/testsuite/Magento/Test/Legacy/_files/obsolete_constants.php
@@ -945,4 +945,9 @@ return [
         'THEMES',
         'Magento\Framework\App\Filesystem\DirectoryList'
     ],
+    [
+        'DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR',
+        'Magento\CatalogImportExport\Model\Import\Product',
+        'Magento\ImportExport\Model\Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR'
+    ]
 ];