Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15338293
D13579.id32820.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
8 KB
Referenced Files
None
Subscribers
None
D13579.id32820.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D13579: Rough version of configuration driven unit test engine
Attached
Detach File
Event Timeline
Log In to Comment