go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/lucicfg/starlark/stdlib/internal/luci/lib/resultdb.star (about)

     1  # Copyright 2020 The LUCI 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  """ResultDB integration settings."""
    16  
    17  load("@stdlib//internal/validate.star", "validate")
    18  load(
    19      "@stdlib//internal/luci/proto.star",
    20      "buildbucket_pb",
    21      "predicate_pb",
    22      "resultdb_pb",
    23  )
    24  
    25  def _settings(*, enable = False, bq_exports = None, history_options = None):
    26      """Specifies how Buildbucket should integrate with ResultDB.
    27  
    28      Args:
    29        enable: boolean, whether to enable ResultDB:Buildbucket integration.
    30        bq_exports: list of resultdb_pb.BigQueryExport() protos, configurations
    31          for exporting specific subsets of test results to a designated BigQuery
    32          table, use resultdb.export_test_results(...) to create these.
    33        history_options: Configuration for indexing test results from this
    34          builder's builds for history queries, use resultdb.history_options(...)
    35          to create this value.
    36  
    37      Returns:
    38        A populated buildbucket_pb.BuilderConfig.ResultDB() proto.
    39      """
    40      return buildbucket_pb.BuilderConfig.ResultDB(
    41          enable = validate.bool("enable", enable, default = False, required = False),
    42          bq_exports = bq_exports or [],
    43          history_options = history_options,
    44      )
    45  
    46  def _history_options(*, by_timestamp = False):
    47      """Defines a history indexing configuration.
    48  
    49      Args:
    50        by_timestamp: bool, indicates whether the build's test results will be
    51          indexed by their creation timestamp for the purposes of retrieving the
    52          history of a given set of tests/variants.
    53  
    54      Returns:
    55        A populated resultdb_pb.HistoryOptions() proto.
    56      """
    57      return resultdb_pb.HistoryOptions(
    58          use_invocation_timestamp = by_timestamp,
    59      )
    60  
    61  def _bq_export(bq_table = None):
    62      if type(bq_table) == "tuple":
    63          if len(bq_table) != 3:
    64              fail("Expected tuple of length 3, got %s" % (bq_table,))
    65          project, dataset, table = bq_table
    66      elif type(bq_table) == "string":
    67          project, dataset, table = validate.string(
    68              "bq_table",
    69              bq_table,
    70              regexp = r"^([^.]+)\.([^.]+)\.([^.]+)$",
    71          ).split(".")
    72      else:
    73          fail("Unsupported bq_table type %s" % type(bq_table))
    74  
    75      return resultdb_pb.BigQueryExport(
    76          project = project,
    77          dataset = dataset,
    78          table = table,
    79      )
    80  
    81  def _export_test_results(
    82          *,
    83          bq_table = None,
    84          predicate = None):
    85      """Defines a mapping between a test results and a BigQuery table for them.
    86  
    87      Args:
    88        bq_table: Tuple of `(project, dataset, table)`; OR a string of the form
    89          `<project>.<dataset>.<table>` where the parts represent the
    90          BigQuery-enabled gcp project, dataset and table to export results.
    91        predicate: A predicate_pb.TestResultPredicate() proto. If given, specifies
    92          the subset of test results to export to the above table, instead of all.
    93          Use resultdb.test_result_predicate(...) to generate this, if needed.
    94  
    95      Returns:
    96        A populated resultdb_pb.BigQueryExport() proto.
    97      """
    98      ret = _bq_export(bq_table)
    99      ret.test_results = resultdb_pb.BigQueryExport.TestResults(
   100          predicate = validate.type(
   101              "predicate",
   102              predicate,
   103              predicate_pb.TestResultPredicate(),
   104              required = False,
   105          ),
   106      )
   107      return ret
   108  
   109  def _test_result_predicate(
   110          *,
   111          test_id_regexp = None,
   112          variant = None,
   113          variant_contains = False,
   114          unexpected_only = False):
   115      """Represents a predicate of test results.
   116  
   117      Args:
   118        test_id_regexp: string, regular expression that a test result must fully
   119          match to be considered covered by this definition.
   120        variant: string dict, defines the test variant to match.
   121          E.g. `{"test_suite": "not_site_per_process_webkit_layout_tests"}`
   122        variant_contains: bool, if true the variant parameter above will cause a
   123          match if it's a subset of the test's variant, otherwise it will only
   124          match if it's exactly equal.
   125        unexpected_only: bool, if true only export test results of test variants
   126          that had unexpected results.
   127  
   128      Returns:
   129        A populated predicate_pb.TestResultPredicate() proto.
   130      """
   131      ret = predicate_pb.TestResultPredicate(
   132          test_id_regexp = validate.string(
   133              "test_id_regexp",
   134              test_id_regexp,
   135              required = False,
   136          ),
   137      )
   138  
   139      unexpected_only = validate.bool(
   140          "unexpected_only",
   141          unexpected_only,
   142          default = False,
   143          required = False,
   144      )
   145      if unexpected_only:
   146          ret.expectancy = predicate_pb.TestResultPredicate.VARIANTS_WITH_UNEXPECTED_RESULTS
   147      else:
   148          ret.expectancy = predicate_pb.TestResultPredicate.ALL
   149  
   150      variant_contains = validate.bool(
   151          "variant_contains",
   152          variant_contains,
   153          default = False,
   154          required = False,
   155      )
   156      variant = validate.str_dict("variant", variant, required = False)
   157      if variant:
   158          if variant_contains:
   159              ret.variant.contains = {"def": variant}
   160          else:
   161              ret.variant.equals = {"def": variant}
   162  
   163      return ret
   164  
   165  def _export_text_artifacts(
   166          *,
   167          bq_table = None,
   168          predicate = None):
   169      """Defines a mapping between text artifacts and a BigQuery table for them.
   170  
   171      Args:
   172        bq_table: string of the form `<project>.<dataset>.<table>`
   173          where the parts respresent the BigQuery-enabled gcp project, dataset and
   174          table to export results.
   175        predicate: A predicate_pb.ArtifactPredicate() proto. If given, specifies
   176          the subset of text artifacts to export to the above table, instead of all.
   177          Use resultdb.artifact_predicate(...) to generate this, if needed.
   178  
   179      Returns:
   180        A populated resultdb_pb.BigQueryExport() proto.
   181      """
   182      ret = _bq_export(bq_table)
   183      ret.text_artifacts = resultdb_pb.BigQueryExport.TextArtifacts(
   184          predicate = validate.type(
   185              "predicate",
   186              predicate,
   187              predicate_pb.ArtifactPredicate(),
   188              required = False,
   189          ),
   190      )
   191      return ret
   192  
   193  def _artifact_predicate(
   194          *,
   195          test_result_predicate = None,
   196          included_invocations = None,
   197          test_results = None,
   198          content_type_regexp = None,
   199          artifact_id_regexp = None):
   200      """Represents a predicate of text artifacts.
   201  
   202      Args:
   203        test_result_predicate: predicate_pb.TestResultPredicate(), a predicate of
   204          test results.
   205        included_invocations: bool, if true, invocation level artifacts are
   206          included.
   207        test_results: bool, if true, test result level artifacts are included.
   208        content_type_regexp: string, an artifact must have a content type matching
   209          this regular expression entirely, i.e. the expression is implicitly
   210          wrapped with ^ and $.
   211        artifact_id_regexp: string, an artifact must have an ID matching this
   212          regular expression entirely, i.e. the expression is implicitly wrapped
   213          with ^ and $.
   214  
   215      Returns:
   216        A populated predicate_pb.ArtifactPredicate() proto.
   217      """
   218      ret = predicate_pb.ArtifactPredicate(
   219          test_result_predicate = validate.type(
   220              "test_result_predicate",
   221              test_result_predicate,
   222              predicate_pb.TestResultPredicate(),
   223              required = False,
   224          ),
   225          content_type_regexp = validate.string(
   226              "content_type_regexp",
   227              content_type_regexp,
   228              required = False,
   229          ),
   230          artifact_id_regexp = validate.string(
   231              "artifact_id_regexp",
   232              artifact_id_regexp,
   233              required = False,
   234          ),
   235      )
   236  
   237      included_invocations = validate.bool(
   238          "included_invocations",
   239          included_invocations,
   240          default = None,
   241          required = False,
   242      )
   243      if included_invocations != None:
   244          ret.follow_edges.included_invocations = included_invocations
   245  
   246      test_results = validate.bool(
   247          "test_results",
   248          test_results,
   249          default = None,
   250          required = False,
   251      )
   252      if test_results != None:
   253          ret.follow_edges.test_results = test_results
   254  
   255      return ret
   256  
   257  def _validate_settings(attr, settings):
   258      """Validates the type of a ResultDB settings proto.
   259  
   260      Args:
   261        attr: field name with settings, for error messages. Required.
   262        settings: A proto such as the one returned by resultdb.settings(...).
   263  
   264      Returns:
   265        A validated proto, if it's the correct type.
   266      """
   267      return validate.type(
   268          attr,
   269          settings,
   270          buildbucket_pb.BuilderConfig.ResultDB(),
   271          required = False,
   272      )
   273  
   274  # A struct returned by resultdb.test_presentation.
   275  #
   276  # See resultdb.test_presentation function for all details.
   277  #
   278  # Fields:
   279  #   column_keys: list of string keys that will be rendered as 'columns'.
   280  #   grouping_keys: list of string keys that will be used for grouping tests.
   281  _test_presentation_config_ctor = __native__.genstruct("test_presentation.config")
   282  
   283  def _test_presentation(*, column_keys = None, grouping_keys = None):
   284      """Specifies how test should be rendered.
   285  
   286      Args:
   287        column_keys: list of string keys that will be rendered as 'columns'.
   288          status is always the first column and name is always the last column
   289          (you don't need to specify them). A key must be one of the following:
   290            1. 'v.{variant_key}': variant.def[variant_key] of the test variant
   291              (e.g. v.gpu).
   292          If None, defaults to [].
   293        grouping_keys: list of string keys that will be used for grouping tests.
   294          A key must be one of the following:
   295            1. 'status': status of the test variant.
   296            2. 'name': name of the test variant.
   297            3. 'v.{variant_key}': variant.def[variant_key] of the test variant
   298              (e.g. v.gpu).
   299          If None, defaults to ['status'].
   300          Caveat: test variants with only expected results are not affected by
   301            this setting and are always in their own group.
   302  
   303      Returns:
   304        test_presentation.config struct with fields `column_keys` and
   305        `grouping_keys`.
   306      """
   307      column_keys = validate.str_list("column_keys", column_keys)
   308      for key in column_keys:
   309          if not key.startswith("v."):
   310              fail("invalid column key: %r should be a variant key with 'v.' prefix" % key)
   311  
   312      grouping_keys = validate.str_list("grouping_keys", grouping_keys) or ["status"]
   313      for key in grouping_keys:
   314          if key not in ["status", "name"] and not key.startswith("v."):
   315              fail("invalid grouping key: %r should be 'status', 'name', or a variant key with 'v.' prefix" % key)
   316  
   317      return _test_presentation_config_ctor(
   318          column_keys = column_keys,
   319          grouping_keys = grouping_keys,
   320      )
   321  
   322  def _validate_test_presentation(attr, config, required = False):
   323      """Validates a test presentation config.
   324  
   325      Args:
   326        attr: field name with caches, for error messages. Required.
   327        config: a test_presentation.config to validate.
   328        required: if False, allow 'config' to be None, return None in this case.
   329  
   330      Returns:
   331        A validated test_presentation.config.
   332      """
   333      return validate.struct(attr, config, _test_presentation_config_ctor, required = required)
   334  
   335  def _test_presentation_to_dict(config):
   336      """Converts a test presentation config to a dictionary.
   337  
   338      Args:
   339        config: a test_presentation.config to be converted to a dictionary.
   340  
   341      Returns:
   342        A dictionary representing the test presentation config.
   343      """
   344  
   345      return {
   346          "column_keys": config.column_keys,
   347          "grouping_keys": config.grouping_keys,
   348      }
   349  
   350  resultdb = struct(
   351      settings = _settings,
   352      export_test_results = _export_test_results,
   353      test_result_predicate = _test_result_predicate,
   354      validate_settings = _validate_settings,
   355      history_options = _history_options,
   356      export_text_artifacts = _export_text_artifacts,
   357      artifact_predicate = _artifact_predicate,
   358      test_presentation = _test_presentation,
   359      validate_test_presentation = _validate_test_presentation,
   360  )
   361  
   362  resultdbimpl = struct(
   363      test_presentation_to_dict = _test_presentation_to_dict,
   364  )