Skip to content

Commit c8a0fd6

Browse files
author
Yevhen Miroshnychenko
committed
MAGETWO-92986: Write Logs for Failed Process of Generating Factories in Extensions
1 parent 01103d9 commit c8a0fd6

File tree

6 files changed

+146
-14
lines changed

6 files changed

+146
-14
lines changed

dev/tests/integration/testsuite/Magento/Framework/Code/GeneratorTest.php

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
use Magento\Framework\Code\Generator;
99
use Magento\Framework\App\Filesystem\DirectoryList;
10+
use Magento\Framework\Filesystem;
1011
use Magento\Framework\Interception\Code\Generator as InterceptionGenerator;
1112
use Magento\Framework\ObjectManager\Code\Generator as DIGenerator;
1213
use Magento\Framework\Api\Code\Generator\ExtensionAttributesInterfaceFactoryGenerator;
@@ -36,20 +37,25 @@ class GeneratorTest extends \PHPUnit\Framework\TestCase
3637
/**
3738
* @var \Magento\Framework\Filesystem\Directory\Write
3839
*/
39-
protected $varDirectory;
40+
protected $generatedDirectory;
41+
42+
/**
43+
* @var \Magento\Framework\Filesystem\Directory\Read
44+
*/
45+
protected $logDirectory;
4046

4147
protected function setUp()
4248
{
4349
$objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
44-
$this->varDirectory = $objectManager->get(
45-
\Magento\Framework\Filesystem::class
46-
)->getDirectoryWrite(
47-
DirectoryList::VAR_DIR
48-
);
49-
$generationDirectory = $this->varDirectory->getAbsolutePath('generation');
50+
/** @var Filesystem $filesystem */
51+
$filesystem = $objectManager->get(\Magento\Framework\Filesystem::class);
52+
$this->generatedDirectory = $filesystem->getDirectoryWrite(DirectoryList::GENERATED_CODE);
53+
$this->logDirectory = $filesystem->getDirectoryRead(DirectoryList::LOG);
54+
$this->generatedDirectory->create();
55+
$generatedDirectory = $this->generatedDirectory->getAbsolutePath();
5056
$this->_ioObject = new \Magento\Framework\Code\Generator\Io(
5157
new \Magento\Framework\Filesystem\Driver\File(),
52-
$generationDirectory
58+
$generatedDirectory
5359
);
5460
$this->_generator = $objectManager->create(
5561
\Magento\Framework\Code\Generator::class,
@@ -70,7 +76,8 @@ protected function setUp()
7076

7177
protected function tearDown()
7278
{
73-
$this->varDirectory->delete('generation');
79+
$this->generatedDirectory->changePermissionsRecursively('./', 0777, 0666);
80+
$this->generatedDirectory->delete();
7481
$this->_generator = null;
7582
}
7683

@@ -163,8 +170,8 @@ public function testGenerateClassInterceptorWithNamespace()
163170
public function testGenerateClassExtensionAttributesInterfaceFactoryWithNamespace()
164171
{
165172
$factoryClassName = self::CLASS_NAME_WITH_NAMESPACE . 'ExtensionInterfaceFactory';
166-
$this->varDirectory->create(
167-
$this->varDirectory->getAbsolutePath('generation') . '/Magento/Framework/Code/GeneratorTest/'
173+
$this->generatedDirectory->create(
174+
$this->generatedDirectory->getAbsolutePath() . '/Magento/Framework/Code/GeneratorTest/'
168175
);
169176

170177
$generatorResult = $this->_generator->generateClass($factoryClassName);
@@ -185,4 +192,26 @@ public function testGenerateClassExtensionAttributesInterfaceFactoryWithNamespac
185192
);
186193
$this->assertEquals($expectedContent, $content);
187194
}
195+
196+
public function testGeneratorClassWithErrorSaveClassFile()
197+
{
198+
$exceptionMessageRegExp = '/Error: an object of a generated class may be a dependency for another object, '
199+
. 'but this dependency has not been defined or set correctly in the signature of the related construct '
200+
. 'method\.\nDue to the current error, executing the CLI commands `bin\/magento setup:di:compile` '
201+
. 'or `bin\/magento deploy:mode:set production` does not create the required generated classes\.\nMagento '
202+
. 'cannot write a class file to the "generated" directory that is read\-only\. Before using the read-only '
203+
. 'file system, the classes to be generated must be created beforehand in the "generated" directory\.\n'
204+
. 'For details, see the "File systems access permissions" topic at http:\/\/devdocs\.magento\.com\.\n'
205+
. 'The specified ".*" file couldn\'t be written. in \[.*\]/';
206+
207+
$this->expectException(\RuntimeException::class);
208+
$this->expectExceptionMessageRegExp($exceptionMessageRegExp);
209+
$this->generatedDirectory->changePermissionsRecursively('./', 0555, 0444);
210+
$factoryClassName = self::CLASS_NAME_WITH_NAMESPACE . 'ExtensionInterfaceFactory';
211+
212+
$generatorResult = $this->_generator->generateClass($factoryClassName);
213+
$this->assertFalse($generatorResult);
214+
$pathToSystemLog = $this->logDirectory->getAbsolutePath('system.log');
215+
$this->assertRegexp($exceptionMessageRegExp, file_get_contents($pathToSystemLog));
216+
}
188217
}

generated/.htaccess

100644100755
File mode changed.

lib/internal/Magento/Framework/Code/Generator.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
use Magento\Framework\Code\Generator\DefinedClasses;
99
use Magento\Framework\Code\Generator\EntityAbstract;
10+
use Psr\Log\LoggerInterface;
1011

1112
class Generator
1213
{
@@ -111,8 +112,12 @@ public function generateClass($className)
111112
if ($generator !== null) {
112113
$this->tryToLoadSourceClass($className, $generator);
113114
if (!($file = $generator->generate())) {
115+
/** @var $logger LoggerInterface */
116+
$logger = $this->objectManager->get(LoggerInterface::class);
114117
$errors = $generator->getErrors();
115-
throw new \RuntimeException(implode(' ', $errors) . ' in [' . $className . ']');
118+
$message = implode(PHP_EOL, $errors) . ' in [' . $className . ']';
119+
$logger->critical($message);
120+
throw new \RuntimeException($message);
116121
}
117122
if (!$this->definedClasses->isClassLoadableFromMemory($className)) {
118123
$this->_ioObject->includeFile($file);

lib/internal/Magento/Framework/Code/Generator/EntityAbstract.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
namespace Magento\Framework\Code\Generator;
77

8+
use Magento\Framework\Exception\FileSystemException;
89
use Zend\Code\Generator\ValueGenerator;
910

1011
abstract class EntityAbstract
@@ -106,6 +107,15 @@ public function generate()
106107
$this->_addError('Can\'t generate source code.');
107108
}
108109
}
110+
} catch (FileSystemException $e) {
111+
$message = <<<'EOT'
112+
Error: an object of a generated class may be a dependency for another object, but this dependency has not been defined or set correctly in the signature of the related construct method.
113+
Due to the current error, executing the CLI commands `bin/magento setup:di:compile` or `bin/magento deploy:mode:set production` does not create the required generated classes.
114+
Magento cannot write a class file to the "generated" directory that is read-only. Before using the read-only file system, the classes to be generated must be created beforehand in the "generated" directory.
115+
For details, see the "File systems access permissions" topic at http://devdocs.magento.com.
116+
EOT;
117+
$this->_addError($message);
118+
$this->_addError($e->getMessage());
109119
} catch (\Exception $e) {
110120
$this->_addError($e->getMessage());
111121
}

lib/internal/Magento/Framework/Code/Test/Unit/Generator/EntityAbstractTest.php

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
*/
66
namespace Magento\Framework\Code\Test\Unit\Generator;
77

8+
use Magento\Framework\Exception\FileSystemException;
9+
use Magento\Framework\Phrase;
10+
811
class EntityAbstractTest extends \PHPUnit\Framework\TestCase
912
{
1013
/**#@+
@@ -209,6 +212,48 @@ public function testGenerate(
209212
}
210213
}
211214

215+
/**
216+
* @inheritdoc
217+
*/
218+
public function testGenerateFailure() {
219+
220+
$infoMessage = <<<'EOT'
221+
Error: an object of a generated class may be a dependency for another object, but this dependency has not been defined or set correctly in the signature of the related construct method.
222+
Due to the current error, executing the CLI commands `bin/magento setup:di:compile` or `bin/magento deploy:mode:set production` does not create the required generated classes.
223+
Magento cannot write a class file to the "generated" directory that is read-only. Before using the read-only file system, the classes to be generated must be created beforehand in the "generated" directory.
224+
For details, see the "File systems access permissions" topic at http://devdocs.magento.com.
225+
EOT;
226+
$exceptionMessage = 'Some description';
227+
228+
$abstractGetters = ['_getClassProperties', '_getClassMethods'];
229+
230+
$arguments = $this->_prepareMocksForGenerateCode(true);
231+
232+
/** @var \Magento\Framework\Code\Generator\Io|\PHPUnit_Framework_MockObject_MockObject $ioObjectMock */
233+
$ioObjectMock = $arguments['io_object'];
234+
$ioObjectMock->expects($this->once())
235+
->method('writeResultFile')
236+
->with(self::RESULT_FILE, self::RESULT_CODE)
237+
->willThrowException(new FileSystemException(new Phrase($exceptionMessage)));
238+
239+
$this->_model = $this->getMockForAbstractClass(
240+
\Magento\Framework\Code\Generator\EntityAbstract::class,
241+
$arguments,
242+
'',
243+
true,
244+
true,
245+
true,
246+
$abstractGetters
247+
);
248+
// we need to mock abstract methods to set correct return value type
249+
foreach ($abstractGetters as $methodName) {
250+
$this->_model->expects($this->any())->method($methodName)->will($this->returnValue([]));
251+
}
252+
253+
$result = $this->_model->generate();
254+
$this->assertFalse($result);
255+
$this->assertEquals([$infoMessage, $exceptionMessage], $this->_model->getErrors());
256+
}
212257
/**
213258
* Prepares mocks for validation verification
214259
*

lib/internal/Magento/Framework/Code/Test/Unit/GeneratorTest.php

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6-
76
namespace Magento\Framework\Code\Test\Unit;
87

98
use Magento\Framework\Code\Generator;
109
use Magento\Framework\Code\Generator\DefinedClasses;
1110
use Magento\Framework\Code\Generator\Io;
11+
use Psr\Log\LoggerInterface;
1212

1313
class GeneratorTest extends \PHPUnit\Framework\TestCase
1414
{
@@ -95,7 +95,7 @@ public function testGenerateClassWithWrongName()
9595
/**
9696
* @expectedException \RuntimeException
9797
*/
98-
public function testGenerateClassWithError()
98+
public function testGenerateClassWhenClassIsNotGenerationSuccess()
9999
{
100100
$expectedEntities = array_values($this->expectedEntities);
101101
$resultClassName = self::SOURCE_CLASS . ucfirst(array_shift($expectedEntities));
@@ -108,6 +108,49 @@ public function testGenerateClassWithError()
108108
$this->model->generateClass($resultClassName);
109109
}
110110

111+
/**
112+
* @inheritdoc
113+
*/
114+
public function testGenerateClassWithErrors()
115+
{
116+
$this->expectException(\RuntimeException::class);
117+
$this->expectExceptionMessage("Some error message 0\nSome error message 1\nSome error message 2");
118+
$errorMessages = [
119+
'Some error message 0',
120+
'Some error message 1',
121+
'Some error message 2',
122+
];
123+
$expectedEntities = array_values($this->expectedEntities);
124+
$resultClassName = self::SOURCE_CLASS . ucfirst(array_shift($expectedEntities));
125+
$objectManagerMock = $this->createMock(\Magento\Framework\ObjectManagerInterface::class);
126+
$entityGeneratorMock = $this->getMockBuilder(\Magento\Framework\Code\Generator\EntityAbstract::class)
127+
->disableOriginalConstructor()
128+
->getMock();
129+
$loggerMock = $this->createMock(LoggerInterface::class);
130+
131+
$objectManagerMock->expects($this->once())
132+
->method('create')
133+
->willReturn($entityGeneratorMock);
134+
$entityGeneratorMock->expects($this->once())
135+
->method('getSourceClassName')
136+
->willReturn(self::SOURCE_CLASS);
137+
$this->definedClassesMock->expects($this->once())
138+
->method('isClassLoadable')
139+
->with(self::SOURCE_CLASS)
140+
->willReturn(true);
141+
$entityGeneratorMock->expects($this->once())
142+
->method('generate')
143+
->willReturn(false);
144+
$objectManagerMock->expects($this->once())
145+
->method('get')
146+
->willReturn($loggerMock);
147+
$entityGeneratorMock->expects($this->once())
148+
->method('getErrors')
149+
->willReturn($errorMessages);
150+
$this->model->setObjectManager($objectManagerMock);
151+
$this->model->generateClass($resultClassName);
152+
}
153+
111154
/**
112155
* @dataProvider trueFalseDataProvider
113156
*/

0 commit comments

Comments
 (0)