Index: appengine/findit/waterfall/test/build_failure_analysis_test.py |
diff --git a/appengine/findit/waterfall/test/build_failure_analysis_test.py b/appengine/findit/waterfall/test/build_failure_analysis_test.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..392b1a910b23c52f6c609134e4acb9f26bb0a151 |
--- /dev/null |
+++ b/appengine/findit/waterfall/test/build_failure_analysis_test.py |
@@ -0,0 +1,209 @@ |
+# Copyright 2015 The Chromium Authors. All rights reserved. |
+# Use of this source code is governed by a BSD-style license that can be |
+# found in the LICENSE file. |
+ |
+import unittest |
+ |
+from common.diff import ChangeType |
+from waterfall import build_failure_analysis |
+from waterfall.failure_signal import FailureSignal |
+ |
+ |
+class BuildFailureAnalysisTest(unittest.TestCase): |
+ |
+ def testIsSameFile(self): |
+ self.assertTrue(build_failure_analysis.IsSameFile('a/b/x.cc', 'x.cc')) |
+ self.assertFalse(build_failure_analysis.IsSameFile('a/b/pre_x.cc.', 'x.cc')) |
+ self.assertFalse(build_failure_analysis.IsSameFile('a/b/x.cc.', 'a/b/y.cc')) |
+ |
+ def testJoinAsFilePath(self): |
+ self.assertEqual('a/x.cc', |
+ build_failure_analysis.JoinAsFilePath('a', 'x.cc')) |
+ self.assertEqual('x.cc', |
+ build_failure_analysis.JoinAsFilePath(None, 'x.cc')) |
+ |
+ def testNormalizeObjectFile(self): |
+ cases = { |
+ 'obj/a/T.x.o': 'a/x.o', |
+ 'obj/a/T.x.y.o': 'a/x.y.o', |
+ 'x.o': 'x.o' |
+ } |
+ for obj_file, expected_file in cases.iteritems(): |
+ self.assertEqual( |
+ expected_file, |
+ build_failure_analysis.NormalizeObjectFile(obj_file)) |
+ |
+ def testStripCommonPostfix(self): |
+ cases = { |
+ 'a_file': |
+ 'a_file_%s.cc' % '_'.join(build_failure_analysis.COMMON_POSTFIXES), |
+ 'src/b_file': 'src/b_file_impl_mac.h', |
+ 'c_file': 'c_file_browsertest.cc' |
+ } |
+ for expected_file, file_path in cases.iteritems(): |
+ self.assertEqual( |
+ expected_file, |
+ build_failure_analysis.StripExtensionAndCommonPostfix(file_path)) |
+ |
+ def testIsCorrelated(self): |
+ self.assertTrue(build_failure_analysis.IsCorrelated('a.h', 'a_test.py')) |
+ self.assertTrue(build_failure_analysis.IsCorrelated('a.h', 'a_impl_test.o')) |
+ self.assertTrue(build_failure_analysis.IsCorrelated('a/b/x.cc', 'a/b/y.cc')) |
qyearsley
2015/01/15 21:15:03
Maybe add a negative case -- when is IsCorrelated
stgao
2015/01/16 20:21:39
Negative case added.
If a CL changed file a.h, an
|
+ |
+ def testCheckFilesAgainstSuspectedCL(self): |
+ failure_signal_json = { |
+ 'files': { |
+ 'src/a/b/f1.cc': [], |
+ 'b/c/f2.h': [10, 20], |
+ 'd/e/f3_test.cc': [], |
+ 'x/y/f4.py': [], |
+ 'f5_impl.cc': [] |
+ } |
+ } |
+ change_log_json = { |
+ 'touched_files': [ |
+ { |
+ 'change_type': ChangeType.ADD, |
+ 'old_path': '/dev/null', |
+ 'new_path': 'a/b/f1.cc' |
+ }, |
+ { |
+ 'change_type': ChangeType.MODIFY, |
+ 'old_path': 'a/b/c/f2.h', |
+ 'new_path': 'a/b/c/f2.h' |
+ }, |
+ { |
+ 'change_type': ChangeType.MODIFY, |
+ 'old_path': 'd/e/f3.h', |
+ 'new_path': 'd/e/f3.h' |
+ }, |
+ { |
+ 'change_type': ChangeType.DELETE, |
+ 'old_path': 'x/y/f4.py', |
+ 'new_path': '/dev/null' |
+ }, |
+ { |
+ 'change_type': ChangeType.DELETE, |
+ 'old_path': 'h/f5.h', |
+ 'new_path': '/dev/null' |
+ }, |
+ { |
+ 'change_type': ChangeType.RENAME, |
+ 'old_path': 't/y/x.cc', |
+ 'new_path': 's/z/x.cc' |
+ }, |
+ ] |
+ } |
+ |
+ justification = build_failure_analysis.CheckFiles( |
+ FailureSignal.FromJson(failure_signal_json), change_log_json) |
+ self.assertIsNotNone(justification) |
+ self.assertEqual(2, justification['suspects']) |
+ self.assertEqual(13, justification['scores']) |
+ |
+ def testCheckFilesAgainstUnrelatedCL(self): |
+ failure_signal_json = { |
+ 'files': { |
+ 'src/a/b/f.cc': [], |
+ } |
+ } |
+ change_log_json = { |
+ 'touched_files': [ |
+ { |
+ 'change_type': ChangeType.ADD, |
+ 'old_path': '/dev/null', |
+ 'new_path': 'a/d/f1.cc' |
+ }, |
+ ] |
+ } |
+ |
+ justification = build_failure_analysis.CheckFiles( |
+ FailureSignal.FromJson(failure_signal_json), change_log_json) |
+ self.assertIsNone(justification) |
+ |
+ def testAnalyzeSuccesfulBuild(self): |
+ failure_info = { |
+ 'failed': False, |
+ } |
+ result = build_failure_analysis.AnalyzeBuildFailure( |
+ failure_info, None, None) |
+ self.assertEqual(0, len(result)) |
+ |
+ def testAnalyzeBuildFailure(self): |
+ failure_info = { |
+ 'failed': True, |
+ 'failed_steps': { |
+ 'a': { |
+ 'current_failure': 99, |
+ 'first_failure': 98, |
+ }, |
+ 'b': { |
+ 'current_failure': 99, |
+ 'first_failure': 98, |
+ }, |
+ }, |
+ 'builds': { |
+ '99': { |
+ 'blame_list': ['r99_1', 'r99_2'], |
+ }, |
+ '98': { |
+ 'blame_list': ['r98_1'], |
+ }, |
+ } |
+ } |
+ change_logs = { |
+ 'r99_1': { |
+ 'touched_files': [ |
+ { |
+ 'change_type': ChangeType.ADD, |
+ 'old_path': '/dev/null', |
+ 'new_path': 'x/y/f99_1.cc' |
+ }, |
+ ], |
+ }, |
+ 'r99_2': { |
+ 'touched_files': [ |
+ { |
+ 'change_type': ChangeType.MODIFY, |
+ 'old_path': 'a/b/f99_2.cc', |
+ 'new_path': 'a/b/f99_2.cc' |
+ }, |
+ ], |
+ }, |
+ 'r98_1': { |
+ 'touched_files': [ |
+ { |
+ 'change_type': ChangeType.MODIFY, |
+ 'old_path': 'y/z/f98.cc', |
+ 'new_path': 'y/z/f98.cc' |
+ }, |
+ ], |
+ }, |
+ } |
+ failure_signals_json = { |
+ 'a': { |
+ 'files': { |
+ 'src/a/b/f99_2.cc': [], |
+ }, |
+ }, |
+ 'b': { |
+ 'files': { |
+ 'f.cc': [], |
+ }, |
+ }, |
+ } |
+ expected_analysis_result = { |
+ 'a': { |
+ 'r99_2': { |
+ 'suspects': 0, |
+ 'scores': 1, |
+ 'hints': [ |
+ 'modify a/b/f99_2.cc', |
+ ] |
+ }, |
+ }, |
+ } |
+ |
+ analysis_result = build_failure_analysis.AnalyzeBuildFailure( |
+ failure_info, change_logs, failure_signals_json) |
+ self.assertEqual(expected_analysis_result, analysis_result) |