@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3abstract class PhabricatorTestCase extends PhutilTestCase {
4
5 const NAMESPACE_PREFIX = 'phabricator_unittest_';
6
7 /**
8 * If true, put Lisk in process-isolated mode for the duration of the tests so
9 * that it will establish only isolated, side-effect-free database
10 * connections. Defaults to true.
11 *
12 * NOTE: You should disable this only in rare circumstances. Unit tests should
13 * not rely on external resources like databases, and should not produce
14 * side effects.
15 */
16 const PHABRICATOR_TESTCONFIG_ISOLATE_LISK = 'isolate-lisk';
17
18 /**
19 * If true, build storage fixtures before running tests, and connect to them
20 * during test execution. This will impose a performance penalty on test
21 * execution (currently, it takes roughly one second to build the fixture)
22 * but allows you to perform tests which require data to be read from storage
23 * after writes. The fixture is shared across all test cases in this process.
24 * Defaults to false.
25 *
26 * NOTE: All connections to fixture storage open transactions when established
27 * and roll them back when tests complete. Each test must independently
28 * write data it relies on; data will not persist across tests.
29 *
30 * NOTE: Enabling this implies disabling process isolation.
31 */
32 const PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES = 'storage-fixtures';
33
34 private $configuration;
35 private $env;
36
37 private static $storageFixtureReferences = 0;
38 private static $storageFixture;
39 private static $storageFixtureObjectSeed = 0;
40 private static $testsAreRunning = 0;
41
42 protected function getPhabricatorTestCaseConfiguration() {
43 return array();
44 }
45
46 private function getComputedConfiguration() {
47 $config = $this->getPhabricatorTestCaseConfiguration() + array(
48 self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK => true,
49 self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => false,
50 );
51
52 if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) {
53 // Fixtures don't make sense with process isolation.
54 $config[self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK] = false;
55 }
56
57 return $config;
58 }
59
60 /** @phutil-external-symbol function init_phabricator_script */
61 public function willRunTestCases(array $test_cases) {
62 $root = dirname(phutil_get_library_root('phabricator'));
63 if (!function_exists('init_phabricator_script')) {
64 // Run the initialization routines only if nothing else already did
65 require_once $root.'/scripts/init/lib.php';
66 init_phabricator_script(
67 array(
68 'config.optional' => false,
69 'no-extensions' => true,
70 ));
71 }
72
73 $config = $this->getComputedConfiguration();
74
75 if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) {
76 ++self::$storageFixtureReferences;
77 if (!self::$storageFixture) {
78 self::$storageFixture = $this->newStorageFixture();
79 }
80 }
81
82 ++self::$testsAreRunning;
83 }
84
85 public function didRunTestCases(array $test_cases) {
86 if (self::$storageFixture) {
87 self::$storageFixtureReferences--;
88 if (!self::$storageFixtureReferences) {
89 self::$storageFixture = null;
90 }
91 }
92
93 --self::$testsAreRunning;
94 }
95
96 protected function willRunTests() {
97 $config = $this->getComputedConfiguration();
98
99 if ($config[self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK]) {
100 LiskDAO::beginIsolateAllLiskEffectsToCurrentProcess();
101 }
102
103 $this->env = PhabricatorEnv::beginScopedEnv();
104
105 // NOTE: While running unit tests, we act as though all applications are
106 // enabled, regardless of the install's configuration. Tests which need
107 // to disable applications are responsible for adjusting state themselves
108 // (such tests are exceedingly rare).
109
110 $this->env->overrideEnvConfig(
111 'phabricator.uninstalled-applications',
112 array());
113 $this->env->overrideEnvConfig(
114 'phabricator.show-prototypes',
115 true);
116
117 // Reset application settings to defaults, particularly policies.
118 $this->env->overrideEnvConfig(
119 'phabricator.application-settings',
120 array());
121
122 // We can't stub this service right now, and it's not generally useful
123 // to publish notifications about test execution.
124 $this->env->overrideEnvConfig(
125 'notification.servers',
126 array());
127
128 $this->env->overrideEnvConfig(
129 'phabricator.base-uri',
130 'http://phabricator.example.com');
131
132 $this->env->overrideEnvConfig(
133 'auth.email-domains',
134 array());
135
136 // Tests do their own stubbing/voiding for events.
137 $this->env->overrideEnvConfig('phabricator.silent', false);
138
139 $this->env->overrideEnvConfig('cluster.read-only', false);
140
141 $this->env->overrideEnvConfig(
142 'maniphest.custom-field-definitions',
143 array());
144 }
145
146 protected function didRunTests() {
147 $config = $this->getComputedConfiguration();
148
149 if ($config[self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK]) {
150 LiskDAO::endIsolateAllLiskEffectsToCurrentProcess();
151 }
152
153 try {
154 if (phutil_is_hiphop_runtime()) {
155 $this->env->__destruct();
156 }
157 unset($this->env);
158 } catch (Exception $ex) {
159 throw new Exception(
160 pht(
161 'Some test called %s, but is still holding '.
162 'a reference to the scoped environment!',
163 'PhabricatorEnv::beginScopedEnv()'));
164 }
165 }
166
167 protected function willRunOneTest($test) {
168 $config = $this->getComputedConfiguration();
169
170 if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) {
171 LiskDAO::beginIsolateAllLiskEffectsToTransactions();
172 }
173 }
174
175 protected function didRunOneTest($test) {
176 $config = $this->getComputedConfiguration();
177
178 if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) {
179 LiskDAO::endIsolateAllLiskEffectsToTransactions();
180 }
181 }
182
183 protected function newStorageFixture() {
184 $bytes = Filesystem::readRandomCharacters(24);
185 $name = self::NAMESPACE_PREFIX.$bytes;
186
187 return new PhabricatorStorageFixtureScopeGuard($name);
188 }
189
190 /**
191 * Returns an integer seed to use when building unique identifiers (e.g.,
192 * non-colliding usernames). The seed is unstable and its value will change
193 * between test runs, so your tests must not rely on it.
194 *
195 * @return int A unique integer.
196 */
197 protected function getNextObjectSeed() {
198 self::$storageFixtureObjectSeed += mt_rand(1, 100);
199 return self::$storageFixtureObjectSeed;
200 }
201
202 protected function generateNewTestUser() {
203 $seed = $this->getNextObjectSeed();
204
205 $user = id(new PhabricatorUser())
206 ->setRealName(pht('Test User %s', $seed))
207 ->setUserName("test{$seed}")
208 ->setIsApproved(1);
209
210 $email = id(new PhabricatorUserEmail())
211 ->setAddress("testuser{$seed}@example.com")
212 ->setIsVerified(1);
213
214 $editor = new PhabricatorUserEditor();
215 $editor->setActor($user);
216 $editor->createNewUser($user, $email);
217
218 // When creating a new test user, we prefill their setting cache as empty.
219 // This is a little more efficient than doing a query to load the empty
220 // settings.
221 $user->attachRawCacheData(
222 array(
223 PhabricatorUserPreferencesCacheType::KEY_PREFERENCES => '[]',
224 ));
225
226 return $user;
227 }
228
229
230 /**
231 * Throws unless tests are currently executing. This method can be used to
232 * guard code which is specific to unit tests and should not normally be
233 * reachable.
234 *
235 * If tests aren't currently being executed, throws an exception.
236 */
237 public static function assertExecutingUnitTests() {
238 if (!self::$testsAreRunning) {
239 throw new Exception(
240 pht(
241 'Executing test code outside of test execution! '.
242 'This code path can only be run during unit tests.'));
243 }
244 }
245
246 protected function requireBinaryForTest($binary) {
247 if (!Filesystem::binaryExists($binary)) {
248 $this->assertSkipped(
249 pht(
250 'No binary "%s" found on this system, skipping test.',
251 $binary));
252 }
253 }
254
255 protected function newContentSource() {
256 return PhabricatorContentSource::newForSource(
257 PhabricatorUnitTestContentSource::SOURCECONST);
258 }
259
260}