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 = '&amp;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')