k8s.io/test-infra@v0.0.0-20240520184403-27c6b4c223d8/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, 'junit_filename.xml') 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, "junit_filename.xml", "")], 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', "junit_filename.xml", 64 "out: first line\nout: second line\nerr: first line", 65 )]) 66 67 def test_testsuites_no_time(self): 68 results = self.parse(""" 69 <testsuites> 70 <testsuite name="k8s.io/suite"> 71 <properties> 72 <property name="go.version" value="go1.6"/> 73 </properties> 74 <testcase name="TestBad"> 75 <failure>something bad</failure> 76 <system-out>out: first line</system-out> 77 <system-err>err: first line</system-err> 78 <system-out>out: second line</system-out> 79 </testcase> 80 </testsuite> 81 </testsuites>""") 82 self.assertEqual(results['failed'], [( 83 'k8s.io/suite TestBad', 0.0, 'something bad', "junit_filename.xml", 84 "out: first line\nout: second line\nerr: first line", 85 )]) 86 87 88 def test_nested_testsuites(self): 89 results = self.parse(""" 90 <testsuites> 91 <testsuite name="k8s.io/suite"> 92 <testsuite name="k8s.io/suite/sub"> 93 <properties> 94 <property name="go.version" value="go1.6"/> 95 </properties> 96 <testcase name="TestBad" time="0.1"> 97 <failure>something bad</failure> 98 <system-out>out: first line</system-out> 99 <system-err>err: first line</system-err> 100 <system-out>out: second line</system-out> 101 </testcase> 102 </testsuite> 103 </testsuite> 104 </testsuites>""") 105 self.assertEqual(results['failed'], [( 106 'k8s.io/suite/sub TestBad', 0.1, 'something bad', "junit_filename.xml", 107 "out: first line\nout: second line\nerr: first line", 108 )]) 109 110 def test_bad_xml(self): 111 self.assertEqual(self.parse("""<body />""")['failed'], []) 112 113 def test_corrupt_xml(self): 114 self.assertEqual(self.parse('<a>\xff</a>')['failed'], []) 115 failures = self.parse(""" 116 <testsuites> 117 <testsuite name="a"> 118 <testcase name="Corrupt" time="0"> 119 <failure>something bad \xff</failure> 120 </testcase> 121 </testsuite> 122 </testsuites>""")['failed'] 123 self.assertEqual(failures, [('a Corrupt', 0.0, 'something bad ?', 124 'junit_filename.xml', '')]) 125 126 def test_not_xml(self): 127 failures = self.parse('\x01')['failed'] 128 self.assertEqual(failures, 129 [(failures[0][0], 0.0, 'not well-formed (invalid token): line 1, column 0', 130 'junit_filename.xml', '')]) 131 132 def test_empty_output(self): 133 results = self.parse(""" 134 <testsuites> 135 <testsuite name="k8s.io/suite"> 136 <testcase name="TestBad" time="0.1"> 137 <failure>something bad</failure> 138 <system-out></system-out> 139 </testcase> 140 </testsuite> 141 </testsuites>""") 142 self.assertEqual(results['failed'], [( 143 'k8s.io/suite TestBad', 0.1, 'something bad', "junit_filename.xml", "")]) 144 145 class BuildTest(main_test.TestBase): 146 # pylint: disable=too-many-public-methods 147 148 JOB_DIR = '/kubernetes-jenkins/logs/somejob/' 149 BUILD_DIR = JOB_DIR + '1234/' 150 151 def setUp(self): 152 self.init_stubs() 153 init_build(self.BUILD_DIR) 154 testgrid_test.write_config() 155 156 def get_build_page(self, trailing=''): 157 return app.get('/build' + self.BUILD_DIR + trailing) 158 159 def test_missing(self): 160 """Test that a missing build gives a 404.""" 161 response = app.get('/build' + self.BUILD_DIR.replace('1234', '1235'), 162 status=404) 163 self.assertIn('1235', response) 164 165 def test_missing_started(self): 166 """Test that a missing started.json still renders a proper page.""" 167 build_dir = '/kubernetes-jenkins/logs/job-with-no-started/1234/' 168 init_build(build_dir, started=False) 169 response = app.get('/build' + build_dir) 170 self.assertRegexpMatches(response.body, 'Result.*SUCCESS') 171 self.assertIn('job-with-no-started', response) 172 self.assertNotIn('Started', response) # no start timestamp 173 self.assertNotIn('github.com', response) # no version => no src links 174 175 def test_missing_finished(self): 176 """Test that a missing finished.json still renders a proper page.""" 177 build_dir = '/kubernetes-jenkins/logs/job-still-running/1234/' 178 init_build(build_dir, finished=False) 179 response = app.get('/build' + build_dir) 180 self.assertRegexpMatches(response.body, 'Result.*Not Finished') 181 self.assertIn('job-still-running', response) 182 self.assertIn('Started', response) 183 184 def test_build(self): 185 """Test that the build page works in the happy case.""" 186 response = self.get_build_page() 187 self.assertIn('2014-07-28', response) # started 188 self.assertIn('v1+56', response) # build version 189 self.assertIn('16m40s', response) # build duration 190 self.assertIn('Third', response) # test name 191 self.assertIn('1m36s', response) # test duration 192 self.assertRegexpMatches(response.body, 'Result.*SUCCESS') 193 self.assertIn('Error Goes Here', response) 194 self.assertIn('test.go#L123">', response) # stacktrace link works 195 196 def test_finished_has_revision(self): 197 """Test that metadata with version in finished works.""" 198 init_build(self.BUILD_DIR, finished_has_version=True) 199 self.test_build() 200 201 def test_build_no_failures(self): 202 """Test that builds with no Junit artifacts work.""" 203 gcs.delete(self.BUILD_DIR + 'artifacts/junit_01.xml') 204 response = self.get_build_page() 205 self.assertIn('No Test Failures', response) 206 207 def test_show_metadata(self): 208 write(self.BUILD_DIR + 'started.json', 209 { 210 'revision': 'v1+56', 211 'timestamp': 1406535800, 212 'node': 'agent-light-7', 213 'pull': 'master:1234,35:abcd,72814', 214 'metadata': { 215 'master-version': 'm12' 216 } 217 }) 218 write(self.BUILD_DIR + 'finished.json', 219 { 220 'timestamp': 1406536800, 221 'passed': True, 222 'metadata': { 223 'skew-version': 'm11' 224 } 225 }) 226 response = self.get_build_page() 227 self.assertIn('v1+56', response) 228 self.assertIn('agent-light-7', response) 229 self.assertIn('<td>master-version<td>m12', response) 230 self.assertIn('<td>skew-version<td>m11', response) 231 self.assertIn('1234', response) 232 self.assertIn('abcd', response) 233 self.assertIn('72814', response) 234 235 def test_build_show_log(self): 236 """Test that builds that failed with no failures show the build log.""" 237 gcs.delete(self.BUILD_DIR + 'artifacts/junit_01.xml') 238 write(self.BUILD_DIR + 'finished.json', 239 {'passed': False, 'timestamp': 1406536800}) 240 241 # Unable to fetch build-log.txt, still works. 242 response = self.get_build_page() 243 self.assertNotIn('Error lines', response) 244 245 self.testbed.init_memcache_stub() # clear cached result 246 write(self.BUILD_DIR + 'build-log.txt', 247 u'ERROR: test \u039A\n\n\n\n\n\n\n\n\nblah'.encode('utf8')) 248 response = self.get_build_page() 249 self.assertIn('Error lines', response) 250 self.assertIn('No Test Failures', response) 251 self.assertIn('ERROR</span>: test', response) 252 self.assertNotIn('blah', response) 253 254 def test_build_show_passed_skipped(self): 255 response = self.get_build_page() 256 self.assertIn('First', response) 257 self.assertIn('Second', response) 258 self.assertIn('Third', response) 259 self.assertIn('Show 1 Skipped Tests', response) 260 self.assertIn('Show 1 Passed Tests', response) 261 262 def test_build_optional_log(self): 263 """Test that passing builds do not show logs by default but display them when requested""" 264 write(self.BUILD_DIR + 'build-log.txt', 'error or timeout or something') 265 response = self.get_build_page() 266 self.assertIn('<a href="?log#log">', response) 267 self.assertNotIn('timeout', response) 268 self.assertNotIn('build-log.txt', response) 269 response = self.get_build_page('?log') 270 self.assertIn('timeout', response) 271 self.assertIn('build-log.txt', response) 272 273 def test_build_testgrid_links(self): 274 response = self.get_build_page() 275 base = 'https://testgrid.k8s.io/k8s#ajob' 276 self.assertIn('a href="%s"' % base, response) 277 option = '&include-filter-by-regex=%5EOverall%24%7CThird' 278 self.assertIn('a href="%s%s"' % (base, option), response) 279 280 def test_build_failure_no_text(self): 281 # Some failures don't have any associated text. 282 write(self.BUILD_DIR + 'artifacts/junit_01.xml', """ 283 <testsuites> 284 <testsuite tests="1" failures="1" time="3.274" name="k8s.io/test/integration"> 285 <testcase classname="integration" name="TestUnschedulableNodes" time="0.210"> 286 <failure message="Failed" type=""/> 287 </testcase> 288 </testsuite> 289 </testsuites>""") 290 response = self.get_build_page() 291 self.assertIn('TestUnschedulableNodes', response) 292 self.assertIn('junit_01.xml', response) 293 294 def test_build_empty_junit(self): 295 # Sometimes junit files are actually empty (???) 296 write(self.BUILD_DIR + 'artifacts/junit_01.xml', '') 297 response = self.get_build_page() 298 print response 299 self.assertIn('No Test Failures', response) 300 301 def test_parse_pr_path(self): 302 def check(prefix, expected): 303 self.assertEqual( 304 view_build.parse_pr_path(gcs_path=prefix, 305 default_org='kubernetes', 306 default_repo='kubernetes', 307 ), 308 expected 309 ) 310 311 check('kubernetes-jenkins/pr-logs/pull/123', ('123', '', 'kubernetes/kubernetes')) 312 check('kubernetes-jenkins/pr-logs/pull/charts/123', ('123', 'charts/', 'kubernetes/charts')) 313 check( 314 'kubernetes-jenkins/pr-logs/pull/google_cadvisor/296', 315 ('296', 'google/cadvisor/', 'google/cadvisor')) 316 check( 317 'kj/pr-logs/pull/kubernetes-sigs_testing_frameworks/49', 318 ('49', 'kubernetes-sigs/testing_frameworks/', 'kubernetes-sigs/testing_frameworks')) 319 320 def test_github_commit_links(self): 321 def check(build_dir, result): 322 init_build(build_dir) 323 response = app.get('/build' + build_dir) 324 self.assertIn(result, response) 325 326 check('/kubernetes-jenkins/logs/ci-kubernetes-e2e/2/', 327 'github.com/kubernetes/kubernetes/commit/') 328 check('/kubernetes-jenkins/pr-logs/pull/charts/123/e2e/40/', 329 'github.com/kubernetes/charts/commit/') 330 check('/kubernetes-jenkins/pr-logs/pull/google_cadvisor/432/e2e/296/', 331 'github.com/google/cadvisor/commit/') 332 333 def test_build_pr_link(self): 334 """ The build page for a PR build links to the PR results.""" 335 build_dir = '/kubernetes-jenkins/pr-logs/pull/123/e2e/567/' 336 init_build(build_dir) 337 response = app.get('/build' + build_dir) 338 self.assertIn('PR #123', response) 339 self.assertIn('href="/pr/123"', response) 340 341 def test_build_pr_link_other(self): 342 build_dir = '/kubernetes-jenkins/pr-logs/pull/charts/123/e2e/567/' 343 init_build(build_dir) 344 response = app.get('/build' + build_dir) 345 self.assertIn('PR #123', response) 346 self.assertIn('href="/pr/charts/123"', response) 347 348 def test_build_xref(self): 349 """Test that builds show issues that reference them.""" 350 github.models.GHIssueDigest.make( 351 'org/repo', 123, True, True, [], 352 {'xrefs': [self.BUILD_DIR[:-1]], 'title': 'an update on testing'}, None).put() 353 response = app.get('/build' + self.BUILD_DIR) 354 self.assertIn('PR #123', response) 355 self.assertIn('an update on testing', response) 356 self.assertIn('org/repo/issues/123', response) 357 358 def test_build_list_xref(self): 359 """Test that builds show issues that reference them.""" 360 github.models.GHIssueDigest.make( 361 'org/repo', 123, False, True, [], 362 {'xrefs': [self.BUILD_DIR[:-1]], 'title': 'an update on testing'}, None).put() 363 response = app.get('/builds' + self.JOB_DIR) 364 self.assertIn('#123', response) 365 self.assertIn('an update on testing', response) 366 self.assertIn('org/repo/issues/123', response) 367 368 def test_cache(self): 369 """Test that caching works at some level.""" 370 response = self.get_build_page() 371 gcs.delete(self.BUILD_DIR + 'started.json') 372 gcs.delete(self.BUILD_DIR + 'finished.json') 373 response2 = self.get_build_page() 374 self.assertEqual(str(response), str(response2)) 375 376 def test_build_directory_redir(self): 377 build_dir = '/kubernetes-jenkins/pr-logs/directory/somejob/1234' 378 target_dir = '/kubernetes-jenkins/pr-logs/pull/45/somejob/1234' 379 write(build_dir + '.txt', 'gs:/' + target_dir) 380 resp = app.get('/build' + build_dir) 381 self.assertEqual(resp.status_code, 302) 382 self.assertEqual(resp.location, 'http://localhost/build' + target_dir) 383 384 def do_view_build_list_test(self, job_dir='/buck/some-job/', indirect=False): 385 sta_result = {'timestamp': 12345} 386 fin_result = {'passed': True, 'result': 'SUCCESS'} 387 for n in xrange(120): 388 write('%s%d/started.json' % (job_dir, n), sta_result) 389 write('%s%d/finished.json' % (job_dir, n), fin_result) 390 if indirect: 391 for n in xrange(120): 392 write('%sdirectory/%d.txt' % (job_dir, n), 'gs:/%s%d' % (job_dir, n)) 393 394 view_target = job_dir if not indirect else job_dir + 'directory/' 395 396 builds, _ = view_build.build_list(view_target, None) 397 self.assertEqual(builds, 398 [(str(n), '%s%s' % (job_dir, n), sta_result, fin_result) 399 for n in range(119, 79, -1)]) 400 # test that ?before works 401 builds, _ = view_build.build_list(view_target, '80') 402 self.assertEqual(builds, 403 [(str(n), '%s%s' % (job_dir, n), sta_result, fin_result) 404 for n in range(79, 39, -1)]) 405 406 def test_view_build_list_with_latest(self): 407 write('/buck/some-job/latest-build.txt', '119') 408 self.do_view_build_list_test() 409 410 def test_view_build_list_with_old_latest(self): 411 # latest-build.txt is a hint -- it will probe newer by looking for started.json 412 write('/buck/some-job/latest-build.txt', '110') 413 self.do_view_build_list_test() 414 415 def test_view_build_list_no_latest(self): 416 self.do_view_build_list_test() 417 418 def test_view_build_list_indirect_with_latest(self): 419 write('/buck/some-job/directory/latest-build.txt', '119') 420 self.do_view_build_list_test(indirect=True) 421 422 def test_view_build_list_indirect_no_latest(self): 423 self.do_view_build_list_test(indirect=True) 424 425 def test_build_list_handler(self): 426 """Test that the job page shows a list of builds.""" 427 response = app.get('/builds' + os.path.dirname(self.BUILD_DIR[:-1])) 428 self.assertIn('/1234/">1234', response) 429 self.assertIn('gcsweb', response) 430 431 def test_job_list(self): 432 """Test that the job list shows our job.""" 433 response = app.get('/jobs/kubernetes-jenkins/logs') 434 self.assertIn('somejob/">somejob</a>', response) 435 436 def test_recent_runs_across_prs(self): 437 """Test that "Recent Runs Across PRs" links are correct.""" 438 def expect(path, directory): 439 response = app.get('/builds/' + path) 440 self.assertIn('href="/builds/%s"' % directory, response) 441 # pull request job in main repo 442 expect( 443 'k-j/pr-logs/pull/514/pull-kubernetes-unit/', 444 'k-j/pr-logs/directory/pull-kubernetes-unit') 445 # pull request jobs in different repos 446 expect( 447 'k-j/pr-logs/pull/test-infra/4213/pull-test-infra-bazel', 448 'k-j/pr-logs/directory/pull-test-infra-bazel') 449 expect( 450 'i-p/pull/istio_istio/517/istio-presubmit/', 451 'i-p/directory/istio-presubmit')