silverstripe-framework/tests/php/Core/Manifest/ConfigManifestTest.php

698 lines
23 KiB
PHP

<?php
namespace SilverStripe\Core\Tests\Manifest;
use SilverStripe\Control\Director;
use SilverStripe\Core\Config\Config;
use SilverStripe\Core\Manifest\ConfigManifest;
use SilverStripe\Dev\SapphireTest;
use ReflectionProperty;
use Symfony\Component\Cache\Simple\ArrayCache;
class ConfigManifestTest extends SapphireTest
{
/**
* This is a helper method for getting a new manifest
*
* @param string $name
* @return mixed
*/
protected function getConfigFixtureValue($name)
{
$manifest = new ConfigManifest(dirname(__FILE__).'/fixtures/configmanifest', true, true);
return $manifest->get(__CLASS__, $name);
}
/**
* This is a helper method for displaying a relevant message about a parsing failure
*/
protected function getParsedAsMessage($path)
{
return sprintf('Reference path "%s" failed to parse correctly', $path);
}
/**
* A helper method to return a mock of the cache in order to test expectations and reduce dependency
*
* @return \PHPUnit_Framework_MockObject_MockObject
*/
protected function getCacheMock()
{
return $this->getMock(
ArrayCache::class,
array('set', 'get'),
array(),
'',
false
);
}
/**
* A helper method to return a mock of the manifest in order to test expectations and reduce dependency
*
* @param $methods
* @return \PHPUnit_Framework_MockObject_MockObject
*/
protected function getManifestMock($methods)
{
return $this->getMock(
ConfigManifest::class,
$methods,
array(), // no constructor arguments
'', // default
false // don't call the constructor
);
}
/**
* Test the caching functionality when we are forcing regeneration
*
* 1. Test that regenerate is called in the default case and that cache->load isn't
* 2. Test that save is called correctly after the regeneration
*/
public function testCachingForceRegeneration()
{
// Test that regenerate is called correctly.
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
$manifest->expects($this->once()) // regenerate should be called once
->method('regenerate')
->with($this->equalTo(true)); // includeTests = true
// Set up a cache where we expect load to never be called
$cache = $this->getCacheMock();
$cache->expects($this->never())
->method('get');
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', true, true);
// Test that save is called correctly
$manifest = $this->getManifestMock(array('getCache'));
$cache = $this->getCacheMock();
$cache->expects($this->atLeastOnce())
->method('set');
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', true, true);
}
/**
* Test the caching functionality when we are not forcing regeneration
*
* 1. Test that load is called
* 2. Test the regenerate is called when the cache is unprimed
* 3. Test that when there is a value in the cache regenerate isn't called
*/
public function testCachingNotForceRegeneration()
{
// Test that load is called
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
// Load should be called twice
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('get');
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', true, false);
// Now test that regenerate is called because the cache is unprimed
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('get')
->will($this->onConsecutiveCalls(false, false));
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->expects($this->once())
->method('regenerate')
->with($this->equalTo(false)); //includeTests = false
$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', false, false);
// Now test that when there is a value in the cache that regenerate isn't called
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('get')
->will($this->onConsecutiveCalls(array(), array()));
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->expects($this->never())
->method('regenerate');
$manifest->__construct(dirname(__FILE__).'/fixtures/configmanifest', false, false);
}
/**
* Test cache regeneration if all or some of the cache files are missing
*
* 1. Test regeneration if all cache files are missing
* 2. Test regeneration if 'variant_key_spec' cache file is missing
* 3. Test regeneration if 'php_config_sources' cache file is missing
*/
public function testAutomaticCacheRegeneration()
{
$base = dirname(__FILE__) . '/fixtures/configmanifest';
// Test regeneration if all cache files are missing
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
$manifest->expects($this->once())// regenerate should be called once
->method('regenerate')
->with($this->equalTo(false)); // includeTests = false
// Set up a cache where we expect load to never be called
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->will($this->returnValue(false))
->method('get');
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->__construct($base);
// Test regeneration if 'variant_key_spec' cache file is missing
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
$manifest->expects($this->once())// regenerate should be called once
->method('regenerate')
->with($this->equalTo(false)); // includeTests = false
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('get')
->will($this->returnCallback(function ($parameter) {
if (strpos($parameter, 'variant_key_spec') !== false) {
return false;
}
return array();
}));
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->__construct($base);
// Test regeneration if 'php_config_sources' cache file is missing
$manifest = $this->getManifestMock(array('getCache', 'regenerate', 'buildYamlConfigVariant'));
$manifest->expects($this->once())// regenerate should be called once
->method('regenerate')
->with($this->equalTo(false)); // includeTests = false
$cache = $this->getCacheMock();
$cache->expects($this->exactly(2))
->method('get')
->will($this->returnCallback(function ($parameter) {
if (strpos($parameter, 'php_config_sources') !== false) {
return false;
}
return array();
}));
$manifest->expects($this->any())
->method('getCache')
->will($this->returnValue($cache));
$manifest->__construct($base);
}
/**
* This test checks the processing of before and after reference paths (module-name/filename#fragment)
* This method uses fixture/configmanifest/mysite/_config/addyamlconfigfile.yml as a fixture
*/
public function testAddYAMLConfigFileReferencePathParsing()
{
// Use a mock to avoid testing unrelated functionality
$manifest = $this->getManifestMock(array('addModule'));
// This tests that the addModule method is called with the correct value
$manifest->expects($this->once())
->method('addModule')
->with($this->equalTo(dirname(__FILE__).'/fixtures/configmanifest/mysite'));
// Call the method to be tested
$manifest->addYAMLConfigFile(
'addyamlconfigfile.yml',
dirname(__FILE__).'/fixtures/configmanifest/mysite/_config/addyamlconfigfile.yml',
false
);
// There is no getter for yamlConfigFragments
$property = new ReflectionProperty('SilverStripe\\Core\\Manifest\\ConfigManifest', 'yamlConfigFragments');
$property->setAccessible(true);
// Get the result back from the parsing
$result = $property->getValue($manifest);
$this->assertEquals(
array(
array(
'module' => 'mysite',
'file' => 'testfile',
'name' => 'fragment',
),
),
@$result[0]['after'],
$this->getParsedAsMessage('mysite/testfile#fragment')
);
$this->assertEquals(
array(
array(
'module' => 'test-module',
'file' => 'testfile',
'name' => 'fragment',
),
),
@$result[1]['after'],
$this->getParsedAsMessage('test-module/testfile#fragment')
);
$this->assertEquals(
array(
array(
'module' => '*',
'file' => '*',
'name' => '*',
),
),
@$result[2]['after'],
$this->getParsedAsMessage('*')
);
$this->assertEquals(
array(
array(
'module' => '*',
'file' => 'testfile',
'name' => '*'
),
),
@$result[3]['after'],
$this->getParsedAsMessage('*/testfile')
);
$this->assertEquals(
array(
array(
'module' => '*',
'file' => '*',
'name' => 'fragment'
),
),
@$result[4]['after'],
$this->getParsedAsMessage('*/*#fragment')
);
$this->assertEquals(
array(
array(
'module' => '*',
'file' => '*',
'name' => 'fragment'
),
),
@$result[5]['after'],
$this->getParsedAsMessage('#fragment')
);
$this->assertEquals(
array(
array(
'module' => 'test-module',
'file' => '*',
'name' => 'fragment'
),
),
@$result[6]['after'],
$this->getParsedAsMessage('test-module#fragment')
);
$this->assertEquals(
array(
array(
'module' => 'test-module',
'file' => '*',
'name' => '*'
),
),
@$result[7]['after'],
$this->getParsedAsMessage('test-module')
);
$this->assertEquals(
array(
array(
'module' => 'test-module',
'file' => '*',
'name' => '*'
),
),
@$result[8]['after'],
$this->getParsedAsMessage('test-module/*')
);
$this->assertEquals(
array(
array(
'module' => 'test-module',
'file' => '*',
'name' => '*'
),
),
@$result[9]['after'],
$this->getParsedAsMessage('test-module/*/#*')
);
}
public function testClassRules()
{
$config = $this->getConfigFixtureValue('Class');
$this->assertEquals(
'Yes',
@$config['DirectorExists'],
'Only rule correctly detects existing class'
);
$this->assertEquals(
'No',
@$config['NoSuchClassExists'],
'Except rule correctly detects missing class'
);
}
public function testModuleRules()
{
$config = $this->getConfigFixtureValue('Module');
$this->assertEquals(
'Yes',
@$config['MysiteExists'],
'Only rule correctly detects existing module'
);
$this->assertEquals(
'No',
@$config['NoSuchModuleExists'],
'Except rule correctly detects missing module'
);
}
public function testEnvVarSetRules()
{
$loader = new \Dotenv\Loader(null);
$loader->setEnvironmentVariable('ENVVARSET_FOO', 1);
$config = $this->getConfigFixtureValue('EnvVarSet');
$this->assertEquals(
'Yes',
@$config['FooSet'],
'Only rule correctly detects set environment variable'
);
$this->assertEquals(
'No',
@$config['BarSet'],
'Except rule correctly detects unset environment variable'
);
}
public function testConstantDefinedRules()
{
define('CONSTANTDEFINED_FOO', 1);
$config = $this->getConfigFixtureValue('ConstantDefined');
$this->assertEquals(
'Yes',
@$config['FooDefined'],
'Only rule correctly detects defined constant'
);
$this->assertEquals(
'No',
@$config['BarDefined'],
'Except rule correctly detects undefined constant'
);
}
public function testEnvOrConstantMatchesValueRules()
{
$loader = new \Dotenv\Loader(null);
$loader->setEnvironmentVariable('ENVORCONSTANTMATCHESVALUE_FOO', 'Foo');
define('ENVORCONSTANTMATCHESVALUE_BAR', 'Bar');
$config = $this->getConfigFixtureValue('EnvOrConstantMatchesValue');
$this->assertEquals(
'Yes',
@$config['FooIsFoo'],
'Only rule correctly detects environment variable matches specified value'
);
$this->assertEquals(
'Yes',
@$config['BarIsBar'],
'Only rule correctly detects constant matches specified value'
);
$this->assertEquals(
'No',
@$config['FooIsQux'],
'Except rule correctly detects environment variable that doesn\'t match specified value'
);
$this->assertEquals(
'No',
@$config['BarIsQux'],
'Except rule correctly detects environment variable that doesn\'t match specified value'
);
$this->assertEquals(
'No',
@$config['BazIsBaz'],
'Except rule correctly detects undefined variable'
);
}
public function testEnvironmentRules()
{
foreach (array('dev', 'test', 'live') as $env) {
Config::nest();
Config::inst()->update('SilverStripe\\Control\\Director', 'environment_type', $env);
$config = $this->getConfigFixtureValue('Environment');
foreach (array('dev', 'test', 'live') as $check) {
$this->assertEquals(
$env == $check ? $check : 'not'.$check,
@$config[ucfirst($check).'Environment'],
'Only & except rules correctly detect environment'
);
}
Config::unnest();
}
}
public function testDynamicEnvironmentRules()
{
// First, make sure environment_type is live
Director::config()->update('environment_type', 'live');
$this->assertEquals('live', Director::config()->get('environment_type'));
// Then, load in a new manifest, which includes a _config.php that sets environment_type to dev
$manifest = new ConfigManifest(dirname(__FILE__).'/fixtures/configmanifest_dynamicenv', true, true);
Config::inst()->pushConfigYamlManifest($manifest);
// Make sure that stuck
$this->assertEquals('dev', Director::config()->get('environment_type'));
// And that the dynamic rule was calculated correctly
$this->assertEquals('dev', Config::inst()->get(__CLASS__, 'DynamicEnvironment'));
}
public function testMultipleRules()
{
$loader = new \Dotenv\Loader(null);
$loader->setEnvironmentVariable('MULTIPLERULES_ENVVARIABLESET', 1);
define('MULTIPLERULES_DEFINEDCONSTANT', 'defined');
$config = $this->getConfigFixtureValue('MultipleRules');
$this->assertFalse(
isset($config['TwoOnlyFail']),
'Fragment is not included if one of the Only rules fails.'
);
$this->assertTrue(
isset($config['TwoOnlySucceed']),
'Fragment is included if both Only rules succeed.'
);
$this->assertTrue(
isset($config['TwoExceptSucceed']),
'Fragment is included if one of the Except rules matches.'
);
$this->assertFalse(
isset($config['TwoExceptFail']),
'Fragment is not included if both of the Except rules fail.'
);
$this->assertFalse(
isset($config['TwoBlocksFail']),
'Fragment is not included if one block fails.'
);
$this->assertTrue(
isset($config['TwoBlocksSucceed']),
'Fragment is included if both blocks succeed.'
);
}
public function testRelativeOrder()
{
$accessor = new ConfigManifestTest\ConfigManifestAccess(BASE_PATH, true, false);
// A fragment with a fully wildcard before rule
$beforeWildcarded = array(
'module' => 'foo', 'file' => 'alpha', 'name' => '1',
'before' => array(array('module' => '*', 'file' => '*', 'name' => '*'))
);
// A fragment with a fully wildcard before rule and a fully explicit after rule
$beforeWildcardedAfterExplicit = array_merge(
$beforeWildcarded,
array(
'after' => array(array('module' => 'bar', 'file' => 'beta', 'name' => '2'))
)
);
// A fragment with a fully wildcard before rule and two fully explicit after rules
$beforeWildcardedAfterTwoExplicitRules = array_merge(
$beforeWildcarded,
array(
'after' => array(
array('module' => 'bar', 'file' => 'beta', 'name' => '2'),
array('module' => 'baz', 'file' => 'gamma', 'name' => '3')
)
)
);
// A fragment with a fully wildcard before rule and a partially explicit after rule
$beforeWildcardedAfterPartialWildcarded = array_merge(
$beforeWildcarded,
array(
'after' => array(array('module' => 'bar', 'file' => 'beta', 'name' => '*'))
)
);
// Wildcard should match any module
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcarded,
array('module' => 'qux', 'file' => 'delta', 'name' => '4')
),
'before'
);
// Wildcard should match any module even if there is an opposing rule, if opposing rule doesn't match
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterExplicit,
array('module' => 'qux', 'file' => 'delta', 'name' => '4')
),
'before'
);
// Wildcard should match any module even if there is an opposing rule, if opposing rule doesn't match, no
// matter how many opposing rules
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterExplicit,
array('module' => 'qux', 'file' => 'delta', 'name' => '4')
),
'before'
);
// Wildcard should match any module even if there is an opposing rule, if opposing rule doesn't match
// (even if some portions do)
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterExplicit,
array('module' => 'bar', 'file' => 'beta', 'name' => 'nomatchy')
),
'before'
);
// When opposing rule matches, wildcard should be ignored
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterExplicit,
array('module' => 'bar', 'file' => 'beta', 'name' => '2')
),
'after'
);
// When any one of mutiple opposing rule exists, wildcard should be ignored
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterTwoExplicitRules,
array('module' => 'bar', 'file' => 'beta', 'name' => '2')
),
'after'
);
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterTwoExplicitRules,
array('module' => 'baz', 'file' => 'gamma', 'name' => '3')
),
'after'
);
// When two opposed wildcard rules, and more specific one doesn't match, other should win
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterPartialWildcarded,
array('module' => 'qux', 'file' => 'delta', 'name' => '4')
),
'before'
);
// When two opposed wildcard rules, and more specific one does match, more specific one should win
$this->assertEquals(
$accessor->relativeOrder(
$beforeWildcardedAfterPartialWildcarded,
array('module' => 'bar', 'file' => 'beta', 'name' => 'wildcardmatchy')
),
'after'
);
}
}