LCOV - code coverage report
Current view: top level - server/project - SubmitRequirementsEvaluatorImpl.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 87 87 100.0 %
Date: 2022-11-19 15:00:39 Functions: 15 15 100.0 %

          Line data    Source code
       1             : // Copyright (C) 2021 The Android Open Source Project
       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             : package com.google.gerrit.server.project;
      16             : 
      17             : import static com.google.common.collect.ImmutableMap.toImmutableMap;
      18             : import static com.google.gerrit.server.project.ProjectCache.illegalState;
      19             : 
      20             : import com.google.common.collect.ImmutableMap;
      21             : import com.google.common.flogger.FluentLogger;
      22             : import com.google.gerrit.entities.SubmitRequirement;
      23             : import com.google.gerrit.entities.SubmitRequirementExpression;
      24             : import com.google.gerrit.entities.SubmitRequirementExpressionResult;
      25             : import com.google.gerrit.entities.SubmitRequirementExpressionResult.PredicateResult;
      26             : import com.google.gerrit.entities.SubmitRequirementResult;
      27             : import com.google.gerrit.index.query.Predicate;
      28             : import com.google.gerrit.index.query.QueryParseException;
      29             : import com.google.gerrit.server.plugincontext.PluginSetContext;
      30             : import com.google.gerrit.server.query.change.ChangeData;
      31             : import com.google.gerrit.server.query.change.SubmitRequirementChangeQueryBuilder;
      32             : import com.google.gerrit.server.util.ManualRequestContext;
      33             : import com.google.gerrit.server.util.OneOffRequestContext;
      34             : import com.google.inject.AbstractModule;
      35             : import com.google.inject.Inject;
      36             : import com.google.inject.Module;
      37             : import com.google.inject.Provider;
      38             : import com.google.inject.Scopes;
      39             : import java.util.Map;
      40             : import java.util.Optional;
      41             : import java.util.function.Function;
      42             : import java.util.stream.Stream;
      43             : 
      44             : /** Evaluates submit requirements for different change data. */
      45             : public class SubmitRequirementsEvaluatorImpl implements SubmitRequirementsEvaluator {
      46         152 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      47             : 
      48             :   private final Provider<SubmitRequirementChangeQueryBuilder> queryBuilder;
      49             :   private final ProjectCache projectCache;
      50             :   private final PluginSetContext<SubmitRequirement> globalSubmitRequirements;
      51             :   private final OneOffRequestContext requestContext;
      52             : 
      53             :   public static Module module() {
      54         152 :     return new AbstractModule() {
      55             :       @Override
      56             :       protected void configure() {
      57         152 :         bind(SubmitRequirementsEvaluator.class)
      58         152 :             .to(SubmitRequirementsEvaluatorImpl.class)
      59         152 :             .in(Scopes.SINGLETON);
      60         152 :       }
      61             :     };
      62             :   }
      63             : 
      64             :   @Inject
      65             :   private SubmitRequirementsEvaluatorImpl(
      66             :       Provider<SubmitRequirementChangeQueryBuilder> queryBuilder,
      67             :       ProjectCache projectCache,
      68             :       PluginSetContext<SubmitRequirement> globalSubmitRequirements,
      69         146 :       OneOffRequestContext requestContext) {
      70         146 :     this.queryBuilder = queryBuilder;
      71         146 :     this.projectCache = projectCache;
      72         146 :     this.globalSubmitRequirements = globalSubmitRequirements;
      73         146 :     this.requestContext = requestContext;
      74         146 :   }
      75             : 
      76             :   @Override
      77             :   public void validateExpression(SubmitRequirementExpression expression)
      78             :       throws QueryParseException {
      79           4 :     queryBuilder.get().parse(expression.expressionString());
      80           4 :   }
      81             : 
      82             :   @Override
      83             :   public ImmutableMap<SubmitRequirement, SubmitRequirementResult> evaluateAllRequirements(
      84             :       ChangeData cd) {
      85         103 :     return getRequirements(cd);
      86             :   }
      87             : 
      88             :   @Override
      89             :   public SubmitRequirementResult evaluateRequirement(SubmitRequirement sr, ChangeData cd) {
      90           2 :     try (ManualRequestContext ignored = requestContext.open()) {
      91             :       // Use a request context to execute predicates as an internal user with expanded visibility.
      92             :       // This is so that the evaluation does not depend on who is running the current request (e.g.
      93             :       // a "ownerin" predicate with group that is not visible to the person making this request).
      94             : 
      95             :       Optional<SubmitRequirementExpressionResult> applicabilityResult =
      96           2 :           sr.applicabilityExpression().isPresent()
      97           2 :               ? Optional.of(evaluateExpression(sr.applicabilityExpression().get(), cd))
      98           2 :               : Optional.empty();
      99           2 :       Optional<SubmitRequirementExpressionResult> submittabilityResult =
     100           2 :           Optional.of(
     101           2 :               SubmitRequirementExpressionResult.notEvaluated(sr.submittabilityExpression()));
     102             :       Optional<SubmitRequirementExpressionResult> overrideResult =
     103           2 :           sr.overrideExpression().isPresent()
     104           2 :               ? Optional.of(
     105           2 :                   SubmitRequirementExpressionResult.notEvaluated(sr.overrideExpression().get()))
     106           2 :               : Optional.empty();
     107           2 :       if (!sr.applicabilityExpression().isPresent()
     108           2 :           || SubmitRequirementResult.assertPass(applicabilityResult)) {
     109           2 :         submittabilityResult = Optional.of(evaluateExpression(sr.submittabilityExpression(), cd));
     110             :         overrideResult =
     111           2 :             sr.overrideExpression().isPresent()
     112           2 :                 ? Optional.of(evaluateExpression(sr.overrideExpression().get(), cd))
     113           2 :                 : Optional.empty();
     114             :       }
     115             : 
     116           2 :       if (applicabilityResult.isPresent()) {
     117           2 :         logger.atFine().log(
     118             :             "Applicability expression result for SR name '%s':"
     119             :                 + " passing atoms: %s, failing atoms: %s",
     120           2 :             sr.name(),
     121           2 :             applicabilityResult.get().passingAtoms(),
     122           2 :             applicabilityResult.get().failingAtoms());
     123             :       }
     124           2 :       if (submittabilityResult.isPresent()) {
     125           2 :         logger.atFine().log(
     126             :             "Submittability expression result for SR name '%s':"
     127             :                 + " passing atoms: %s, failing atoms: %s",
     128           2 :             sr.name(),
     129           2 :             submittabilityResult.get().passingAtoms(),
     130           2 :             submittabilityResult.get().failingAtoms());
     131             :       }
     132           2 :       if (overrideResult.isPresent()) {
     133           2 :         logger.atFine().log(
     134             :             "Override expression result for SR name '%s':"
     135             :                 + " passing atoms: %s, failing atoms: %s",
     136           2 :             sr.name(), overrideResult.get().passingAtoms(), overrideResult.get().failingAtoms());
     137             :       }
     138             : 
     139           2 :       return SubmitRequirementResult.builder()
     140           2 :           .legacy(Optional.of(false))
     141           2 :           .submitRequirement(sr)
     142           2 :           .patchSetCommitId(cd.currentPatchSet().commitId())
     143           2 :           .submittabilityExpressionResult(submittabilityResult)
     144           2 :           .applicabilityExpressionResult(applicabilityResult)
     145           2 :           .overrideExpressionResult(overrideResult)
     146           2 :           .build();
     147             :     }
     148             :   }
     149             : 
     150             :   @Override
     151             :   public SubmitRequirementExpressionResult evaluateExpression(
     152             :       SubmitRequirementExpression expression, ChangeData changeData) {
     153             :     try {
     154           3 :       Predicate<ChangeData> predicate = queryBuilder.get().parse(expression.expressionString());
     155           3 :       PredicateResult predicateResult = evaluatePredicateTree(predicate, changeData);
     156           3 :       return SubmitRequirementExpressionResult.create(expression, predicateResult);
     157           3 :     } catch (QueryParseException | SubmitRequirementEvaluationException e) {
     158           3 :       return SubmitRequirementExpressionResult.error(expression, e.getMessage());
     159             :     }
     160             :   }
     161             : 
     162             :   /**
     163             :    * Evaluate and return all {@link SubmitRequirement}s.
     164             :    *
     165             :    * <p>This includes all globally bound {@link SubmitRequirement}s, as well as requirements stored
     166             :    * in this project's config and its parents.
     167             :    *
     168             :    * <p>The behaviour in case of the name match is controlled by {@link
     169             :    * SubmitRequirement#allowOverrideInChildProjects} of global {@link SubmitRequirement}.
     170             :    */
     171             :   private ImmutableMap<SubmitRequirement, SubmitRequirementResult> getRequirements(ChangeData cd) {
     172         103 :     Map<String, SubmitRequirement> globalRequirements = getGlobalRequirements();
     173             : 
     174         103 :     ProjectState state = projectCache.get(cd.project()).orElseThrow(illegalState(cd.project()));
     175         103 :     Map<String, SubmitRequirement> projectConfigRequirements = state.getSubmitRequirements();
     176             : 
     177         103 :     ImmutableMap<String, SubmitRequirement> requirements =
     178         103 :         Stream.concat(
     179         103 :                 globalRequirements.entrySet().stream(),
     180         103 :                 projectConfigRequirements.entrySet().stream())
     181         103 :             .collect(
     182         103 :                 toImmutableMap(
     183             :                     Map.Entry::getKey,
     184             :                     Map.Entry::getValue,
     185             :                     (globalSubmitRequirement, projectConfigRequirement) ->
     186             :                         // Override with projectConfigRequirement if allowed by
     187             :                         // globalSubmitRequirement configuration
     188           2 :                         globalSubmitRequirement.allowOverrideInChildProjects()
     189           2 :                             ? projectConfigRequirement
     190           2 :                             : globalSubmitRequirement));
     191             :     ImmutableMap.Builder<SubmitRequirement, SubmitRequirementResult> results =
     192         103 :         ImmutableMap.builder();
     193         103 :     for (SubmitRequirement requirement : requirements.values()) {
     194           2 :       results.put(requirement, evaluateRequirement(requirement, cd));
     195           2 :     }
     196         103 :     return results.build();
     197             :   }
     198             : 
     199             :   /**
     200             :    * Returns a map of all global {@link SubmitRequirement}s, keyed by their lower-case name.
     201             :    *
     202             :    * <p>The global {@link SubmitRequirement}s apply to all projects and can be bound by plugins.
     203             :    */
     204             :   private Map<String, SubmitRequirement> getGlobalRequirements() {
     205         103 :     return globalSubmitRequirements.stream()
     206         103 :         .collect(
     207         103 :             toImmutableMap(
     208         103 :                 globalRequirement -> globalRequirement.name().toLowerCase(), Function.identity()));
     209             :   }
     210             : 
     211             :   /** Evaluate the predicate recursively using change data. */
     212             :   private PredicateResult evaluatePredicateTree(
     213             :       Predicate<ChangeData> predicate, ChangeData changeData) {
     214             :     PredicateResult.Builder predicateResult =
     215           3 :         PredicateResult.builder()
     216           3 :             .predicateString(predicate.isLeaf() ? predicate.getPredicateString() : "")
     217           3 :             .status(predicate.asMatchable().match(changeData));
     218           3 :     predicate
     219           3 :         .getChildren()
     220           3 :         .forEach(
     221           2 :             c -> predicateResult.addChildPredicateResult(evaluatePredicateTree(c, changeData)));
     222           3 :     return predicateResult.build();
     223             :   }
     224             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750