chromium/infra/config/lint-luci-milo.py

#!/usr/bin/env python3
# Copyright 2018 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Checks that the main console and subconsole configs are consistent."""

import collections
import difflib
import os
import sys

THIS_DIR = os.path.dirname(os.path.abspath(__file__))
SRC_ROOT = os.path.join(THIS_DIR, '..', '..')
sys.path.insert(1, os.path.join(
    SRC_ROOT, "third_party", "protobuf", "python"))

import google.protobuf.text_format
import project_pb2


def compare_builders(name, main_builders, sub_builders):
  # Checks that the builders on a subwaterfall on the main waterfall
  # are consistent with the builders on that subwaterfall's main page.
  # For example, checks that the builders on the "chromium.win" section
  # are the same as on the dedicated standalone chromium.win waterfall.
  def to_list(builders, category_prefix=''):
    desc_list = []
    for builder in builders:
      desc_list.append('name: ' + ', '.join(builder.name))
      # A bot with "chromium.win|foo|bar" on the main waterfall should have
      # a category of "foo|bar" on the "chromium.win" subwaterfall.
      category = builder.category
      if category_prefix:
        if category:
          category = category_prefix + '|' + category
        else:
          category = category_prefix
      desc_list.append('category: ' + category)
      desc_list.append('short_name: ' + builder.short_name)
    return desc_list
  main_desc = to_list(main_builders)
  sub_desc = to_list(sub_builders, name)

  if main_desc != sub_desc:
    print('bot lists different between main waterfall ' +
          'and stand-alone %s waterfall:' % name)
    print('\n'.join(
        difflib.unified_diff(main_desc,
                             sub_desc,
                             fromfile='main',
                             tofile=name,
                             lineterm='')))
    print('')
    return False
  return True


def main():
  project = project_pb2.Project()
  with open(os.path.join(THIS_DIR, 'generated', 'luci', 'luci-milo.cfg'),
            'rb') as f:
    google.protobuf.text_format.Parse(f.read(), project,
                                      allow_unknown_field=True)

  # Maps subwaterfall name to list of builders on that subwaterfall
  # on the main waterfall.
  subwaterfalls = collections.defaultdict(list)
  for console in project.consoles:
    if console.id == 'main':
      # Chromium main waterfall console.
      for builder in console.builders:
        subwaterfall = builder.category.split('|', 1)[0]
        subwaterfalls[subwaterfall].append(builder)

  # subwaterfalls contains the waterfalls referenced by the main console
  # Check that every referenced subwaterfall has its own console, unless it's
  # explicitly excluded below.
  excluded_names = [
      # This is the chrome console in src-internal.
      'chrome',
  ]
  all_console_names = [console.id for console in project.consoles]
  referenced_names = set(subwaterfalls.keys())
  missing_names = referenced_names - set(all_console_names + excluded_names)
  if missing_names:
    print('Missing subwaterfall console for', missing_names)
    return 1

  # Check that the bots on a subwaterfall match the corresponding bots on the
  # main waterfall
  all_good = True
  for console in project.consoles:
    if console.id in subwaterfalls:
      if not compare_builders(console.id, subwaterfalls[console.id],
                              console.builders):
        all_good = False
  return 0 if all_good else 1


if __name__ == '__main__':
  sys.exit(main())