Skip to content

Commit 6e9d03b

Browse files
committed
Tests: new ConfigDouble test helper class
The PHP_CodeSniffer native `Config` class contains a number of static properties. As the value of these static properties will be retained between instantiations of the class, config values set in one test can influence the results for another test, which makes tests unstable. This commit introduces a test "double" of the `Config` class which prevents this from happening. In _most_ cases, tests should be using this class instead of the "normal" Config, with the exception of select tests for the Config class itself.
1 parent 074ad2a commit 6e9d03b

File tree

1 file changed

+196
-0
lines changed

1 file changed

+196
-0
lines changed

tests/ConfigDouble.php

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
<?php
2+
/**
3+
* Config class for use in the tests.
4+
*
5+
* The Config class contains a number of static properties.
6+
* As the value of these static properties will be retained between instantiations of the class,
7+
* config values set in one test can influence the results for another test, which makes tests unstable.
8+
*
9+
* This class is a "double" of the Config class which prevents this from happening.
10+
* In _most_ cases, tests should be using this class instead of the "normal" Config,
11+
* with the exception of select tests for the Config class itself.
12+
*
13+
* @author Juliette Reinders Folmer <phpcs_nospam@adviesenzo.nl>
14+
* @copyright 2024 Juliette Reinders Folmer. All rights reserved.
15+
* @license https://github.com/PHPCSStandards/PHP_CodeSniffer/blob/master/licence.txt BSD Licence
16+
*/
17+
18+
namespace PHP_CodeSniffer\Tests;
19+
20+
use PHP_CodeSniffer\Config;
21+
use ReflectionProperty;
22+
23+
final class ConfigDouble extends Config
24+
{
25+
26+
/**
27+
* Whether or not the setting of a standard should be skipped.
28+
*
29+
* @var boolean
30+
*/
31+
private $skipSettingStandard = false;
32+
33+
34+
/**
35+
* Creates a clean Config object and populates it with command line values.
36+
*
37+
* @param array<string> $cliArgs An array of values gathered from CLI args.
38+
* @param bool $skipSettingStandard Whether to skip setting a standard to prevent
39+
* the Config class trying to auto-discover a ruleset file.
40+
* Should only be set to `true` for tests which actually test
41+
* the ruleset auto-discovery.
42+
* Note: there is no need to set this to `true` when a standard
43+
* is being passed via the `$cliArgs`. Those settings will always
44+
* respected.
45+
* Defaults to `false`. Will result in the standard being set
46+
* to "PSR1" if not provided via `$cliArgs`.
47+
* @param bool $skipSettingReportWidth Whether to skip setting a report-width to prevent
48+
* the Config class trying to auto-discover the screen width.
49+
* Should only be set to `true` for tests which actually test
50+
* the screen width auto-discovery.
51+
* Note: there is no need to set this to `true` when a report-width
52+
* is being passed via the `$cliArgs`. Those settings will always
53+
* respected.
54+
* Defaults to `false`. Will result in the reportWidth being set
55+
* to "80" if not provided via `$cliArgs`.
56+
*
57+
* @return void
58+
*/
59+
public function __construct(array $cliArgs=[], $skipSettingStandard=false, $skipSettingReportWidth=false)
60+
{
61+
$this->skipSettingStandard = $skipSettingStandard;
62+
63+
$this->resetSelectProperties();
64+
$this->preventReadingCodeSnifferConfFile();
65+
66+
parent::__construct($cliArgs);
67+
68+
if ($skipSettingReportWidth !== true) {
69+
$this->preventAutoDiscoveryScreenWidth();
70+
}
71+
72+
}//end __construct()
73+
74+
75+
/**
76+
* Sets the command line values and optionally prevents a file system search for a custom ruleset.
77+
*
78+
* @param array<string> $args An array of command line arguments to set.
79+
*
80+
* @return void
81+
*/
82+
public function setCommandLineValues($args)
83+
{
84+
parent::setCommandLineValues($args);
85+
86+
if ($this->skipSettingStandard !== true) {
87+
$this->preventSearchingForRuleset();
88+
}
89+
90+
}//end setCommandLineValues()
91+
92+
93+
/**
94+
* Reset a few properties on the Config class to their default values.
95+
*
96+
* @return void
97+
*/
98+
private function resetSelectProperties()
99+
{
100+
$this->setStaticConfigProperty('overriddenDefaults', []);
101+
$this->setStaticConfigProperty('executablePaths', []);
102+
103+
}//end resetSelectProperties()
104+
105+
106+
/**
107+
* Prevent the values in a potentially available user-specific `CodeSniffer.conf` file
108+
* from influencing the tests.
109+
*
110+
* This also prevents some file system calls which can influence the test runtime.
111+
*
112+
* @return void
113+
*/
114+
private function preventReadingCodeSnifferConfFile()
115+
{
116+
$this->setStaticConfigProperty('configData', []);
117+
$this->setStaticConfigProperty('configDataFile', '');
118+
119+
}//end preventReadingCodeSnifferConfFile()
120+
121+
122+
/**
123+
* Prevent searching for a custom ruleset by setting a standard, but only if the test
124+
* being run doesn't set a standard itself.
125+
*
126+
* This also prevents some file system calls which can influence the test runtime.
127+
*
128+
* The standard being set is the smallest one available so the ruleset initialization
129+
* will be the fastest possible.
130+
*
131+
* @return void
132+
*/
133+
private function preventSearchingForRuleset()
134+
{
135+
$overriddenDefaults = $this->getStaticConfigProperty('overriddenDefaults');
136+
if (isset($overriddenDefaults['standards']) === false) {
137+
$this->standards = ['PSR1'];
138+
$overriddenDefaults['standards'] = true;
139+
}
140+
141+
self::setStaticConfigProperty('overriddenDefaults', $overriddenDefaults);
142+
143+
}//end preventSearchingForRuleset()
144+
145+
146+
/**
147+
* Prevent a call to stty to figure out the screen width, but only if the test being run
148+
* doesn't set a report width itself.
149+
*
150+
* @return void
151+
*/
152+
private function preventAutoDiscoveryScreenWidth()
153+
{
154+
$settings = $this->getSettings();
155+
if ($settings['reportWidth'] === 'auto') {
156+
$this->reportWidth = self::DEFAULT_REPORT_WIDTH;
157+
}
158+
159+
}//end preventAutoDiscoveryScreenWidth()
160+
161+
162+
/**
163+
* Helper function to retrieve the value of a private static property on the Config class.
164+
*
165+
* @param string $name The name of the property to retrieve.
166+
*
167+
* @return mixed
168+
*/
169+
private function getStaticConfigProperty($name)
170+
{
171+
$property = new ReflectionProperty('PHP_CodeSniffer\Config', $name);
172+
$property->setAccessible(true);
173+
return $property->getValue();
174+
175+
}//end getStaticConfigProperty()
176+
177+
178+
/**
179+
* Helper function to set the value of a private static property on the Config class.
180+
*
181+
* @param string $name The name of the property to set.
182+
* @param mixed $value The value to set the property to.
183+
*
184+
* @return void
185+
*/
186+
private function setStaticConfigProperty($name, $value)
187+
{
188+
$property = new ReflectionProperty('PHP_CodeSniffer\Config', $name);
189+
$property->setAccessible(true);
190+
$property->setValue(null, $value);
191+
$property->setAccessible(false);
192+
193+
}//end setStaticConfigProperty()
194+
195+
196+
}//end class

0 commit comments

Comments
 (0)