Page MenuHomePhabricator

D12621.diff
No OneTemporary

D12621.diff

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

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)

Event Timeline