Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(55)

Side by Side Diff: appengine/findit/waterfall/test/identify_try_job_culprit_pipeline_test.py

Issue 1924173003: [Findit] Fix urls in dashboard and result page and prevent duplicated culprits in dashboard. (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: . Created 4 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2015 The Chromium Authors. All rights reserved. 1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 from testing_utils import testing 5 from testing_utils import testing
6 6
7 from common.git_repository import GitRepository 7 from common.git_repository import GitRepository
8 from model import analysis_status 8 from model import analysis_status
9 from model import result_status 9 from model import result_status
10 from model.wf_analysis import WfAnalysis 10 from model.wf_analysis import WfAnalysis
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
95 # Heuristic analysis provided no results, but the try job found a culprit. 95 # Heuristic analysis provided no results, but the try job found a culprit.
96 analysis = WfAnalysis.Create('m', 'b', 1) 96 analysis = WfAnalysis.Create('m', 'b', 1)
97 analysis.result_status = result_status.NOT_FOUND_UNTRIAGED 97 analysis.result_status = result_status.NOT_FOUND_UNTRIAGED
98 analysis.put() 98 analysis.put()
99 99
100 result = { 100 result = {
101 'culprit': { 101 'culprit': {
102 'compile': { 102 'compile': {
103 'revision': 'rev1', 103 'revision': 'rev1',
104 'commit_position': '1', 104 'commit_position': '1',
105 'review_url': 'url_1', 105 'url': 'url_1',
106 'repo_name': 'chromium' 106 'repo_name': 'chromium'
107 } 107 }
108 } 108 }
109 } 109 }
110 110
111 status = identify_try_job_culprit_pipeline._GetResultAnalysisStatus( 111 status = identify_try_job_culprit_pipeline._GetResultAnalysisStatus(
112 analysis, result) 112 analysis, result)
113 113
114 self.assertEqual(status, result_status.FOUND_UNTRIAGED) 114 self.assertEqual(status, result_status.FOUND_UNTRIAGED)
115 115
116 def testGetResultAnalysisStatusWithTryJobCulpritNotFoundCorrect(self): 116 def testGetResultAnalysisStatusWithTryJobCulpritNotFoundCorrect(self):
117 # Heuristic analysis found no results, which was correct. In this case, the 117 # Heuristic analysis found no results, which was correct. In this case, the
118 # try job result is actually a false positive. 118 # try job result is actually a false positive.
119 analysis = WfAnalysis.Create('m', 'b', 1) 119 analysis = WfAnalysis.Create('m', 'b', 1)
120 analysis.result_status = result_status.NOT_FOUND_CORRECT 120 analysis.result_status = result_status.NOT_FOUND_CORRECT
121 analysis.put() 121 analysis.put()
122 122
123 result = { 123 result = {
124 'culprit': { 124 'culprit': {
125 'compile': { 125 'compile': {
126 'revision': 'rev1', 126 'revision': 'rev1',
127 'commit_position': '1', 127 'commit_position': '1',
128 'review_url': 'url_1', 128 'url': 'url_1',
129 'repo_name': 'chromium' 129 'repo_name': 'chromium'
130 } 130 }
131 } 131 }
132 } 132 }
133 133
134 status = identify_try_job_culprit_pipeline._GetResultAnalysisStatus( 134 status = identify_try_job_culprit_pipeline._GetResultAnalysisStatus(
135 analysis, result) 135 analysis, result)
136 136
137 self.assertEqual(status, result_status.FOUND_UNTRIAGED) 137 self.assertEqual(status, result_status.FOUND_UNTRIAGED)
138 138
139 def testGetResultanalysisStatusWithTryJobCulpritNotFoundIncorrect(self): 139 def testGetResultanalysisStatusWithTryJobCulpritNotFoundIncorrect(self):
140 # Heuristic analysis found no results and was triaged to incorrect before a 140 # Heuristic analysis found no results and was triaged to incorrect before a
141 # try job result was found. In this case the try job result should override 141 # try job result was found. In this case the try job result should override
142 # the heuristic result. 142 # the heuristic result.
143 analysis = WfAnalysis.Create('m', 'b', 1) 143 analysis = WfAnalysis.Create('m', 'b', 1)
144 analysis.result_status = result_status.NOT_FOUND_INCORRECT 144 analysis.result_status = result_status.NOT_FOUND_INCORRECT
145 analysis.put() 145 analysis.put()
146 146
147 result = { 147 result = {
148 'culprit': { 148 'culprit': {
149 'compile': { 149 'compile': {
150 'revision': 'rev1', 150 'revision': 'rev1',
151 'commit_position': '1', 151 'commit_position': '1',
152 'review_url': 'url_1', 152 'url': 'url_1',
153 'repo_name': 'chromium' 153 'repo_name': 'chromium'
154 } 154 }
155 } 155 }
156 } 156 }
157 157
158 status = identify_try_job_culprit_pipeline._GetResultAnalysisStatus( 158 status = identify_try_job_culprit_pipeline._GetResultAnalysisStatus(
159 analysis, result) 159 analysis, result)
160 160
161 self.assertEqual(status, result_status.FOUND_UNTRIAGED) 161 self.assertEqual(status, result_status.FOUND_UNTRIAGED)
162 162
163 def testGetResultanalysisStatusWithTryJobCulpritNoHeuristicResult(self): 163 def testGetResultanalysisStatusWithTryJobCulpritNoHeuristicResult(self):
164 # In this case, the try job found a result before the heuristic result is 164 # In this case, the try job found a result before the heuristic result is
165 # available. This case should generally never happen, as heuristic analysis 165 # available. This case should generally never happen, as heuristic analysis
166 # is usually much faster than try jobs. 166 # is usually much faster than try jobs.
167 analysis = WfAnalysis.Create('m', 'b', 1) 167 analysis = WfAnalysis.Create('m', 'b', 1)
168 analysis.put() 168 analysis.put()
169 169
170 result = { 170 result = {
171 'culprit': { 171 'culprit': {
172 'compile': { 172 'compile': {
173 'revision': 'rev1', 173 'revision': 'rev1',
174 'commit_position': '1', 174 'commit_position': '1',
175 'review_url': 'url_1', 175 'url': 'url_1',
176 'repo_name': 'chromium' 176 'repo_name': 'chromium'
177 } 177 }
178 } 178 }
179 } 179 }
180 180
181 status = identify_try_job_culprit_pipeline._GetResultAnalysisStatus( 181 status = identify_try_job_culprit_pipeline._GetResultAnalysisStatus(
182 analysis, result) 182 analysis, result)
183 183
184 self.assertEqual(status, result_status.FOUND_UNTRIAGED) 184 self.assertEqual(status, result_status.FOUND_UNTRIAGED)
185 185
(...skipping 14 matching lines...) Expand all
200 # result should not overwrite it. 200 # result should not overwrite it.
201 analysis = WfAnalysis.Create('m', 'b', 1) 201 analysis = WfAnalysis.Create('m', 'b', 1)
202 analysis.result_status = result_status.FOUND_CORRECT 202 analysis.result_status = result_status.FOUND_CORRECT
203 analysis.put() 203 analysis.put()
204 204
205 result = { 205 result = {
206 'culprit': { 206 'culprit': {
207 'compile': { 207 'compile': {
208 'revision': 'rev1', 208 'revision': 'rev1',
209 'commit_position': '1', 209 'commit_position': '1',
210 'review_url': 'url_1', 210 'url': 'url_1',
211 'repo_name': 'chromium' 211 'repo_name': 'chromium'
212 } 212 }
213 } 213 }
214 } 214 }
215 215
216 status = identify_try_job_culprit_pipeline._GetResultAnalysisStatus( 216 status = identify_try_job_culprit_pipeline._GetResultAnalysisStatus(
217 analysis, result) 217 analysis, result)
218 self.assertEqual(status, result_status.FOUND_CORRECT) 218 self.assertEqual(status, result_status.FOUND_CORRECT)
219 219
220 def testGetResultanalysisStatusWithNoCulpritTriagedCorrect(self): 220 def testGetResultanalysisStatusWithNoCulpritTriagedCorrect(self):
(...skipping 21 matching lines...) Expand all
242 result = {} 242 result = {}
243 243
244 status = identify_try_job_culprit_pipeline._GetResultAnalysisStatus( 244 status = identify_try_job_culprit_pipeline._GetResultAnalysisStatus(
245 analysis, result) 245 analysis, result)
246 self.assertEqual(status, result_status.NOT_FOUND_INCORRECT) 246 self.assertEqual(status, result_status.NOT_FOUND_INCORRECT)
247 247
248 def testGetSuspectedCLsForCompileTryJob(self): 248 def testGetSuspectedCLsForCompileTryJob(self):
249 heuristic_suspected_cl = { 249 heuristic_suspected_cl = {
250 'revision': 'rev1', 250 'revision': 'rev1',
251 'commit_position': '1', 251 'commit_position': '1',
252 'review_url': 'url_1', 252 'url': 'url_1',
253 'repo_name': 'chromium' 253 'repo_name': 'chromium'
254 } 254 }
255 255
256 compile_suspected_cl = { 256 compile_suspected_cl = {
257 'revision': 'rev2', 257 'revision': 'rev2',
258 'commit_position': '2', 258 'commit_position': '2',
259 'review_url': 'url_2', 259 'url': 'url_2',
260 'repo_name': 'chromium' 260 'repo_name': 'chromium'
261 } 261 }
262 262
263 analysis = WfAnalysis.Create('m', 'b', 1) 263 analysis = WfAnalysis.Create('m', 'b', 1)
264 analysis.suspected_cls = [heuristic_suspected_cl] 264 analysis.suspected_cls = [heuristic_suspected_cl]
265 analysis.put() 265 analysis.put()
266 266
267 result = { 267 result = {
268 'culprit': { 268 'culprit': {
269 'compile': compile_suspected_cl 269 'compile': compile_suspected_cl
270 } 270 }
271 } 271 }
272 272
273 self.assertEqual( 273 self.assertEqual(
274 identify_try_job_culprit_pipeline._GetSuspectedCLs(analysis, result), 274 identify_try_job_culprit_pipeline._GetSuspectedCLs(analysis, result),
275 [heuristic_suspected_cl, compile_suspected_cl]) 275 [heuristic_suspected_cl, compile_suspected_cl])
276 276
277 def testGetSuspectedCLsForTestTryJobAndHeuristicResultsSame(self): 277 def testGetSuspectedCLsForTestTryJobAndHeuristicResultsSame(self):
278 suspected_cl = { 278 suspected_cl = {
279 'revision': 'rev1', 279 'revision': 'rev1',
280 'commit_position': '1', 280 'commit_position': '1',
281 'review_url': 'url_1', 281 'url': 'url_1',
282 'repo_name': 'chromium' 282 'repo_name': 'chromium'
283 } 283 }
284 284
285 analysis = WfAnalysis.Create('m', 'b', 1) 285 analysis = WfAnalysis.Create('m', 'b', 1)
286 analysis.suspected_cls = [suspected_cl] 286 analysis.suspected_cls = [suspected_cl]
287 analysis.put() 287 analysis.put()
288 288
289 result = { 289 result = {
290 'culprit': { 290 'culprit': {
291 'compile': suspected_cl 291 'compile': suspected_cl
292 } 292 }
293 } 293 }
294 294
295 self.assertEqual( 295 self.assertEqual(
296 identify_try_job_culprit_pipeline._GetSuspectedCLs(analysis, result), 296 identify_try_job_culprit_pipeline._GetSuspectedCLs(analysis, result),
297 [suspected_cl]) 297 [suspected_cl])
298 298
299 def testGetSuspectedCLsForTestTryJob(self): 299 def testGetSuspectedCLsForTestTryJob(self):
300 suspected_cl1 = { 300 suspected_cl1 = {
301 'revision': 'rev1', 301 'revision': 'rev1',
302 'commit_position': '1', 302 'commit_position': '1',
303 'review_url': 'url_1', 303 'url': 'url_1',
304 'repo_name': 'chromium' 304 'repo_name': 'chromium'
305 } 305 }
306 suspected_cl2 = { 306 suspected_cl2 = {
307 'revision': 'rev2', 307 'revision': 'rev2',
308 'commit_position': '2', 308 'commit_position': '2',
309 'review_url': 'url_2', 309 'url': 'url_2',
310 'repo_name': 'chromium' 310 'repo_name': 'chromium'
311 } 311 }
312 suspected_cl3 = { 312 suspected_cl3 = {
313 'revision': 'rev3', 313 'revision': 'rev3',
314 'commit_position': '3', 314 'commit_position': '3',
315 'review_url': 'url_3', 315 'url': 'url_3',
316 'repo_name': 'chromium' 316 'repo_name': 'chromium'
317 } 317 }
318 318
319 analysis = WfAnalysis.Create('m', 'b', 1) 319 analysis = WfAnalysis.Create('m', 'b', 1)
320 analysis.suspected_cls = [] 320 analysis.suspected_cls = []
321 analysis.put() 321 analysis.put()
322 322
323 result = { 323 result = {
324 'culprit': { 324 'culprit': {
325 'a_test': { 325 'a_test': {
326 'tests': { 326 'tests': {
327 'a_test1': suspected_cl1, 327 'a_test1': suspected_cl1,
328 'a_test2': suspected_cl1 328 'a_test2': suspected_cl1
329 } 329 }
330 }, 330 },
331 'b_test': { 331 'b_test': {
332 'tests': { 332 'tests': {
333 'b_test1': suspected_cl2 333 'b_test1': suspected_cl2
334 } 334 }
335 }, 335 },
336 'c_test': { 336 'c_test': {
337 'revision': 'rev3', 337 'revision': 'rev3',
338 'commit_position': '3', 338 'commit_position': '3',
339 'review_url': 'url_3', 339 'url': 'url_3',
340 'repo_name': 'chromium', 340 'repo_name': 'chromium',
341 'tests': {} 341 'tests': {}
342 } 342 }
343 } 343 }
344 } 344 }
345 345
346 self.assertEqual( 346 self.assertEqual(
347 identify_try_job_culprit_pipeline._GetSuspectedCLs(analysis, result), 347 identify_try_job_culprit_pipeline._GetSuspectedCLs(analysis, result),
348 [suspected_cl3, suspected_cl2, suspected_cl1]) 348 [suspected_cl3, suspected_cl2, suspected_cl1])
349 349
350 def testGetSuspectedCLsForTestTryJobWithHeuristicResult(self): 350 def testGetSuspectedCLsForTestTryJobWithHeuristicResult(self):
351 suspected_cl = { 351 suspected_cl = {
352 'revision': 'rev1', 352 'revision': 'rev1',
353 'commit_position': '1', 353 'commit_position': '1',
354 'review_url': 'url_1', 354 'url': 'url_1',
355 'repo_name': 'chromium' 355 'repo_name': 'chromium'
356 } 356 }
357 357
358 analysis = WfAnalysis.Create('m', 'b', 1) 358 analysis = WfAnalysis.Create('m', 'b', 1)
359 analysis.suspected_cls = [suspected_cl] 359 analysis.suspected_cls = [suspected_cl]
360 analysis.put() 360 analysis.put()
361 361
362 result = { 362 result = {
363 'culprit': { 363 'culprit': {
364 'a_test': { 364 'a_test': {
365 'revision': 'rev1', 365 'revision': 'rev1',
366 'commit_position': '1', 366 'commit_position': '1',
367 'review_url': 'url_1', 367 'url': 'url_1',
368 'repo_name': 'chromium', 368 'repo_name': 'chromium',
369 'tests': {} 369 'tests': {}
370 } 370 }
371 } 371 }
372 } 372 }
373
374 self.assertEqual( 373 self.assertEqual(
375 identify_try_job_culprit_pipeline._GetSuspectedCLs(analysis, result), 374 identify_try_job_culprit_pipeline._GetSuspectedCLs(analysis, result),
376 [suspected_cl]) 375 [suspected_cl])
377 376
378 def testIdentifyCulpritForCompileTryJobNoCulprit(self): 377 def testIdentifyCulpritForCompileTryJobNoCulprit(self):
379 master_name = 'm' 378 master_name = 'm'
380 builder_name = 'b' 379 builder_name = 'b'
381 build_number = 1 380 build_number = 1
382 try_job_id = '1' 381 try_job_id = '1'
383 382
(...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after
437 436
438 pipeline = IdentifyTryJobCulpritPipeline() 437 pipeline = IdentifyTryJobCulpritPipeline()
439 culprit = pipeline.run( 438 culprit = pipeline.run(
440 master_name, builder_name, build_number, ['rev1'], 439 master_name, builder_name, build_number, ['rev1'],
441 TryJobType.COMPILE, '1', compile_result) 440 TryJobType.COMPILE, '1', compile_result)
442 441
443 expected_culprit = 'rev2' 442 expected_culprit = 'rev2'
444 expected_suspected_cl = { 443 expected_suspected_cl = {
445 'revision': 'rev2', 444 'revision': 'rev2',
446 'commit_position': '2', 445 'commit_position': '2',
447 'review_url': 'url_2', 446 'url': 'url_2',
448 'repo_name': 'chromium' 447 'repo_name': 'chromium'
449 } 448 }
450 expected_compile_result = { 449 expected_compile_result = {
451 'report': { 450 'report': {
452 'result': { 451 'result': {
453 'rev1': 'passed', 452 'rev1': 'passed',
454 'rev2': 'failed' 453 'rev2': 'failed'
455 } 454 }
456 }, 455 },
457 'try_job_id': try_job_id, 456 'try_job_id': try_job_id,
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
537 536
538 def testIdentifyCulpritForTestTryJobNoTryJobResultWithHeuristicResult(self): 537 def testIdentifyCulpritForTestTryJobNoTryJobResultWithHeuristicResult(self):
539 master_name = 'm' 538 master_name = 'm'
540 builder_name = 'b' 539 builder_name = 'b'
541 build_number = 1 540 build_number = 1
542 try_job_id = '1' 541 try_job_id = '1'
543 542
544 suspected_cl = { 543 suspected_cl = {
545 'revision': 'rev1', 544 'revision': 'rev1',
546 'commit_position': '1', 545 'commit_position': '1',
547 'review_url': 'url_1', 546 'url': 'url_1',
548 'repo_name': 'chromium' 547 'repo_name': 'chromium'
549 } 548 }
550 549
551 WfTryJobData.Create(try_job_id).put() 550 WfTryJobData.Create(try_job_id).put()
552 try_job = WfTryJob.Create(master_name, builder_name, build_number) 551 try_job = WfTryJob.Create(master_name, builder_name, build_number)
553 try_job.status = analysis_status.RUNNING 552 try_job.status = analysis_status.RUNNING
554 try_job.put() 553 try_job.put()
555 554
556 # Heuristic analysis already provided some results. 555 # Heuristic analysis already provided some results.
557 analysis = WfAnalysis.Create(master_name, builder_name, build_number) 556 analysis = WfAnalysis.Create(master_name, builder_name, build_number)
(...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after
730 analysis.put() 729 analysis.put()
731 730
732 pipeline = IdentifyTryJobCulpritPipeline() 731 pipeline = IdentifyTryJobCulpritPipeline()
733 culprit = pipeline.run( 732 culprit = pipeline.run(
734 master_name, builder_name, build_number, ['rev1', 'rev2'], 733 master_name, builder_name, build_number, ['rev1', 'rev2'],
735 TryJobType.TEST, '1', test_result) 734 TryJobType.TEST, '1', test_result)
736 735
737 a_test1_suspected_cl = { 736 a_test1_suspected_cl = {
738 'revision': 'rev1', 737 'revision': 'rev1',
739 'commit_position': '1', 738 'commit_position': '1',
740 'review_url': 'url_1', 739 'url': 'url_1',
741 'repo_name': 'chromium' 740 'repo_name': 'chromium'
742 } 741 }
743 a_test2_suspected_cl = { 742 a_test2_suspected_cl = {
744 'revision': 'rev2', 743 'revision': 'rev2',
745 'commit_position': '2', 744 'commit_position': '2',
746 'review_url': 'url_2', 745 'url': 'url_2',
747 'repo_name': 'chromium' 746 'repo_name': 'chromium'
748 } 747 }
749 748
750 b_test1_suspected_cl = a_test1_suspected_cl 749 b_test1_suspected_cl = a_test1_suspected_cl
751 750
752 expected_test_result = { 751 expected_test_result = {
753 'report': { 752 'report': {
754 'result': { 753 'result': {
755 'rev1': { 754 'rev1': {
756 'a_test': { 755 'a_test': {
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
796 } 795 }
797 }, 796 },
798 'b_test': { 797 'b_test': {
799 'tests': { 798 'tests': {
800 'b_test1': b_test1_suspected_cl 799 'b_test1': b_test1_suspected_cl
801 } 800 }
802 }, 801 },
803 'c_test': { 802 'c_test': {
804 'revision': 'rev2', 803 'revision': 'rev2',
805 'commit_position': '2', 804 'commit_position': '2',
806 'review_url': 'url_2', 805 'url': 'url_2',
807 'repo_name': 'chromium', 806 'repo_name': 'chromium',
808 'tests': {} 807 'tests': {}
809 } 808 }
810 } 809 }
811 } 810 }
812 811
813 self.assertEqual(expected_test_result['culprit'], culprit) 812 self.assertEqual(expected_test_result['culprit'], culprit)
814 813
815 try_job = WfTryJob.Get(master_name, builder_name, build_number) 814 try_job = WfTryJob.Get(master_name, builder_name, build_number)
816 self.assertEqual(expected_test_result, try_job.test_results[-1]) 815 self.assertEqual(expected_test_result, try_job.test_results[-1])
(...skipping 18 matching lines...) Expand all
835 834
836 def testAnalysisIsUpdatedOnlyIfStatusOrSuspectedCLsChanged(self): 835 def testAnalysisIsUpdatedOnlyIfStatusOrSuspectedCLsChanged(self):
837 master_name = 'm' 836 master_name = 'm'
838 builder_name = 'b' 837 builder_name = 'b'
839 build_number = 1 838 build_number = 1
840 try_job_id = '1' 839 try_job_id = '1'
841 840
842 suspected_cl = { 841 suspected_cl = {
843 'revision': 'rev1', 842 'revision': 'rev1',
844 'commit_position': '1', 843 'commit_position': '1',
845 'review_url': 'url_1', 844 'url': 'url_1',
846 'repo_name': 'chromium' 845 'repo_name': 'chromium'
847 } 846 }
848 847
849 analysis = WfAnalysis.Create(master_name, builder_name, build_number) 848 analysis = WfAnalysis.Create(master_name, builder_name, build_number)
850 analysis.suspected_cls = [suspected_cl] 849 analysis.suspected_cls = [suspected_cl]
851 analysis.result_status = result_status.FOUND_UNTRIAGED 850 analysis.result_status = result_status.FOUND_UNTRIAGED
852 analysis.put() 851 analysis.put()
853 version = analysis.version 852 version = analysis.version
854 compile_result = { 853 compile_result = {
855 'report': { 854 'report': {
(...skipping 20 matching lines...) Expand all
876 try_job.put() 875 try_job.put()
877 876
878 pipeline = IdentifyTryJobCulpritPipeline() 877 pipeline = IdentifyTryJobCulpritPipeline()
879 pipeline.run(master_name, builder_name, build_number, ['rev1'], 878 pipeline.run(master_name, builder_name, build_number, ['rev1'],
880 TryJobType.COMPILE, '1', compile_result) 879 TryJobType.COMPILE, '1', compile_result)
881 880
882 self.assertEqual(analysis.result_status, 881 self.assertEqual(analysis.result_status,
883 result_status.FOUND_UNTRIAGED) 882 result_status.FOUND_UNTRIAGED)
884 self.assertEqual(analysis.suspected_cls, [suspected_cl]) 883 self.assertEqual(analysis.suspected_cls, [suspected_cl])
885 self.assertEqual(version, analysis.version) # No update to analysis. 884 self.assertEqual(version, analysis.version) # No update to analysis.
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698