FeatureContext.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. <?php
  2. use Behat\Behat\Tester\Exception\PendingException;
  3. use Behat\Behat\Context\Context;
  4. use Behat\Behat\Context\SnippetAcceptingContext;
  5. use Behat\Behat\Hook\Scope\BeforeScenarioScope;
  6. use Behat\Behat\Hook\Scope\AfterScenarioScope;
  7. use Behat\Gherkin\Node\PyStringNode;
  8. use Behat\Gherkin\Node\TableNode;
  9. use Supervisor\Configuration\Parser\File as Parser;
  10. use Supervisor\Configuration\Writer\File as Writer;
  11. use Supervisor\Configuration\Section;
  12. use Supervisor\Connector\XmlRpc;
  13. use Supervisor\Supervisor;
  14. use fXmlRpc\Client;
  15. use fXmlRpc\Transport\Guzzle4Bridge;
  16. use GuzzleHttp\Client as GuzzleClient;
  17. /**
  18. * Defines application features from the specific context.
  19. */
  20. class FeatureContext implements Context, SnippetAcceptingContext
  21. {
  22. /**
  23. * Initializes context.
  24. *
  25. * Every scenario gets its own context instance.
  26. * You can also pass arbitrary arguments to the
  27. * context constructor through behat.yml.
  28. */
  29. public function __construct($bin = 'supervisord')
  30. {
  31. $this->bin = $bin;
  32. }
  33. /**
  34. * @BeforeScenario
  35. */
  36. public function setUpSupervisor(BeforeScenarioScope $scope)
  37. {
  38. $parser = new Parser(__DIR__.'/../../resources/supervisord.conf');
  39. $this->configuration = $parser->parse();
  40. $this->setUpConnector();
  41. }
  42. protected function setUpConnector()
  43. {
  44. $client = new Client(
  45. 'http://127.0.0.1:9001/RPC2',
  46. new Guzzle4Bridge(new GuzzleClient(['defaults' => ['auth' => ['user', '123']]]))
  47. );
  48. $connector = new XmlRpc($client);
  49. $this->supervisor = new Supervisor($connector);
  50. }
  51. /**
  52. * @AfterScenario
  53. */
  54. public function stopSupervisor(AfterScenarioScope $scope)
  55. {
  56. isset($this->process) and posix_kill($this->process, SIGKILL);
  57. }
  58. /**
  59. * @Given I have Supervisor running
  60. */
  61. public function iHaveSupervisorRunning()
  62. {
  63. $writer = new Writer($file = tempnam(sys_get_temp_dir(), 'supervisord_'));
  64. $writer->write($this->configuration);
  65. if ($this->supervisor->isConnected()) {
  66. posix_kill($this->supervisor->getPID(), SIGKILL);
  67. }
  68. $command = sprintf('(%s --configuration %s > /dev/null 2>&1 & echo $!)&', $this->bin, $file);
  69. exec($command, $op);
  70. $this->process = (int)$op[0];
  71. $c = 0;
  72. while (!$this->supervisor->isConnected() and $c < 100) {
  73. usleep(10000);
  74. $c++;
  75. }
  76. if ($c >= 100) {
  77. throw new \RuntimeException('Could not connect to supervisord');
  78. }
  79. if ($this->process !== $this->supervisor->getPID()) {
  80. throw new \RuntimeException('Connected to supervisord with a different PID');
  81. }
  82. }
  83. /**
  84. * @When I ask for the API version
  85. */
  86. public function iAskForTheApiVersion()
  87. {
  88. $this->version = $this->supervisor->getAPIVersion();
  89. }
  90. /**
  91. * @Then I should get at least :ver version
  92. */
  93. public function iShouldGetAtLeastVersion($ver)
  94. {
  95. if (version_compare($this->version, $ver) == -1) {
  96. throw new \Exception(sprintf('Version "%s" does not match the minimum required "%s"', $this->version, $ver));
  97. }
  98. }
  99. /**
  100. * @When I ask for Supervisor version
  101. */
  102. public function iAskForSupervisorVersion()
  103. {
  104. $this->version = $this->supervisor->getVersion();
  105. }
  106. /**
  107. * @Given my Supervisor instance is called :identifier
  108. */
  109. public function mySupervisorInstanceIsCalled($identifier)
  110. {
  111. $supervisord = $this->configuration->getSection('supervisord');
  112. $supervisord->setProperty('identifier', $identifier);
  113. }
  114. /**
  115. * @When I ask for Supervisor identification
  116. */
  117. public function iAskForSupervisorIdentification()
  118. {
  119. $this->identifier = $this->supervisor->getIdentification();
  120. }
  121. /**
  122. * @Then I should get :identifier as identifier
  123. */
  124. public function iShouldGetAsIdentifier($identifier)
  125. {
  126. if ($this->identifier !== $identifier) {
  127. throw new \Exception(sprintf('Identification "%s" does not match the required "%s"', $this->identifier, $identifier));
  128. }
  129. }
  130. /**
  131. * @When I ask for the state
  132. */
  133. public function iAskForTheState()
  134. {
  135. $this->state = $this->supervisor->getState();
  136. }
  137. /**
  138. * @Then I should get :code as statecode and :name as statename
  139. */
  140. public function iShouldGetAsStatecodeAndAsStatename($code, $name)
  141. {
  142. if ($this->state['statecode'] != $code) {
  143. throw new \Exception(sprintf('State code "%s" does not match the required "%s"', $this->state['statecode'], $code));
  144. }
  145. if ($this->state['statename'] !== $name) {
  146. throw new \Exception(sprintf('Statename "%s" does not match the required "%s"', $this->state['statename'], $name));
  147. }
  148. }
  149. /**
  150. * @When I ask for the PID
  151. */
  152. public function iAskForThePid()
  153. {
  154. $this->pid = $this->supervisor->getPID();
  155. }
  156. /**
  157. * @Then I should get the real PID
  158. */
  159. public function iShouldGetTheRealPid()
  160. {
  161. if ($this->process !== $this->pid) {
  162. throw new \Exception(sprintf('PID "%s" does not match the real "%s"', $this->pid, $this->process));
  163. }
  164. }
  165. /**
  166. * @When I ask for the log
  167. */
  168. public function iAskForTheLog()
  169. {
  170. $this->log = trim($this->supervisor->readLog(-(35 + strlen($this->process)), 0));
  171. }
  172. /**
  173. * @Then I should get an INFO about supervisord started
  174. */
  175. public function iShouldGetAnInfoAboutSupervisordStarted()
  176. {
  177. if ($this->log !== 'INFO supervisord started with pid '.$this->process) {
  178. throw new \Exception(sprintf('The following log entry was expected: "%s", but we got this: "%s"', 'INFO supervisord started with pid '.$this->process, $this->log));
  179. }
  180. }
  181. /**
  182. * @When I try to call :action action
  183. */
  184. public function iTryToCallAction($action)
  185. {
  186. $this->action = $action;
  187. $this->response = call_user_func([$this->supervisor, $action]);
  188. }
  189. /**
  190. * @When I check if the log is really empty
  191. */
  192. public function iCheckIfTheLogIsReallyEmpty()
  193. {
  194. $this->log = trim($this->supervisor->readLog(-24, 0));
  195. }
  196. /**
  197. * @Then I should get a success response
  198. */
  199. public function iShouldGetASuccessResponse()
  200. {
  201. if ($this->response !== true) {
  202. throw new \Exception(sprintf('Action "%s" was unsuccessful', $this->action));
  203. }
  204. }
  205. /**
  206. * @Then I should get a cleared log
  207. */
  208. public function iShouldGetAClearedLog()
  209. {
  210. if ($this->log !== 'INFO reopening log file') {
  211. throw new \Exception('Empty log cannot be confirmed');
  212. }
  213. }
  214. /**
  215. * @Then it should be stopped
  216. */
  217. public function itShouldBeStopped()
  218. {
  219. if ($this->supervisor->isConnected() === true) {
  220. throw new \Exception('Supervisor is still available');
  221. }
  222. }
  223. /**
  224. * @Then it should be running again
  225. */
  226. public function itShouldBeRunningAgain()
  227. {
  228. if ($this->supervisor->isConnected() !== true) {
  229. throw new \Exception('Supervisor is unavailable');
  230. }
  231. }
  232. /**
  233. * @Given I have a process called :process
  234. */
  235. public function iHaveAProcessCalled($process)
  236. {
  237. $this->processName = $this->processes[] = $process;
  238. $program = new Section\Program($process, [
  239. 'command' => exec('which '.$process),
  240. ]);
  241. $this->configuration->addSection($program);
  242. }
  243. /**
  244. * @When I wait for start
  245. */
  246. public function iWaitForStart()
  247. {
  248. usleep(100000);
  249. }
  250. /**
  251. * @When I get information about the processes
  252. */
  253. public function iGetInformationAboutTheProcesses()
  254. {
  255. $processInfo = $this->supervisor->getAllProcessInfo();
  256. $processNames = array_column($processInfo, 'name');
  257. $this->processInfo = array_combine($processNames, $processInfo);
  258. }
  259. /**
  260. * @Then I should see running
  261. */
  262. public function iShouldSeeRunning()
  263. {
  264. foreach ($this->processes as $process) {
  265. if (!isset($this->processInfo[$process]) or $this->processInfo[$process]['state'] < 10) {
  266. throw new \Exception(sprintf('Process "%s" is not running', $process));
  267. }
  268. }
  269. }
  270. /**
  271. * @Given autostart is disabled
  272. */
  273. public function autostartIsDisabled()
  274. {
  275. $program = $this->configuration->getSection('program:'.$this->processName);
  276. $program->setProperty('autostart', false);
  277. }
  278. /**
  279. * @When I get information about the processes before action
  280. */
  281. public function iGetInformationAboutTheProcessesBeforeAction()
  282. {
  283. $processInfo = $this->supervisor->getAllProcessInfo();
  284. $processNames = array_column($processInfo, 'name');
  285. $this->firstProcessInfo = array_combine($processNames, $processInfo);
  286. }
  287. /**
  288. * @When I :action the process
  289. */
  290. public function iTheProcess($action)
  291. {
  292. $this->action = $action.'Process';
  293. $this->response = call_user_func([$this->supervisor, $this->action], $this->processName, false);
  294. }
  295. /**
  296. * @Then I should see not running first
  297. */
  298. public function iShouldSeeNotRunningFirst()
  299. {
  300. foreach ($this->processes as $process) {
  301. if (!isset($this->firstProcessInfo[$process]) or $this->firstProcessInfo[$process]['state'] > 0) {
  302. throw new \Exception(sprintf('Process "%s" is running', $process));
  303. }
  304. }
  305. }
  306. /**
  307. * @When I :action the processes
  308. */
  309. public function iTheProcesses($action)
  310. {
  311. $this->action = $action.'AllProcesses';
  312. $this->response = call_user_func([$this->supervisor, $this->action], false);
  313. }
  314. /**
  315. * @Then I should get a success response for all
  316. */
  317. public function iShouldGetASuccessResponseForAll()
  318. {
  319. foreach ($this->response as $response) {
  320. if ($response['description'] !== 'OK') {
  321. throw new \Exception(sprintf('Action "%s" was unsuccessful', $this->action));
  322. }
  323. }
  324. }
  325. /**
  326. * @Then I should see running first
  327. */
  328. public function iShouldSeeRunningFirst()
  329. {
  330. foreach ($this->processes as $process) {
  331. if (!isset($this->firstProcessInfo[$process]) or $this->firstProcessInfo[$process]['state'] < 10) {
  332. throw new \Exception(sprintf('Process "%s" is not running before "%s"', $process, $this->action));
  333. }
  334. }
  335. }
  336. /**
  337. * @Then I should see not running
  338. */
  339. public function iShouldSeeNotRunning()
  340. {
  341. foreach ($this->processes as $process) {
  342. if (!isset($this->processInfo[$process]) or $this->processInfo[$process]['state'] > 0) {
  343. throw new \Exception(sprintf('Process "%s" is running', $process));
  344. }
  345. }
  346. }
  347. /**
  348. * @Given it is part of group called :grp
  349. */
  350. public function itIsPartOfGroupCalled($grp)
  351. {
  352. $this->groupName = $grp;
  353. $program = $this->configuration->getSection('program:'.$this->processName);
  354. $group = $this->configuration->getSection('group:'.$grp);
  355. if (is_null($group)) {
  356. $group = new Section\Group($grp, ['programs' => $this->processName]);
  357. $this->configuration->addSection($group);
  358. } else {
  359. $programs = $group->getProperty('programs');
  360. $programs[] = $this->processName;
  361. $group->setProperty('programs', $programs);
  362. }
  363. }
  364. /**
  365. * @When I :action the processes in the group
  366. */
  367. public function iTheProcessesInTheGroup($action)
  368. {
  369. $this->action = $action.'ProcessGroup';
  370. $this->response = call_user_func([$this->supervisor, $this->action], $this->groupName, false);
  371. }
  372. /**
  373. * @Then I should see them as part of the group
  374. */
  375. public function iShouldSeeThemAsPartOfTheGroup()
  376. {
  377. foreach ($this->response as $response) {
  378. if ($response['group'] !== $this->groupName) {
  379. throw new \Exception(sprintf('Process "%s" is not part of the group "%s"', $response['name'], $this->groupName));
  380. }
  381. }
  382. }
  383. }