Page MenuHomePhabricator

D13579.id32820.diff
No OneTemporary

D13579.id32820.diff

diff --git a/.arcconfig b/.arcconfig
--- a/.arcconfig
+++ b/.arcconfig
@@ -1,6 +1,6 @@
{
"phabricator.uri": "https://secure.phabricator.com/",
- "unit.engine": "PhutilUnitTestEngine",
+ "unit.engine": "ArcanistConfigurationDrivenTestEngine",
"load": [
"src/"
]
diff --git a/.arcunit b/.arcunit
new file mode 100644
--- /dev/null
+++ b/.arcunit
@@ -0,0 +1,8 @@
+{
+ "engines": {
+ "phutil": {
+ "include": "(\\.php$)",
+ "type": "phutil"
+ }
+ }
+}
diff --git a/resources/arcunit/example.arcunit b/resources/arcunit/example.arcunit
new file mode 100644
--- /dev/null
+++ b/resources/arcunit/example.arcunit
@@ -0,0 +1,12 @@
+{
+ "engines": {
+ "phpunit": {
+ "include": "(^src/)",
+ "type": "phpunit"
+ },
+ "phutil": {
+ "include": "(^support/arcanist/)",
+ "type": "phutil"
+ }
+ }
+}
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -58,6 +58,7 @@
'ArcanistConcatenationOperatorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistConcatenationOperatorXHPASTLinterRule.php',
'ArcanistConfiguration' => 'configuration/ArcanistConfiguration.php',
'ArcanistConfigurationDrivenLintEngine' => 'lint/engine/ArcanistConfigurationDrivenLintEngine.php',
+ 'ArcanistConfigurationDrivenTestEngine' => 'unit/engine/ArcanistConfigurationDrivenTestEngine.php',
'ArcanistConfigurationManager' => 'configuration/ArcanistConfigurationManager.php',
'ArcanistConsoleLintRenderer' => 'lint/renderer/ArcanistConsoleLintRenderer.php',
'ArcanistConstructorParenthesesXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistConstructorParenthesesXHPASTLinterRule.php',
@@ -331,6 +332,7 @@
'ArcanistConcatenationOperatorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistConfiguration' => 'Phobject',
'ArcanistConfigurationDrivenLintEngine' => 'ArcanistLintEngine',
+ 'ArcanistConfigurationDrivenTestEngine' => 'ArcanistUnitTestEngine',
'ArcanistConfigurationManager' => 'Phobject',
'ArcanistConsoleLintRenderer' => 'ArcanistLintRenderer',
'ArcanistConstructorParenthesesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
diff --git a/src/unit/engine/ArcanistConfigurationDrivenTestEngine.php b/src/unit/engine/ArcanistConfigurationDrivenTestEngine.php
new file mode 100644
--- /dev/null
+++ b/src/unit/engine/ArcanistConfigurationDrivenTestEngine.php
@@ -0,0 +1,184 @@
+<?php
+
+final class ArcanistConfigurationDrivenTestEngine
+ extends ArcanistUnitTestEngine {
+
+ protected function supportsRunAllTests() {
+ return true;
+ }
+
+ public function buildTestEngines() {
+ $working_copy = $this->getWorkingCopy();
+ $config_path = $working_copy->getProjectPath('.arcunit');
+
+ if (!Filesystem::pathExists($config_path)) {
+ throw new ArcanistUsageException(
+ pht(
+ "Unable to find '%s' file to configure test engines. Create an ".
+ "'%s' file in the root directory of the working copy.",
+ '.arcunit',
+ '.arcunit'));
+ }
+
+ $data = Filesystem::readFile($config_path);
+ $config = null;
+ try {
+ $config = phutil_json_decode($data);
+ } catch (PhutilJSONParserException $ex) {
+ throw new PhutilProxyException(
+ pht(
+ "Expected '%s' file to be a valid JSON file, but ".
+ "failed to decode '%s'.",
+ '.arcunit',
+ $config_path),
+ $ex);
+ }
+
+ $test_engines = $this->loadAvailableTestEngines();
+
+ try {
+ PhutilTypeSpec::checkMap(
+ $config,
+ array(
+ 'engines' => 'map<string, map<string, wild>>',
+ ));
+ } catch (PhutilTypeCheckException $ex) {
+ throw new PhutilProxyException(
+ pht("Error in parsing '%s' file.", $config_path),
+ $ex);
+ }
+
+ $built_test_engines = array();
+ $all_paths = $this->getPaths();
+
+ foreach ($config['engines'] as $name => $spec) {
+ $type = idx($spec, 'type');
+
+ if ($type !== null) {
+ if (empty($test_engines[$type])) {
+ throw new ArcanistUsageException(
+ pht(
+ "Test engine '%s' specifies invalid type '%s'. ".
+ "Available test engines are: %s.",
+ $name,
+ $type,
+ implode(', ', array_keys($test_engines))));
+ }
+
+ $test_engine = clone $test_engines[$type];
+ } else {
+ // We'll raise an error below about the invalid "type" key.
+ $test_engine = null;
+ }
+
+ try {
+ PhutilTypeSpec::checkMap(
+ $spec,
+ array(
+ 'type' => 'string',
+ 'include' => 'optional regex | list<regex>',
+ 'exclude' => 'optional regex | list<regex>',
+ ));
+ } catch (PhutilTypeCheckException $ex) {
+ throw new PhutilProxyException(
+ pht(
+ "Error in parsing '%s' file, for test engine '%s'.",
+ '.arcunit',
+ $name),
+ $ex);
+ }
+
+ $include = (array)idx($spec, 'include', array());
+ $exclude = (array)idx($spec, 'exclude', array());
+ $paths = $this->matchPaths(
+ $all_paths,
+ $include,
+ $exclude);
+
+ $test_engine->setPaths($paths);
+ $built_test_engines[] = $test_engine;
+ }
+
+ return $built_test_engines;
+ }
+
+ public function run() {
+ $paths = $this->getPaths();
+
+ // If we are running with `--everything` then `$paths` will be `null`.
+ if (!$paths) {
+ $paths = array();
+ }
+
+ $engines = $this->buildTestEngines();
+ $results = array();
+ $exceptions = array();
+
+ foreach ($engines as $engine) {
+ $engine
+ ->setWorkingCopy($this->getWorkingCopy())
+ ->setEnableAsyncTests($this->getEnableAsyncTests())
+ ->setEnableCoverage($this->getEnableCoverage());
+
+ if ($engine->supportsRunAllTests()) {
+ $engine->setRunAllTests($this->getRunAllTests());
+ }
+
+ try {
+ $results[] = $engine->run();
+ } catch (ArcanistNoEffectException $ex) {
+ $exceptions[] = $ex;
+ }
+ }
+
+ if (!$results) {
+ // If all engines throw an `ArcanistNoEffectException`, then we should
+ // preserve this behavior.
+ throw new ArcanistNoEffectException(pht('No tests to run.'));
+ }
+
+ return array_mergev($results);
+ }
+
+ private function loadAvailableTestEngines() {
+ return id(new PhutilClassMapQuery())
+ ->setAncestorClass('ArcanistUnitTestEngine')
+ ->setUniqueMethod('getEngineConfigurationName', true)
+ ->execute();
+ }
+
+ private function matchPaths(array $paths, array $include, array $exclude) {
+ $match = array();
+
+ foreach ($paths as $path) {
+ $keep = false;
+ if (!$include) {
+ $keep = true;
+ } else {
+ foreach ($include as $rule) {
+ if (preg_match($rule, $path)) {
+ $keep = true;
+ break;
+ }
+ }
+ }
+
+ if (!$keep) {
+ continue;
+ }
+
+ if ($exclude) {
+ foreach ($exclude as $rule) {
+ if (preg_match($rule, $path)) {
+ continue 2;
+ }
+ }
+ }
+
+ $match[] = $path;
+ }
+
+ return $match;
+ }
+
+}
diff --git a/src/unit/engine/ArcanistUnitTestEngine.php b/src/unit/engine/ArcanistUnitTestEngine.php
--- a/src/unit/engine/ArcanistUnitTestEngine.php
+++ b/src/unit/engine/ArcanistUnitTestEngine.php
@@ -16,6 +16,10 @@
final public function __construct() {}
+ public function getEngineConfigurationName() {
+ return null;
+ }
+
final public function setRunAllTests($run_all_tests) {
if (!$this->supportsRunAllTests() && $run_all_tests) {
throw new Exception(
diff --git a/src/unit/engine/PhutilUnitTestEngine.php b/src/unit/engine/PhutilUnitTestEngine.php
--- a/src/unit/engine/PhutilUnitTestEngine.php
+++ b/src/unit/engine/PhutilUnitTestEngine.php
@@ -5,6 +5,10 @@
*/
final class PhutilUnitTestEngine extends ArcanistUnitTestEngine {
+ public function getEngineConfigurationName() {
+ return 'phutil';
+ }
+
protected function supportsRunAllTests() {
return true;
}

File Metadata

Mime Type
text/plain
Expires
Mon, Mar 10, 10:22 AM (1 d, 18 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7428007
Default Alt Text
D13579.id32820.diff (8 KB)

Event Timeline