Path: blob/master/src/infrastructure/testing/PhabricatorTestCase.php
12241 views
<?php12abstract class PhabricatorTestCase extends PhutilTestCase {34const NAMESPACE_PREFIX = 'phabricator_unittest_';56/**7* If true, put Lisk in process-isolated mode for the duration of the tests so8* that it will establish only isolated, side-effect-free database9* connections. Defaults to true.10*11* NOTE: You should disable this only in rare circumstances. Unit tests should12* not rely on external resources like databases, and should not produce13* side effects.14*/15const PHABRICATOR_TESTCONFIG_ISOLATE_LISK = 'isolate-lisk';1617/**18* If true, build storage fixtures before running tests, and connect to them19* during test execution. This will impose a performance penalty on test20* execution (currently, it takes roughly one second to build the fixture)21* but allows you to perform tests which require data to be read from storage22* after writes. The fixture is shared across all test cases in this process.23* Defaults to false.24*25* NOTE: All connections to fixture storage open transactions when established26* and roll them back when tests complete. Each test must independently27* write data it relies on; data will not persist across tests.28*29* NOTE: Enabling this implies disabling process isolation.30*/31const PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES = 'storage-fixtures';3233private $configuration;34private $env;3536private static $storageFixtureReferences = 0;37private static $storageFixture;38private static $storageFixtureObjectSeed = 0;39private static $testsAreRunning = 0;4041protected function getPhabricatorTestCaseConfiguration() {42return array();43}4445private function getComputedConfiguration() {46$config = $this->getPhabricatorTestCaseConfiguration() + array(47self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK => true,48self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => false,49);5051if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) {52// Fixtures don't make sense with process isolation.53$config[self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK] = false;54}5556return $config;57}5859public function willRunTestCases(array $test_cases) {60$root = dirname(phutil_get_library_root('phabricator'));61require_once $root.'/scripts/__init_script__.php';6263$config = $this->getComputedConfiguration();6465if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) {66++self::$storageFixtureReferences;67if (!self::$storageFixture) {68self::$storageFixture = $this->newStorageFixture();69}70}7172++self::$testsAreRunning;73}7475public function didRunTestCases(array $test_cases) {76if (self::$storageFixture) {77self::$storageFixtureReferences--;78if (!self::$storageFixtureReferences) {79self::$storageFixture = null;80}81}8283--self::$testsAreRunning;84}8586protected function willRunTests() {87$config = $this->getComputedConfiguration();8889if ($config[self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK]) {90LiskDAO::beginIsolateAllLiskEffectsToCurrentProcess();91}9293$this->env = PhabricatorEnv::beginScopedEnv();9495// NOTE: While running unit tests, we act as though all applications are96// installed, regardless of the install's configuration. Tests which need97// to uninstall applications are responsible for adjusting state themselves98// (such tests are exceedingly rare).99100$this->env->overrideEnvConfig(101'phabricator.uninstalled-applications',102array());103$this->env->overrideEnvConfig(104'phabricator.show-prototypes',105true);106107// Reset application settings to defaults, particularly policies.108$this->env->overrideEnvConfig(109'phabricator.application-settings',110array());111112// We can't stub this service right now, and it's not generally useful113// to publish notifications about test execution.114$this->env->overrideEnvConfig(115'notification.servers',116array());117118$this->env->overrideEnvConfig(119'phabricator.base-uri',120'http://phabricator.example.com');121122$this->env->overrideEnvConfig(123'auth.email-domains',124array());125126// Tests do their own stubbing/voiding for events.127$this->env->overrideEnvConfig('phabricator.silent', false);128129$this->env->overrideEnvConfig('cluster.read-only', false);130131$this->env->overrideEnvConfig(132'maniphest.custom-field-definitions',133array());134}135136protected function didRunTests() {137$config = $this->getComputedConfiguration();138139if ($config[self::PHABRICATOR_TESTCONFIG_ISOLATE_LISK]) {140LiskDAO::endIsolateAllLiskEffectsToCurrentProcess();141}142143try {144if (phutil_is_hiphop_runtime()) {145$this->env->__destruct();146}147unset($this->env);148} catch (Exception $ex) {149throw new Exception(150pht(151'Some test called %s, but is still holding '.152'a reference to the scoped environment!',153'PhabricatorEnv::beginScopedEnv()'));154}155}156157protected function willRunOneTest($test) {158$config = $this->getComputedConfiguration();159160if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) {161LiskDAO::beginIsolateAllLiskEffectsToTransactions();162}163}164165protected function didRunOneTest($test) {166$config = $this->getComputedConfiguration();167168if ($config[self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES]) {169LiskDAO::endIsolateAllLiskEffectsToTransactions();170}171}172173protected function newStorageFixture() {174$bytes = Filesystem::readRandomCharacters(24);175$name = self::NAMESPACE_PREFIX.$bytes;176177return new PhabricatorStorageFixtureScopeGuard($name);178}179180/**181* Returns an integer seed to use when building unique identifiers (e.g.,182* non-colliding usernames). The seed is unstable and its value will change183* between test runs, so your tests must not rely on it.184*185* @return int A unique integer.186*/187protected function getNextObjectSeed() {188self::$storageFixtureObjectSeed += mt_rand(1, 100);189return self::$storageFixtureObjectSeed;190}191192protected function generateNewTestUser() {193$seed = $this->getNextObjectSeed();194195$user = id(new PhabricatorUser())196->setRealName(pht('Test User %s', $seed))197->setUserName("test{$seed}")198->setIsApproved(1);199200$email = id(new PhabricatorUserEmail())201->setAddress("testuser{$seed}@example.com")202->setIsVerified(1);203204$editor = new PhabricatorUserEditor();205$editor->setActor($user);206$editor->createNewUser($user, $email);207208// When creating a new test user, we prefill their setting cache as empty.209// This is a little more efficient than doing a query to load the empty210// settings.211$user->attachRawCacheData(212array(213PhabricatorUserPreferencesCacheType::KEY_PREFERENCES => '[]',214));215216return $user;217}218219220/**221* Throws unless tests are currently executing. This method can be used to222* guard code which is specific to unit tests and should not normally be223* reachable.224*225* If tests aren't currently being executed, throws an exception.226*/227public static function assertExecutingUnitTests() {228if (!self::$testsAreRunning) {229throw new Exception(230pht(231'Executing test code outside of test execution! '.232'This code path can only be run during unit tests.'));233}234}235236protected function requireBinaryForTest($binary) {237if (!Filesystem::binaryExists($binary)) {238$this->assertSkipped(239pht(240'No binary "%s" found on this system, skipping test.',241$binary));242}243}244245protected function newContentSource() {246return PhabricatorContentSource::newForSource(247PhabricatorUnitTestContentSource::SOURCECONST);248}249250}251252253