Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14104763
D12621.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
11 KB
Referenced Files
None
Subscribers
None
D12621.diff
View Options
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
@@ -211,6 +211,7 @@
'ArcanistXMLLinterTestCase' => 'lint/linter/__tests__/ArcanistXMLLinterTestCase.php',
'ArcanistXUnitTestResultParser' => 'unit/parser/ArcanistXUnitTestResultParser.php',
'CSharpToolsTestEngine' => 'unit/engine/CSharpToolsTestEngine.php',
+ 'GoTestEngine' => 'unit/engine/GoTestEngine.php',
'NoseTestEngine' => 'unit/engine/NoseTestEngine.php',
'PhpunitTestEngine' => 'unit/engine/PhpunitTestEngine.php',
'PhpunitTestEngineTestCase' => 'unit/engine/__tests__/PhpunitTestEngineTestCase.php',
@@ -389,6 +390,7 @@
'ArcanistXMLLinter' => 'ArcanistLinter',
'ArcanistXMLLinterTestCase' => 'ArcanistLinterTestCase',
'CSharpToolsTestEngine' => 'XUnitTestEngine',
+ 'GoTestEngine' => 'ArcanistUnitTestEngine',
'NoseTestEngine' => 'ArcanistUnitTestEngine',
'PhpunitTestEngine' => 'ArcanistUnitTestEngine',
'PhpunitTestEngineTestCase' => 'ArcanistTestCase',
diff --git a/src/unit/engine/GoTestEngine.php b/src/unit/engine/GoTestEngine.php
new file mode 100644
--- /dev/null
+++ b/src/unit/engine/GoTestEngine.php
@@ -0,0 +1,157 @@
+<?php
+
+/**
+ * Go test Runner
+ */
+final class GoTestEngine extends ArcanistUnitTestEngine {
+
+ const USE_GODEP_KEY = 'unit.go.godep';
+ const USE_RACE_KEY = 'unit.go.race';
+
+ public function run() {
+ $this->projectRoot = $this->getWorkingCopy()->getProjectRoot();
+ $this->cmdTmpl = $this->getCommandTemplate();
+
+ $futures = $this->buildFutures($this->getPaths(), $this->cmdTmpl);
+ if (empty($futures)) {
+ throw new ArcanistNoEffectException('No tests to run.');
+ }
+
+ $results = array();
+ foreach ($futures as $package => $future) {
+ $results = array_merge(
+ $results,
+ $this->resolveFuture($package, $future));
+ }
+
+ return $results;
+ }
+
+ protected function getBinary() {
+ return 'go';
+ }
+
+ protected function getVersion() {
+ $cmd = csprintf('%s version', $this->getBinary());
+ list($stdout) = execx('%C', $cmd);
+ $matches = array();
+ preg_match(
+ '/^go version go(?P<version>[0-9\.]+).*/',
+ $stdout,
+ $matches);
+ return $matches['version'];
+ }
+
+ protected function getDefaultConfig() {
+ return array(
+ self::USE_GODEP_KEY => true,
+ self::USE_RACE_KEY => true,
+ );
+ }
+
+ protected function getCommandTemplate() {
+ $cmd = '';
+ if ($this->useGodep()) {
+ $cmd = 'godep ';
+ }
+
+ $cmd .= 'go test -v';
+
+ if ($this->useRace()) {
+ $cmd .= ' -race';
+ }
+
+ $cmd .= ' ./';
+
+ return $cmd;
+ }
+
+ protected function useRace() {
+ $default = idx($this->getDefaultConfig(), self::USE_RACE_KEY);
+ if ($this->getConfig(self::USE_RACE_KEY, $default) === false) {
+ return false;
+ }
+
+ $version = explode('.', $this->getVersion());
+ if ($version[0] == 1 && $version[1] < 1) {
+ return false;
+ }
+
+ return true;
+ }
+
+ protected function useGodep() {
+ $default = idx($this->getDefaultConfig(), self::USE_GODEP_KEY);
+ if ($this->getConfig(self::USE_GODEP_KEY, $default) === false) {
+ return false;
+ }
+
+ if (is_dir(Filesystem::resolvePath('Godeps', $this->projectRoot))) {
+ return true;
+ }
+
+ return false;
+ }
+
+ protected function getConfig($key, $default = null) {
+ return $this->getConfigurationManager()->getConfigFromAnySource(
+ $key,
+ $default);
+ }
+
+ protected function buildFutures(array $packages, $cmd_tmpl) {
+ $affected_packages = array();
+ foreach ($packages as $package) {
+ // Must always test a package.
+ if (!is_dir($package)) {
+ // If it's a file but not a go file. Skip this test
+ if (substr($package, -3) != '.go') {
+ continue;
+ }
+
+ $package = dirname($package);
+ }
+
+ if (!array_key_exists($package, $affected_packages)) {
+ $affected_packages[] = $package;
+ }
+ }
+
+ $futures = array();
+ foreach ($affected_packages as $package) {
+ if ($package === '.') {
+ $package = '';
+ }
+
+ $future = new ExecFuture(
+ '%C%C',
+ $cmd_tmpl,
+ $package);
+ $future->setCWD($this->projectRoot);
+ $futures[$package] = $future;
+ }
+
+ return $futures;
+ }
+
+ protected function resolveFuture($package, Future $future) {
+ list($err, $stdout, $stderr) = $future->resolve();
+ $parser = new ArcanistGoTestResultParser();
+ $messages = $parser->parseTestResults($package, $stdout, $stderr);
+
+ if ($messages === false) {
+ if ($err) {
+ $future->resolvex();
+ } else {
+ throw new Exception(
+ sprintf(
+ "%s\n\nSTDOUT\n%s\n\nSTDERR\n%s",
+ pht('Linter failed to parse output!'),
+ $stdout,
+ $stderr));
+ }
+ }
+
+ return $messages;
+ }
+}
diff --git a/src/unit/parser/ArcanistGoTestResultParser.php b/src/unit/parser/ArcanistGoTestResultParser.php
--- a/src/unit/parser/ArcanistGoTestResultParser.php
+++ b/src/unit/parser/ArcanistGoTestResultParser.php
@@ -12,11 +12,13 @@
* (e.g. `go test -v`)
*
* @param string $path Path to test
- * @param string $test_results String containing Go test output
+ * @param string $stdout the Stdout of the command.
+ * @param string $stderr the Stderr of the command.
*
* @return array
*/
- public function parseTestResults($path, $test_results) {
+ public function parseTestResults($path, $stdout, $stderr = '') {
+ $test_results = $stderr.$stdout;
$test_results = explode("\n", $test_results);
$results = array();
@@ -25,7 +27,44 @@
// Temp store for test case results (in case we run multiple test cases)
$test_case_results = array();
- foreach ($test_results as $i => $line) {
+ for ($i = 0; $i < count($test_results); $i++) {
+ $line = $test_results[$i];
+
+ if (strlen($line) >= 18
+ && strncmp($line, '==================', 18) === 0
+ && strncmp($test_results[$i + 1], 'WARNING: DATA RACE', 18) === 0) {
+ // We have a race condition
+ $i++; // Advance to WARNING: DATA RACE
+ $reason = '';
+ $test_name = '';
+
+ // loop to collect all data and move to the === line
+ while (strncmp($test_results[$i], '==================', 18) !== 0) {
+ if (strncmp($test_results[$i], 'Goroutine', 9) === 0) {
+ $meta = array();
+ preg_match(
+ '/^.*\.(?P<test_name>[^\.]+)$/',
+ $test_results[$i + 1],
+ $meta);
+ $test_name = $meta['test_name'].' Race Detected';
+ }
+ $reason .= $test_results[$i++]."\n";
+
+ // Are we out of lines?
+ if ($i > count($test_results)) {
+ return false;
+ }
+ }
+
+ $result = new ArcanistUnitTestResult();
+ $result->setName($test_name);
+ $result->setResult(ArcanistUnitTestResult::RESULT_FAIL);
+ $result->setUserData($reason);
+
+ $test_case_results[] = $result;
+
+ continue;
+ }
if (strncmp($line, '--- PASS', 8) === 0) {
// We have a passing test
diff --git a/src/unit/parser/__tests__/ArcanistGoTestResultParserTestCase.php b/src/unit/parser/__tests__/ArcanistGoTestResultParserTestCase.php
--- a/src/unit/parser/__tests__/ArcanistGoTestResultParserTestCase.php
+++ b/src/unit/parser/__tests__/ArcanistGoTestResultParserTestCase.php
@@ -39,6 +39,47 @@
$parsed_results[1]->getResult());
}
+ public function testRaceWarningTestCaseSuccess() {
+ $stubbed_results = Filesystem::readFile(
+ dirname(__FILE__).'/testresults/go.race-warning-test-case-success-go1.4');
+
+ $parsed_results = id(new ArcanistGoTestResultParser())
+ ->parseTestResults('racepackage_test.go', $stubbed_results);
+
+ $this->assertEqual(3, count($parsed_results));
+ $this->assertEqual(
+ ArcanistUnitTestResult::RESULT_FAIL,
+ $parsed_results[0]->getResult());
+ $this->assertEqual(
+ 'WARNING: DATA RACE',
+ idx(explode("\n", $parsed_results[0]->getUserData()), 0));
+ $this->assertEqual(
+ ArcanistUnitTestResult::RESULT_PASS,
+ $parsed_results[1]->getResult());
+ }
+
+ public function testRaceWarningTestCaseFailure() {
+ $stubbed_results = Filesystem::readFile(
+ dirname(__FILE__).'/testresults/go.race-warning-test-case-failure-go1.4');
+
+ $parsed_results = id(new ArcanistGoTestResultParser())
+ ->parseTestResults('racepackage_test.go', $stubbed_results);
+
+ $this->assertEqual(3, count($parsed_results));
+ $this->assertEqual(
+ ArcanistUnitTestResult::RESULT_FAIL,
+ $parsed_results[0]->getResult());
+ $this->assertEqual(
+ 'WARNING: DATA RACE',
+ idx(explode("\n", $parsed_results[0]->getUserData()), 0));
+ $this->assertEqual(
+ ArcanistUnitTestResult::RESULT_PASS,
+ $parsed_results[1]->getResult());
+ $this->assertEqual(
+ "racepackage_test.go:30: got: 2, want: 1\n",
+ $parsed_results[2]->getUserData());
+ }
+
public function testMultipleTestCasesSuccessful() {
$stubbed_results = Filesystem::readFile(
dirname(__FILE__).'/testresults/go.multiple-test-cases-successful');
diff --git a/src/unit/parser/__tests__/testresults/go.race-warning-test-case-failure-go1.4 b/src/unit/parser/__tests__/testresults/go.race-warning-test-case-failure-go1.4
new file mode 100644
--- /dev/null
+++ b/src/unit/parser/__tests__/testresults/go.race-warning-test-case-failure-go1.4
@@ -0,0 +1,29 @@
+==================
+WARNING: DATA RACE
+Write by goroutine 8:
+ racepackage.func~002()
+ /tmp/racepackage_test.go:21 +0x5d
+
+Previous write by goroutine 7:
+ racepackage.func~001()
+ /tmp/racepackage_test.go:17 +0x5d
+
+Goroutine 8 (running) created at:
+ racepackage.TestBar()
+ /tmp/racepackage_test.go:22 +0x1e8
+ testing.tRunner()
+ /usr/local/Cellar/go/1.4.2/libexec/src/testing/testing.go:447 +0x133
+
+Goroutine 7 (finished) created at:
+ racepackage.TestBar()
+ /tmp/racepackage_test.go:18 +0x13e
+ testing.tRunner()
+ /usr/local/Cellar/go/1.4.2/libexec/src/testing/testing.go:447 +0x133
+==================
+=== RUN TestFoo
+--- PASS: TestFoo (0.03s)
+=== RUN TestBar
+--- FAIL: TestBar (0.02s)
+ racepackage_test.go:30: got: 2, want: 1
+PASS
+ok package/racepackage 0.042s
diff --git a/src/unit/parser/__tests__/testresults/go.race-warning-test-case-success-go1.4 b/src/unit/parser/__tests__/testresults/go.race-warning-test-case-success-go1.4
new file mode 100644
--- /dev/null
+++ b/src/unit/parser/__tests__/testresults/go.race-warning-test-case-success-go1.4
@@ -0,0 +1,28 @@
+==================
+WARNING: DATA RACE
+Write by goroutine 8:
+ racepackage.func~002()
+ /tmp/racepackage_test.go:21 +0x5d
+
+Previous write by goroutine 7:
+ racepackage.func~001()
+ /tmp/racepackage_test.go:17 +0x5d
+
+Goroutine 8 (running) created at:
+ racepackage.TestBar()
+ /tmp/racepackage_test.go:22 +0x1e8
+ testing.tRunner()
+ /usr/local/Cellar/go/1.4.2/libexec/src/testing/testing.go:447 +0x133
+
+Goroutine 7 (finished) created at:
+ racepackage.TestBar()
+ /tmp/racepackage_test.go:18 +0x13e
+ testing.tRunner()
+ /usr/local/Cellar/go/1.4.2/libexec/src/testing/testing.go:447 +0x133
+==================
+=== RUN TestFoo
+--- PASS: TestFoo (0.03s)
+=== RUN TestBar
+--- PASS: TestBar (0.01s)
+PASS
+ok package/racepackage 0.042s
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Thu, Nov 28, 12:37 AM (16 h, 49 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6797496
Default Alt Text
D12621.diff (11 KB)
Attached To
Mode
D12621: Add a Go unit test engine with support for Race and Godep.
Attached
Detach File
Event Timeline
Log In to Comment