github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/gubernator/view_build_test.py (about) 1 # Copyright 2016 The Kubernetes Authors. 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # See the License for the specific language governing permissions and 13 # limitations under the License. 14 15 import os 16 import unittest 17 18 import cloudstorage as gcs 19 20 import view_build 21 22 import main_test 23 import gcs_async_test 24 import github.models 25 import testgrid_test 26 27 app = main_test.app 28 init_build = main_test.init_build 29 write = gcs_async_test.write 30 31 class ParseJunitTest(unittest.TestCase): 32 @staticmethod 33 def parse(xml): 34 parser = view_build.JUnitParser() 35 parser.parse_xml(xml, 'fp') 36 return parser.get_results() 37 38 def test_normal(self): 39 results = self.parse(main_test.JUNIT_SUITE) 40 stack = '/go/src/k8s.io/kubernetes/test.go:123\nError Goes Here' 41 self.assertEqual(results, { 42 'passed': ['Second'], 43 'skipped': ['First'], 44 'failed': [('Third', 96.49, stack, "fp", "")], 45 }) 46 47 def test_testsuites(self): 48 results = self.parse(''' 49 <testsuites> 50 <testsuite name="k8s.io/suite"> 51 <properties> 52 <property name="go.version" value="go1.6"/> 53 </properties> 54 <testcase name="TestBad" time="0.1"> 55 <failure>something bad</failure> 56 <system-out>out: first line</system-out> 57 <system-err>err: first line</system-err> 58 <system-out>out: second line</system-out> 59 </testcase> 60 </testsuite> 61 </testsuites>''') 62 self.assertEqual(results['failed'], [( 63 'k8s.io/suite TestBad', 0.1, 'something bad', "fp", 64 "out: first line\nout: second line\nerr: first line", 65 )]) 66 67 def test_nested_testsuites(self): 68 results = self.parse(''' 69 <testsuites> 70 <testsuite name="k8s.io/suite"> 71 <testsuite name="k8s.io/suite/sub"> 72 <properties> 73 <property name="go.version" value="go1.6"/> 74 </properties> 75 <testcase name="TestBad" time="0.1"> 76 <failure>something bad</failure> 77 <system-out>out: first line</system-out> 78 <system-err>err: first line</system-err> 79 <system-out>out: second line</system-out> 80 </testcase> 81 </testsuite> 82 </testsuite> 83 </testsuites>''') 84 self.assertEqual(results['failed'], [( 85 'k8s.io/suite/sub TestBad', 0.1, 'something bad', "fp", 86 "out: first line\nout: second line\nerr: first line", 87 )]) 88 89 def test_bad_xml(self): 90 self.assertEqual(self.parse('''<body />''')['failed'], []) 91 92 def test_corrupt_xml(self): 93 self.assertEqual(self.parse('<a>\xff</a>')['failed'], []) 94 failures = self.parse(''' 95 <testsuites> 96 <testsuite name="a"> 97 <testcase name="Corrupt" time="0"> 98 <failure>something bad \xff</failure> 99 </testcase> 100 </testsuite> 101 </testsuites>''')['failed'] 102 self.assertEqual(failures, [('a Corrupt', 0.0, 'something bad ?', 'fp', '')]) 103 104 def test_not_xml(self): 105 failures = self.parse('\x01')['failed'] 106 self.assertEqual(failures, 107 [(failures[0][0], 0.0, 'not well-formed (invalid token): line 1, column 0', 'fp', '')]) 108 109 class BuildTest(main_test.TestBase): 110 # pylint: disable=too-many-public-methods 111 112 BUILD_DIR = '/kubernetes-jenkins/logs/somejob/1234/' 113 114 def setUp(self): 115 self.init_stubs() 116 init_build(self.BUILD_DIR) 117 testgrid_test.write_config() 118 119 def get_build_page(self, trailing=''): 120 return app.get('/build' + self.BUILD_DIR + trailing) 121 122 def test_missing(self): 123 """Test that a missing build gives a 404.""" 124 response = app.get('/build' + self.BUILD_DIR.replace('1234', '1235'), 125 status=404) 126 self.assertIn('1235', response) 127 128 def test_missing_started(self): 129 """Test that a missing started.json still renders a proper page.""" 130 build_dir = '/kubernetes-jenkins/logs/job-with-no-started/1234/' 131 init_build(build_dir, started=False) 132 response = app.get('/build' + build_dir) 133 self.assertRegexpMatches(response.body, 'Result.*SUCCESS') 134 self.assertIn('job-with-no-started', response) 135 self.assertNotIn('Started', response) # no start timestamp 136 self.assertNotIn('github.com', response) # no version => no src links 137 138 def test_missing_finished(self): 139 """Test that a missing finished.json still renders a proper page.""" 140 build_dir = '/kubernetes-jenkins/logs/job-still-running/1234/' 141 init_build(build_dir, finished=False) 142 response = app.get('/build' + build_dir) 143 self.assertRegexpMatches(response.body, 'Result.*Not Finished') 144 self.assertIn('job-still-running', response) 145 self.assertIn('Started', response) 146 147 def test_build(self): 148 """Test that the build page works in the happy case.""" 149 response = self.get_build_page() 150 self.assertIn('2014-07-28', response) # started 151 self.assertIn('v1+56', response) # build version 152 self.assertIn('16m40s', response) # build duration 153 self.assertIn('Third', response) # test name 154 self.assertIn('1m36s', response) # test duration 155 self.assertRegexpMatches(response.body, 'Result.*SUCCESS') 156 self.assertIn('Error Goes Here', response) 157 self.assertIn('test.go#L123">', response) # stacktrace link works 158 159 def test_finished_has_version(self): 160 """Test that metadata with version in finished works.""" 161 init_build(self.BUILD_DIR, finished_has_version=True) 162 self.test_build() 163 164 def test_build_no_failures(self): 165 """Test that builds with no Junit artifacts work.""" 166 gcs.delete(self.BUILD_DIR + 'artifacts/junit_01.xml') 167 response = self.get_build_page() 168 self.assertIn('No Test Failures', response) 169 170 def test_show_metadata(self): 171 write(self.BUILD_DIR + 'started.json', 172 { 173 'version': 'v1+56', 174 'timestamp': 1406535800, 175 'jenkins-node': 'agent-light-7', 176 'pull': 'master:1234,35:abcd', 177 'metadata': { 178 'master-version': 'm12' 179 } 180 }) 181 write(self.BUILD_DIR + 'finished.json', 182 { 183 'timestamp': 1406536800, 184 'result': 'SUCCESS', 185 'metadata': { 186 'skew-version': 'm11' 187 } 188 }) 189 response = self.get_build_page() 190 self.assertIn('v1+56', response) 191 self.assertIn('agent-light-7', response) 192 self.assertIn('<td>master-version<td>m12', response) 193 self.assertIn('<td>skew-version<td>m11', response) 194 self.assertIn('1234', response) 195 self.assertIn('abcd', response) 196 197 def test_build_show_log(self): 198 """Test that builds that failed with no failures show the build log.""" 199 gcs.delete(self.BUILD_DIR + 'artifacts/junit_01.xml') 200 write(self.BUILD_DIR + 'finished.json', 201 {'result': 'FAILURE', 'timestamp': 1406536800}) 202 203 # Unable to fetch build-log.txt, still works. 204 response = self.get_build_page() 205 self.assertNotIn('Error lines', response) 206 207 self.testbed.init_memcache_stub() # clear cached result 208 write(self.BUILD_DIR + 'build-log.txt', 209 u'ERROR: test \u039A\n\n\n\n\n\n\n\n\nblah'.encode('utf8')) 210 response = self.get_build_page() 211 self.assertIn('Error lines', response) 212 self.assertIn('No Test Failures', response) 213 self.assertIn('ERROR</span>: test', response) 214 self.assertNotIn('blah', response) 215 216 def test_build_show_passed_skipped(self): 217 response = self.get_build_page() 218 self.assertIn('First', response) 219 self.assertIn('Second', response) 220 self.assertIn('Third', response) 221 self.assertIn('Show 1 Skipped Tests', response) 222 self.assertIn('Show 1 Passed Tests', response) 223 224 def test_build_optional_log(self): 225 write(self.BUILD_DIR + 'build-log.txt', 'error or timeout or something') 226 response = self.get_build_page() 227 self.assertIn('<a href="?log#log">', response) 228 self.assertNotIn('timeout', response) 229 self.assertNotIn('build-log.txt', response) 230 response = self.get_build_page('?log') 231 self.assertIn('timeout', response) 232 self.assertIn('build-log.txt', response) 233 234 def test_build_testgrid_links(self): 235 response = self.get_build_page() 236 base = 'https://k8s-testgrid.appspot.com/k8s#ajob' 237 self.assertIn('a href="%s"' % base, response) 238 option = '&include-filter-by-regex=%5EOverall%24%7CThird' 239 self.assertIn('a href="%s%s"' % (base, option), response) 240 241 def test_build_failure_no_text(self): 242 # Some failures don't have any associated text. 243 write(self.BUILD_DIR + 'artifacts/junit_01.xml', ''' 244 <testsuites> 245 <testsuite tests="1" failures="1" time="3.274" name="k8s.io/test/integration"> 246 <testcase classname="integration" name="TestUnschedulableNodes" time="0.210"> 247 <failure message="Failed" type=""/> 248 </testcase> 249 </testsuite> 250 </testsuites>''') 251 response = self.get_build_page() 252 self.assertIn('TestUnschedulableNodes', response) 253 self.assertIn('junit_01.xml', response) 254 255 def test_build_empty_junit(self): 256 # Sometimes junit files are actually empty (???) 257 write(self.BUILD_DIR + 'artifacts/junit_01.xml', '') 258 response = self.get_build_page() 259 print response 260 self.assertIn('No Test Failures', response) 261 262 def test_parse_pr_path(self): 263 def check(prefix, expected): 264 self.assertEqual( 265 view_build.parse_pr_path(gcs_path=prefix, 266 default_org='kubernetes', 267 default_repo='kubernetes', 268 ), 269 expected 270 ) 271 272 check('kubernetes-jenkins/pr-logs/pull/123', ('123', '', 'kubernetes/kubernetes')) 273 check('kubernetes-jenkins/pr-logs/pull/charts/123', ('123', 'charts/', 'kubernetes/charts')) 274 check( 275 'kubernetes-jenkins/pr-logs/pull/google_cadvisor/296', 276 ('296', 'google/cadvisor/', 'google/cadvisor')) 277 278 def test_build_pr_link(self): 279 ''' The build page for a PR build links to the PR results.''' 280 build_dir = '/kubernetes-jenkins/pr-logs/pull/123/e2e/567/' 281 init_build(build_dir) 282 response = app.get('/build' + build_dir) 283 self.assertIn('PR #123', response) 284 self.assertIn('href="/pr/123"', response) 285 286 def test_build_pr_link_other(self): 287 build_dir = '/kubernetes-jenkins/pr-logs/pull/charts/123/e2e/567/' 288 init_build(build_dir) 289 response = app.get('/build' + build_dir) 290 self.assertIn('PR #123', response) 291 self.assertIn('href="/pr/charts/123"', response) 292 293 def test_build_xref(self): 294 '''Test that builds show issues that reference them.''' 295 github.models.GHIssueDigest.make( 296 'org/repo', 123, True, True, [], 297 {'xrefs': [self.BUILD_DIR[:-1]], 'title': 'an update on testing'}, None).put() 298 response = app.get('/build' + self.BUILD_DIR) 299 self.assertIn('PR #123', response) 300 self.assertIn('an update on testing', response) 301 self.assertIn('org/repo/issues/123', response) 302 303 def test_cache(self): 304 """Test that caching works at some level.""" 305 response = self.get_build_page() 306 gcs.delete(self.BUILD_DIR + 'started.json') 307 gcs.delete(self.BUILD_DIR + 'finished.json') 308 response2 = self.get_build_page() 309 self.assertEqual(str(response), str(response2)) 310 311 def test_build_directory_redir(self): 312 build_dir = '/kubernetes-jenkins/pr-logs/directory/somejob/1234' 313 target_dir = '/kubernetes-jenkins/pr-logs/pull/45/somejob/1234' 314 write(build_dir + '.txt', 'gs:/' + target_dir) 315 resp = app.get('/build' + build_dir) 316 self.assertEqual(resp.status_code, 302) 317 self.assertEqual(resp.location, 'http://localhost/build' + target_dir) 318 319 def do_view_build_list_test(self, job_dir='/buck/some-job/', indirect=False): 320 sta_result = {'timestamp': 12345} 321 fin_result = {'result': 'SUCCESS'} 322 for n in xrange(120): 323 write('%s%d/started.json' % (job_dir, n), sta_result) 324 write('%s%d/finished.json' % (job_dir, n), fin_result) 325 if indirect: 326 for n in xrange(120): 327 write('%sdirectory/%d.txt' % (job_dir, n), 'gs:/%s%d' % (job_dir, n)) 328 329 view_target = job_dir if not indirect else job_dir + 'directory/' 330 331 builds = view_build.build_list(view_target, None) 332 self.assertEqual(builds, 333 [(str(n), '%s%s' % (job_dir, n), sta_result, fin_result) 334 for n in range(119, 79, -1)]) 335 # test that ?before works 336 builds = view_build.build_list(view_target, '80') 337 self.assertEqual(builds, 338 [(str(n), '%s%s' % (job_dir, n), sta_result, fin_result) 339 for n in range(79, 39, -1)]) 340 341 def test_view_build_list_with_latest(self): 342 write('/buck/some-job/latest-build.txt', '119') 343 self.do_view_build_list_test() 344 345 def test_view_build_list_with_old_latest(self): 346 # latest-build.txt is a hint -- it will probe newer by looking for started.json 347 write('/buck/some-job/latest-build.txt', '110') 348 self.do_view_build_list_test() 349 350 def test_view_build_list_no_latest(self): 351 self.do_view_build_list_test() 352 353 def test_view_build_list_indirect_with_latest(self): 354 write('/buck/some-job/directory/latest-build.txt', '119') 355 self.do_view_build_list_test(indirect=True) 356 357 def test_view_build_list_indirect_no_latest(self): 358 self.do_view_build_list_test(indirect=True) 359 360 def test_build_list_handler(self): 361 """Test that the job page shows a list of builds.""" 362 response = app.get('/builds' + os.path.dirname(self.BUILD_DIR[:-1])) 363 self.assertIn('/1234/">1234', response) 364 self.assertIn('gcsweb', response) 365 366 def test_job_list(self): 367 """Test that the job list shows our job.""" 368 response = app.get('/jobs/kubernetes-jenkins/logs') 369 self.assertIn('somejob/">somejob</a>', response) 370 371 def test_recent_runs_across_prs(self): 372 """Test that "Recent Runs Across PRs" links are correct.""" 373 def expect(path, directory): 374 response = app.get('/builds/' + path) 375 self.assertIn('href="/builds/%s"' % directory, response) 376 # pull request job in main repo 377 expect( 378 'k-j/pr-logs/pull/514/pull-kubernetes-unit/', 379 'k-j/pr-logs/directory/pull-kubernetes-unit') 380 # pull request jobs in different repos 381 expect( 382 'k-j/pr-logs/pull/test-infra/4213/pull-test-infra-bazel', 383 'k-j/pr-logs/directory/pull-test-infra-bazel') 384 expect( 385 'i-p/pull/istio_istio/517/istio-presubmit/', 386 'i-p/directory/istio-presubmit')