docs: def finished(self):
          """
          A finished build is a build that satisfies one of the following conditions:
  
          * it has no pending CI test jobs.
          * it has no submitted CI test jobs, and has at least N test runs for each of
            the project environments, where N is configured in
            Environment.expected_test_runs. Environment.expected_test_runs is
            interpreted as follows:
  
              * None (empty):  there must be at least one test run for that
                environment.
              * 0: the environment is ignored, i.e. any amount of test runs will
                be ok, including 0.
              * N > 0: at least N test runs are expected for that environment
  
          """
          reasons = []
  
          # XXX note that by using test_jobs here, we are adding an implicit
          # dependency on squad.ci, what in theory violates our architecture.
          testjobs = self.test_jobs
          if testjobs.count() > 0:
              if testjobs.filter(fetched=False).count() > 0:
                  # a build that has pending CI jobs is NOT finished
                  reasons.append("There are unfinished CI jobs")
              else:
                  # carry on, and check whether the number of expected test runs
                  # per environment is satisfied.
                  pass
  
          # builds with no CI jobs are finished when each environment has
          # received the expected amount of test runs
          testruns = {
              e.id: {
                  'name': str(e),
                  'expected': e.expected_test_runs,
                  'received': 0
              }
              for e in self.project.environments.all()
          }
  
          for t in self.test_runs.filter(completed=True).all():
              testruns[t.environment_id]['received'] += 1
  
          for env, count in testruns.items():
              expected = count['expected']
              received = count['received']
              env_name = count['name']
              if expected == 0:
                  continue
              if received == 0:
                  reasons.append("No test runs for %s received so far" % env_name)
              if expected and received < expected:
                  reasons.append(
                      "%d test runs expected for %s, but only %d received so far" % (
                          expected,
                          env_name,
                          received,
                      )
                  )
          return (len(reasons) == 0, reasons)