protected function execute(InputInterface $input, OutputInterface $output)
 {
     if (!SeleniumManager::isSeleniumStarted()) {
         SeleniumManager::spawnSelenium($input->getOption('headless'));
     }
     $runners = ['phpunit', 'paratest', 'ptest', 'ftr'];
     $parallel = max(1, (int) $input->getOption('parallel'));
     $runner = $input->getOption('runner');
     if (!in_array($runner, $runners)) {
         $output->writeln('<error>Unsupported runner! Available runners are: phpunit, paratest and ptsest.</error>');
         return;
     }
     if ($parallel > 1 && $runner === 'phpunit') {
         $output->writeln('<error>The PHPUnit runner can\'t run tests in parallel.</error>');
         return;
     }
     $info = $input->getOption('info');
     if ($info && $runner !== 'ptest' && $runner !== 'ftr') {
         $output->writeln('<error>The info option is only supported by the ptest runner.</error>');
         return;
     }
     $runner_path = realpath(FS::join(__DIR__, '/../../vendor/bin/', $runner));
     if (!$runner_path) {
         $output->writeln(sprintf('<error>Could not find runner executable (%s). Did you run composer install?</error>', $runner));
         return;
     }
     if ($input->getArgument('test_name') && $input->getOption('all')) {
         $output->writeln(sprintf('<error>Cannot specify both a test name / folder and the "all" option.</error>'));
         return;
     }
     $domain = $input->getOption('domain');
     $tests_directory = realpath(FS::join(__DIR__, '..', '..', $domain));
     if (!$tests_directory) {
         $output->writeln('<error>Couldn\'t find directory containing tests.</error>');
         return;
     }
     if ($input->getOption('all')) {
         $tests_path = $tests_directory;
     } elseif (($test_name = $input->getArgument('test_name')) && !preg_match('/\\.php$/', $test_name)) {
         $tests_path = realpath(FS::join($tests_directory, $test_name . 'Test.php'));
     } elseif ($test_name) {
         $tests_path = realpath($test_name);
     } else {
         $rdi = new \RecursiveDirectoryIterator($tests_directory, \RecursiveDirectoryIterator::SKIP_DOTS);
         $rii = new \RecursiveIteratorIterator($rdi);
         $tests = [];
         foreach ($rii as $path => $info) {
             if ($info->getExtension() === 'php' && preg_match('/Test$/', $info->getBaseName('.php'))) {
                 $tests[] = substr($path, strlen($tests_directory) + 1, -strlen('Test.php'));
             }
         }
         $question = new Question('Which test do you want to run? ');
         $question->setAutocompleterValues($tests);
         $helper = $this->getHelperSet()->get('question');
         $tests_path = realpath(FS::join($tests_directory, $helper->ask($input, $output, $question) . 'Test.php'));
     }
     if (!$tests_path) {
         $output->writeln('<error>Could not find requested test.</error>');
     }
     $arguments = [];
     $options = [];
     if ($runner === 'ptest' || $runner === 'ftr') {
         $arguments[] = 'run';
     }
     if ($info) {
         $options['-i'] = '';
     }
     $arguments[] = $tests_path;
     if ($runner === 'ptest' || $runner === 'paratest' || $runner === 'ftr') {
         $options['-p'] = $parallel;
     }
     $filter = $input->getOption('filter');
     if ($filter) {
         $options['--filter'] = $filter;
     }
     $z = $input->getOption('data-provider-filter');
     if (!empty($z)) {
         foreach ($z as $opt) {
             $options['--data-provider-filter'] = $opt;
             if ($runner === 'ftr') {
                 break;
                 // this one supports only one filter
             }
         }
     }
     if ($runner === 'ftr') {
         $arguments[] = '--shallow';
     }
     $process = new \djfm\Process\Process($runner_path, $arguments, $options, ['wait' => true]);
     $process->setEnv('SELENIUM_HOST', SeleniumManager::getHost());
     if ($input->getOption('inplace')) {
         echo "-----------------------------------------------------\n";
         echo "-    Warning, running test inplace (as asked).      -\n";
         echo "- Initial states will not be changed, nor restored. -\n";
         echo "-----------------------------------------------------\n";
         echo "\n";
         $process->setEnv('PSTAF_INPLACE', 1);
     }
     if ($input->getOption('no-screenshots')) {
         $process->setEnv('NO_SCREENSHOTS', 1);
     }
     $process->run(STDIN, STDOUT, STDERR);
 }
 /**
  * getShop uses the configuration file and
  * provided options to build a Shop ready for use by selenium
  * scripts
  *
  * @param  array  $options
  * @return a Shop instance
  *
  * $options is an array with the following keys:
  * - initial_state: an array that will be passed to $shop->getFixtureManager()->setupInitialState(),
  * 	 it is used to set the initial state of the shop for the test
  *
  * - temporary: boolean, determines whether the shop is temporary or not.
  *   a temporary shop is always a new shop, and will likely be destroyed at the end  of the tests.
  *   if set to false, the shop is installed to path_to_web_root.
  *   the target path will be deleted before installation and loaded from source again if it exists
  *   AND if the overwrite option is set to true
  *
  * - overwrite: boolean, whether to overwrite or not the target shop files
  *   when calling getShop with temporary === false - defaults to false
  *
  * - use_cache: whether or not the shop can be cached, i.e., installed once,
  *   initialized with correct initial_state, and then restored from a copy of the files
  *   and a dump of the database.
  *   This is OK in most cases, but since there is some trickery involved in doing this
  *   (replacing a few things in the DB, .htaccess file and config files)
  *   it is not recommended to use the option in some scenarios, such as a complicated multishop
  *   setup.
  *
  */
 public function getShop(array $options)
 {
     $inplace = getenv('PSTAF_INPLACE') === '1';
     $conf = $this->getNewConfiguration();
     if (isset($options['conf'])) {
         $conf->update($options['conf']);
     }
     $options['temporary'] = !empty($options['temporary']) && !$inplace;
     $options['overwrite'] = !empty($options['overwrite']);
     $options['use_cache'] = !empty($options['use_cache']) && !$inplace;
     if (!isset($options['initial_state']) || !is_array($options['initial_state'])) {
         $options['initial_state'] = [];
     }
     // this may become a file resource
     // if it is, then we must close it and unlock it once
     // we're done.
     $lock = null;
     // whether or not we need to dump the constructed shop
     // if not null, it is the path where we need to put the files
     // after they have been built
     $dump_shop_to = null;
     // $using_cache will be true iff a warm cache exists
     // so when $dump_shop_to stays null
     $using_cache = false;
     // First we determine the path to the source files that
     // we're gonna use to build the shop.
     // this is the base case
     $source_files_path = $conf->getAsAbsolutePath('shop.filesystem_path');
     // if caching is allowed, we first check
     // whether the source files exist or not
     if ($options['use_cache'] && $options['initial_state'] !== []) {
         // since the source files may still be under build
         // we acquire a lock on a file that describes the initial state
         $initial_state_key = $this->getInitialStateKey($options['initial_state']);
         // acquire lock
         $lock_path = FS::join($this->getWorkingDirectory(), "pstaf.{$initial_state_key}.istate.lock");
         $lock = fopen($lock_path, 'w');
         if (!$lock) {
             throw new \Exception(sprintf('Could not create lock file %s.', $lock_path));
         }
         flock($lock, LOCK_EX);
         $cache_files_path = FS::join($this->getWorkingDirectory(), "pstaf.{$initial_state_key}.istate.shop");
         if (is_dir($cache_files_path)) {
             // we're all set, release the lock and set source files path to the cached files
             flock($lock, LOCK_UN);
             fclose($lock);
             $lock = null;
             $source_files_path = $cache_files_path;
             $using_cache = true;
         } else {
             // well, we're out out of luck, we're going to need to build
             // the cache files ourselves
             // so we don't release the lock just yet, we'll do it when all
             // is nicely written
             // we remember this by setting $dump_shop_to to the path where cache files
             // will live
             $dump_shop_to = $cache_files_path;
             $using_cache = false;
         }
     }
     // At this point, we know where to take the files from, i.e.,
     // from $source_files_path
     // We now determine where to copy them to...
     $shop_name = basename($conf->getAsAbsolutePath('shop.filesystem_path'));
     // Temporary shop needs unique URL / folder-name
     if ($options['temporary']) {
         $suffix = '_tmpshpcpy_' . $this->getUID();
         $shop_name .= $suffix;
     } else {
         $suffix = '';
     }
     // Our shop URL
     $url = preg_replace('#[^/]+/?$#', $shop_name . '/', $conf->get('shop.front_office_url'));
     // Where the shop will live
     $target_files_path = FS::join($conf->getAsAbsolutePath('shop.path_to_web_root'), $shop_name);
     $target_same_as_source = realpath($source_files_path) === realpath($target_files_path);
     // We say we are doing a new installation if the target files are not there
     $new_install = !file_exists($target_files_path);
     if (!$new_install && $options['overwrite'] && !$target_same_as_source) {
         FS::webRmR($target_files_path, $url);
         $new_install = true;
     }
     // Finally put the shop files in place!
     if (!$target_same_as_source && $new_install) {
         FileManagement::copyShopFiles($source_files_path, $target_files_path);
     }
     // Update the configuration with the new values
     $conf->set('shop.front_office_url', $url);
     $conf->set('shop.filesystem_path', $target_files_path);
     $conf->set('shop.mysql_database', $conf->get('shop.mysql_database') . $suffix);
     if (!isset($options['browser'])) {
         // Prepare to fire up selenium
         $seleniumHost = SeleniumManager::getHost();
         $seleniumSettings = ['host' => $seleniumHost];
         // Hoorah! Build our shop
         $shop = new Shop($conf->get('shop'), $seleniumSettings);
     } else {
         $shop = new Shop($conf->get('shop'), null);
         $shop->setBrowser($options['browser']);
     }
     $shop->setOptionProvider($this->optionProvider);
     if ($inplace && !$new_install) {
         // nothing for now
     } elseif (!$using_cache && ($new_install || $options['overwrite'])) {
         $shop->getFixtureManager()->setupInitialState($options['initial_state']);
         if ($dump_shop_to) {
             $shop->getFileManager()->copyShopFilesTo($dump_shop_to);
             $shop->getDatabaseManager()->dumpTo(FS::join($dump_shop_to, 'pstaf.shopdb.sql'));
         }
     } elseif ($using_cache) {
         $dump_path = FS::join($source_files_path, 'pstaf.shopdb.sql');
         if (file_exists($dump_path)) {
             $shop->getDatabaseManager()->loadDump($dump_path)->changeShopUrlPhysicalURI("/{$shop_name}/");
         }
         $shop->getFileManager()->updateSettingsIncIfExists(['_DB_NAME_' => $conf->get('shop.mysql_database')])->changeHtaccessPhysicalURI("/{$shop_name}/");
     }
     $shop->setTemporary($options['temporary'] && !$inplace);
     if ($lock) {
         flock($lock, LOCK_UN);
         fclose($lock);
     }
     return $shop;
 }
 public static function getBrowser()
 {
     if (!self::get('browser')) {
         $browser = new Browser(['host' => SeleniumManager::getHost()]);
         self::set('browser', $browser);
     }
     return self::get('browser');
 }