ExternalCommandRequirementsTrait.php 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. <?php
  2. namespace Drupal\BuildTests\Framework;
  3. use PHPUnit\Framework\SkippedTestError;
  4. use PHPUnit\Util\Test;
  5. use Symfony\Component\Process\ExecutableFinder;
  6. /**
  7. * Allows test classes to require external command line applications.
  8. *
  9. * Use annotation such as '(at)requires externalCommand git'.
  10. */
  11. trait ExternalCommandRequirementsTrait {
  12. /**
  13. * A list of existing external commands we've already discovered.
  14. *
  15. * @var string[]
  16. */
  17. private static $existingCommands = [];
  18. /**
  19. * Checks whether required external commands are available per test class.
  20. *
  21. * @throws \PHPUnit\Framework\SkippedTestError
  22. * Thrown when the requirements are not met, and this test should be
  23. * skipped. Callers should not catch this exception.
  24. */
  25. private static function checkClassCommandRequirements() {
  26. $annotations = Test::parseTestMethodAnnotations(static::class);
  27. if (!empty($annotations['class']['requires'])) {
  28. static::checkExternalCommandRequirements($annotations['class']['requires']);
  29. }
  30. }
  31. /**
  32. * Checks whether required external commands are available per method.
  33. *
  34. * @throws \PHPUnit\Framework\SkippedTestError
  35. * Thrown when the requirements are not met, and this test should be
  36. * skipped. Callers should not catch this exception.
  37. */
  38. private static function checkMethodCommandRequirements($name) {
  39. $annotations = Test::parseTestMethodAnnotations(static::class, $name);
  40. if (!empty($annotations['method']['requires'])) {
  41. static::checkExternalCommandRequirements($annotations['method']['requires']);
  42. }
  43. }
  44. /**
  45. * Checks missing external command requirements.
  46. *
  47. * @param string[] $annotations
  48. * A list of requires annotations from either a method or class annotation.
  49. *
  50. * @throws \PHPUnit\Framework\SkippedTestError
  51. * Thrown when the requirements are not met, and this test should be
  52. * skipped. Callers should not catch this exception.
  53. */
  54. private static function checkExternalCommandRequirements(array $annotations) {
  55. // Make a list of required commands.
  56. $required_commands = [];
  57. foreach ($annotations as $requirement) {
  58. if (strpos($requirement, 'externalCommand ') === 0) {
  59. $command = trim(str_replace('externalCommand ', '', $requirement));
  60. // Use named keys to avoid duplicates.
  61. $required_commands[$command] = $command;
  62. }
  63. }
  64. // Figure out which commands are not available.
  65. $unavailable = [];
  66. foreach ($required_commands as $required_command) {
  67. if (!in_array($required_command, self::$existingCommands)) {
  68. if (static::externalCommandIsAvailable($required_command)) {
  69. // Cache existing commands so we don't have to ask again.
  70. self::$existingCommands[] = $required_command;
  71. }
  72. else {
  73. $unavailable[] = $required_command;
  74. }
  75. }
  76. }
  77. // Skip the test if there were some we couldn't find.
  78. if (!empty($unavailable)) {
  79. throw new SkippedTestError('Required external commands: ' . implode(', ', $unavailable));
  80. }
  81. }
  82. /**
  83. * Determine if an external command is available.
  84. *
  85. * @param $command
  86. * The external command.
  87. *
  88. * @return bool
  89. * TRUE if external command is available, else FALSE.
  90. */
  91. private static function externalCommandIsAvailable($command) {
  92. $finder = new ExecutableFinder();
  93. return (bool) $finder->find($command);
  94. }
  95. }