Copyright plugin source. Touched all the source for the copyright plugin for gerrit team review. Omitted .../testdata/archives/ which contains binary archives containing files from .../testdata/licenses/ Change-Id: Ifdc8aae584598ad4ce1f8c8372468b51fb593a4d
diff --git a/src/main/java/com/googlesource/gerrit/plugins/copyright/CheckConfig.java b/src/main/java/com/googlesource/gerrit/plugins/copyright/CheckConfig.java index d9dd534..14c4e7f 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/copyright/CheckConfig.java +++ b/src/main/java/com/googlesource/gerrit/plugins/copyright/CheckConfig.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -18,6 +18,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.common.base.Stopwatch; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.flogger.FluentLogger; @@ -37,6 +38,7 @@ import java.nio.file.Paths; import java.util.Arrays; import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.jgit.errors.ConfigInvalidException; @@ -57,10 +59,11 @@ private static final String LABEL = "label"; private static final String PLUGIN = "plugin"; private static final int BUFFER_SIZE = 2048; - private static final char[] BUFFER = new char[BUFFER_SIZE]; private String pluginName; + /** All-Projects project.config contents. */ private Config configProject; + /** Plugin config from All-Projects project.config file. */ ScannerConfig scannerConfig; public CheckConfig(String pluginName, String projectConfigContents) @@ -82,27 +85,6 @@ this.scannerConfig.readConfigFile(pluginConfig); } - @Override - public boolean equals(Object other) { - if (this == other) { - return true; - } - if (other == null) { - return false; - } - if (other instanceof CheckConfig) { - CheckConfig that = (CheckConfig) other; - return Objects.equals(this.pluginName, that.pluginName) - && Objects.equals(this.scannerConfig, that.scannerConfig); - } - return false; - } - - @Override - public int hashCode() { - return Objects.hash(pluginName, scannerConfig); - } - /** * Validates the final state of {@code trialConfig}. * @@ -111,6 +93,9 @@ */ public static void checkProjectConfig( CopyrightReviewApi reviewApi, boolean pluginEnabled, CheckConfig trialConfig) { + // Warn without blocking project.config pushes when plugin disabled across entire server. + ValidationMessage.Type errorWhenActive = + pluginEnabled ? ValidationMessage.Type.ERROR : ValidationMessage.Type.WARNING; CurrentUser fromUser = reviewApi == null ? null @@ -127,7 +112,7 @@ ? "the plugin" : fromUser.getLoggableName()) + " will vote on"), - pluginEnabled ? ValidationMessage.Type.ERROR : ValidationMessage.Type.WARNING)); + errorWhenActive)); } else { String labelName = trialConfig.scannerConfig.reviewLabel.trim(); if (!trialConfig.configProject.getSubsections(LABEL).contains(labelName)) { @@ -137,8 +122,10 @@ ScannerConfig.KEY_REVIEW_LABEL, labelName, "no [" + LABEL + " \"" + labelName + "\"] section configured."), - pluginEnabled ? ValidationMessage.Type.ERROR : ValidationMessage.Type.WARNING)); + errorWhenActive)); } + + // Enforce at least 1 approver exists for the copyright review label for content changes. String[] voters = trialConfig.configProject.getStringList( ACCESS, RefNames.REFS_HEADS + "*", "label-" + labelName); @@ -160,8 +147,10 @@ + " on " + RefNames.REFS_HEADS + "*"), - pluginEnabled ? ValidationMessage.Type.ERROR : ValidationMessage.Type.WARNING)); + errorWhenActive)); } + + // Enforce an approver exists for the copyright review label for configuration changes. voters = trialConfig.configProject.getStringList( ACCESS, RefNames.REFS_CONFIG, "label-" + labelName); @@ -179,7 +168,7 @@ ScannerConfig.KEY_REVIEW_LABEL, labelName, "no configured approvers for " + labelName + " on " + RefNames.REFS_CONFIG), - pluginEnabled ? ValidationMessage.Type.ERROR : ValidationMessage.Type.WARNING)); + errorWhenActive)); } } if (trialConfig.scannerConfig.reviewers.isEmpty()) { @@ -199,7 +188,7 @@ + " non-interactive user with full voting permissions for the review label '" + trialConfig.scannerConfig.reviewLabel + "'"), - pluginEnabled ? ValidationMessage.Type.ERROR : ValidationMessage.Type.WARNING)); + errorWhenActive)); // TODO: inject ReviewerAdder into reviewApi, and use ReviewerAdder.prepare // a la // https://gerrit.googlesource.com/gerrit/+/refs/heads/master/java/com/google/gerrit/server/restapi/change/PostReview.java#265 @@ -252,7 +241,7 @@ * the output on success into the commit message. * * <p>This method scans the commit message to find the copied text. If the text was created for - * the same pattern signagure, this method returns a single valid finding with the number of + * the same pattern signature, this method returns a single valid finding with the number of * microseconds it took to scan a large file, which can be used to block patterns that cause * excessive backtracking. * @@ -289,7 +278,6 @@ new CopyrightReviewApi.CommitMessageFinding( commitMessage, m.group(), m.group(1), m.start(), m.end())); } - logger.atSevere().log("signature for %s is %s", m.group(1), signature); builder.add( new CopyrightReviewApi.CommitMessageFinding( commitMessage, m.group(), m.start(), m.end())); @@ -329,15 +317,14 @@ /** Checks whether {@code trialConfig} might cause excessive backtracking. */ private long timeLargeFileInMicros() throws IOException { - long startNanos = System.nanoTime(); + Stopwatch sw = Stopwatch.createStarted(); try { IndexedLineReader file = largeFile(); scannerConfig.scanner.findMatches("file", -1, file); } finally { - long elapsedNanos = System.nanoTime() - startNanos; - long elapsedMicros = elapsedNanos / 1000; - logger.atFine().log("timeLargeFile %dms", elapsedMicros / 1000); - return elapsedMicros; + sw.stop(); + logger.atFine().log("timeLargeFile %dms", sw.elapsed(TimeUnit.MILLISECONDS)); + return sw.elapsed(TimeUnit.MICROSECONDS); } } @@ -436,7 +423,7 @@ /** Read the contents of a project.config file from {@code ilr}. */ private static String readProjectConfigFile(IndexedLineReader ilr) throws IOException { StringBuilder sb = new StringBuilder(); - CharBuffer cb = CharBuffer.wrap(BUFFER); + CharBuffer cb = CharBuffer.wrap(new char[BUFFER_SIZE]); while (ilr.read(cb) >= 0) { cb.flip(); sb.append(cb); @@ -459,6 +446,7 @@ return Hashing.farmHashFingerprint64().hashBytes(sb.toString().getBytes(UTF_8)).toString(); } + // TODO: move check from command-line tool to background thread with timeout. /** Entry point for command-line tool to check for excessive backtracking. */ public static void main(String[] args) { if (args.length != 2) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/copyright/CopyrightConfig.java b/src/main/java/com/googlesource/gerrit/plugins/copyright/CopyrightConfig.java index 5c30033..af45703 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/copyright/CopyrightConfig.java +++ b/src/main/java/com/googlesource/gerrit/plugins/copyright/CopyrightConfig.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -16,20 +16,18 @@ import static java.nio.charset.StandardCharsets.UTF_8; -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.flogger.FluentLogger; +import com.google.gerrit.common.Nullable; import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.extensions.api.changes.AddReviewerResult; import com.google.gerrit.extensions.api.changes.ReviewResult; import com.google.gerrit.extensions.events.GitReferenceUpdatedListener; +import com.google.gerrit.extensions.events.LifecycleListener; import com.google.gerrit.extensions.events.RevisionCreatedListener; import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.extensions.restapi.RestApiException; -import com.google.gerrit.metrics.Description; -import com.google.gerrit.metrics.Description.Units; -import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.metrics.Timer0; import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.reviewdb.client.RefNames; @@ -51,7 +49,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.TimeUnit; import javax.inject.Inject; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.lib.Constants; @@ -65,9 +62,13 @@ /** Listener to manage configuration for enforcing review of copyright declarations and licenses. */ @Singleton class CopyrightConfig - implements CommitValidationListener, RevisionCreatedListener, GitReferenceUpdatedListener { + implements CommitValidationListener, + RevisionCreatedListener, + GitReferenceUpdatedListener, + LifecycleListener { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + /** Default value of timeTestMax configuration parameter for avoiding excessive backtracking. */ private final long DEFAULT_MAX_ELAPSED_SECONDS = 8; private final Metrics metrics; @@ -78,49 +79,21 @@ private final PluginConfigFactory pluginConfigFactory; private final CopyrightReviewApi reviewApi; - private PluginConfig gerritConfig; - private CheckConfig checkConfig; + @Nullable private PluginConfig gerritConfig; + @Nullable private CheckConfig checkConfig; static AbstractModule module() { return new AbstractModule() { @Override protected void configure() { DynamicSet.bind(binder(), CommitValidationListener.class).to(CopyrightConfig.class); + DynamicSet.bind(binder(), LifecycleListener.class).to(CopyrightConfig.class); DynamicSet.bind(binder(), RevisionCreatedListener.class).to(CopyrightConfig.class); DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(CopyrightConfig.class); } }; } - @Singleton - private static class Metrics { - final Timer0 readConfigTimer; - final Timer0 checkConfigTimer; - final Timer0 testConfigTimer; - - @Inject - Metrics(MetricMaker metricMaker) { - readConfigTimer = - metricMaker.newTimer( - "read_config_latency", - new Description("Time spent reading and parsing plugin configurations") - .setCumulative() - .setUnit(Units.MICROSECONDS)); - checkConfigTimer = - metricMaker.newTimer( - "check_config_latency", - new Description("Time spent testing proposed plugin configurations") - .setCumulative() - .setUnit(Units.MICROSECONDS)); - testConfigTimer = - metricMaker.newTimer( - "test_config_latency", - new Description("Time spent testing configurations against difficult file pattern") - .setCumulative() - .setUnit(Units.MICROSECONDS)); - } - } - @Inject CopyrightConfig( Metrics metrics, @@ -129,8 +102,7 @@ GitRepositoryManager repoManager, ProjectCache projectCache, PluginConfigFactory pluginConfigFactory, - CopyrightReviewApi reviewApi) - throws IOException, ConfigInvalidException { + CopyrightReviewApi reviewApi) { this.metrics = metrics; this.allProjectsName = allProjectsName; this.pluginName = pluginName; @@ -138,40 +110,28 @@ this.projectCache = projectCache; this.pluginConfigFactory = pluginConfigFactory; this.reviewApi = reviewApi; - - long nanoStart = System.nanoTime(); - try { - checkConfig = readConfig(projectCache.getAllProjects().getProject().getConfigRefState()); - } finally { - long elapsedMicros = (System.nanoTime() - nanoStart) / 1000; - metrics.readConfigTimer.record(elapsedMicros, TimeUnit.MICROSECONDS); - } - } - - private CopyrightConfig( - MetricMaker metricMaker, CopyrightReviewApi reviewApi, String projectConfigContents) - throws ConfigInvalidException { - metrics = new Metrics(metricMaker); - allProjectsName = new AllProjectsName("All-Projects"); - pluginName = "copyright"; - repoManager = null; - projectCache = null; - pluginConfigFactory = null; - this.reviewApi = reviewApi; - checkConfig = new CheckConfig(pluginName, projectConfigContents); - } - - @VisibleForTesting - static CopyrightConfig createTestInstance( - MetricMaker metricMaker, CopyrightReviewApi reviewApi, String projectConfigContents) - throws ConfigInvalidException { - return new CopyrightConfig(metricMaker, reviewApi, projectConfigContents); + this.checkConfig = null; } ScannerConfig getScannerConfig() { - return checkConfig.scannerConfig; + return checkConfig == null ? null : checkConfig.scannerConfig; } + @Override + public void start() { + try (Timer0.Context ctx = metrics.readConfigTimer.start()) { + checkConfig = readConfig(projectCache.getAllProjects().getProject().getConfigRefState()); + } catch (IOException | ConfigInvalidException e) { + logger.atSevere().withCause(e).log("unable to load configuration"); + metrics.configurationErrors.increment(allProjectsName.get()); + metrics.errors.increment(); + return; + } + } + + @Override + public void stop() {} + /** Listens for merges to /refs/meta/config on All-Projects to reload plugin configuration. */ @Override public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event event) { @@ -181,17 +141,13 @@ if (!event.getProjectName().equals(allProjectsName.get())) { return; } - long nanoStart = System.nanoTime(); - try { - clearConfig(); + try (Timer0.Context ctx = metrics.readConfigTimer.start()) { checkConfig = readConfig(event.getNewObjectId()); } catch (IOException | ConfigInvalidException e) { - logger.atSevere().withCause(e).log("%s plugin unable to load configuration", pluginName); - checkConfig = null; + logger.atSevere().withCause(e).log("unable to load configuration"); + metrics.configurationErrors.increment(allProjectsName.get()); + metrics.errors.increment(); return; - } finally { - long elapsedMicros = (System.nanoTime() - nanoStart) / 1000; - metrics.readConfigTimer.record(elapsedMicros, TimeUnit.MICROSECONDS); } } @@ -205,67 +161,56 @@ if (!event.getProjectNameKey().equals(allProjectsName)) { return Collections.emptyList(); } - long readStart = System.nanoTime(); - long checkStart = -1; - long elapsedMicros = -1; CheckConfig trialConfig = null; try { - trialConfig = readConfig(event.commit.getName()); - elapsedMicros = (System.nanoTime() - readStart) / 1000; - metrics.readConfigTimer.record(elapsedMicros, TimeUnit.MICROSECONDS); - if (Objects.equals(trialConfig.scannerConfig, checkConfig.scannerConfig)) { + try (Timer0.Context ctx = metrics.readConfigTimer.start()) { + trialConfig = readConfig(event.commit.getName()); + } + if (Objects.equals( + trialConfig.scannerConfig, checkConfig == null ? null : checkConfig.scannerConfig)) { return Collections.emptyList(); } - checkStart = System.nanoTime(); - long maxElapsedSeconds = - gerritConfig == null - ? DEFAULT_MAX_ELAPSED_SECONDS - : gerritConfig.getLong(ScannerConfig.KEY_TIME_TEST_MAX, DEFAULT_MAX_ELAPSED_SECONDS); - if (maxElapsedSeconds > 0 - && CheckConfig.hasScanner(trialConfig) - && !CheckConfig.scannersEqual(trialConfig, checkConfig)) { - String commitMessage = event.commit.getFullMessage(); - ImmutableList<CopyrightReviewApi.CommitMessageFinding> findings = - trialConfig.checkCommitMessage(commitMessage); - if (CheckConfig.mustReportFindings(findings, maxElapsedSeconds)) { - throw reviewApi.getCommitMessageException(pluginName, findings, maxElapsedSeconds); + try (Timer0.Context ctx = metrics.checkConfigTimer.start()) { + long maxElapsedSeconds = + gerritConfig == null + ? DEFAULT_MAX_ELAPSED_SECONDS + : gerritConfig.getLong( + ScannerConfig.KEY_TIME_TEST_MAX, DEFAULT_MAX_ELAPSED_SECONDS); + if (maxElapsedSeconds > 0 + && CheckConfig.hasScanner(trialConfig) + && !CheckConfig.scannersEqual(trialConfig, checkConfig)) { + String commitMessage = event.commit.getFullMessage(); + ImmutableList<CopyrightReviewApi.CommitMessageFinding> findings = + trialConfig.checkCommitMessage(commitMessage); + if (CheckConfig.mustReportFindings(findings, maxElapsedSeconds)) { + throw reviewApi.getCommitMessageException(pluginName, findings, maxElapsedSeconds); + } } + boolean pluginEnabled = + gerritConfig != null && gerritConfig.getBoolean(ScannerConfig.KEY_ENABLE, false); + CheckConfig.checkProjectConfig(reviewApi, pluginEnabled, trialConfig); + List<CommitValidationMessage> messages = + trialConfig == null || trialConfig.scannerConfig == null + ? Collections.emptyList() + : trialConfig.scannerConfig.messages; + checkForConfigErrors(trialConfig); + return messages; } - boolean pluginEnabled = - gerritConfig != null && gerritConfig.getBoolean(ScannerConfig.KEY_ENABLE, false); - CheckConfig.checkProjectConfig(reviewApi, pluginEnabled, trialConfig); - return trialConfig == null || trialConfig.scannerConfig == null - ? Collections.emptyList() - : trialConfig.scannerConfig.messages; } catch (IOException e) { - logger.atSevere().withCause(e).log( - "failed to read new project.config for %s plugin", pluginName); - throw new CommitValidationException("failed to read new project.config", e); + logger.atSevere().withCause(e).log("failed to read new project.config"); + throw new CommitValidationException( + pluginName + "plugin failed to read new project.config", e); } catch (ConfigInvalidException e) { - logger.atSevere().withCause(e).log("unable to parse %s plugin config", pluginName); + logger.atSevere().withCause(e).log("unable to parse plugin config"); if (trialConfig != null && trialConfig.scannerConfig != null) { trialConfig.scannerConfig.messages.add(ScannerConfig.errorMessage(e.getMessage())); + metrics.configurationErrors.increment(allProjectsName.get()); + metrics.errors.increment(); + checkForConfigErrors(trialConfig); return trialConfig.scannerConfig.messages; } else { - throw new CommitValidationException("unable to parse new project.config", e); - } - } finally { - if (elapsedMicros < 0) { - elapsedMicros = (System.nanoTime() - readStart) / 1000; - metrics.readConfigTimer.record(elapsedMicros, TimeUnit.MICROSECONDS); - } else if (checkStart >= 0) { - long elapsedNanos = System.nanoTime() - checkStart; - metrics.checkConfigTimer.record(elapsedNanos, TimeUnit.NANOSECONDS); - } - if (trialConfig != null - && trialConfig.scannerConfig != null - && trialConfig.scannerConfig.hasErrors()) { - StringBuilder sb = new StringBuilder(); - sb.append("\nerror in "); - sb.append(pluginName); - sb.append(" plugin configuration"); - trialConfig.scannerConfig.appendMessages(sb); - throw new CommitValidationException(sb.toString()); + throw new CommitValidationException( + pluginName + "plugin unable to parse new project.config", e); } } } @@ -286,49 +231,49 @@ return; } // passed onCommitReceived so expect at worst only warnings here - long readStart = System.nanoTime(); - long checkStart = -1; - long elapsedMicros = -1; CheckConfig trialConfig = null; try { - trialConfig = readConfig(event.getChange().currentRevision); - elapsedMicros = (System.nanoTime() - readStart) / 1000; - metrics.readConfigTimer.record(elapsedMicros, TimeUnit.MICROSECONDS); - if (Objects.equals(trialConfig, checkConfig.scannerConfig)) { + try (Timer0.Context ctx = metrics.readConfigTimer.start()) { + trialConfig = readConfig(event.getChange().currentRevision); + } + if (Objects.equals( + trialConfig.scannerConfig, checkConfig == null ? null : checkConfig.scannerConfig)) { return; } - checkStart = System.nanoTime(); - if (CheckConfig.hasScanner(trialConfig) - && !CheckConfig.scannersEqual(trialConfig, checkConfig)) { - long maxElapsedSeconds = - gerritConfig == null - ? DEFAULT_MAX_ELAPSED_SECONDS - : gerritConfig.getLong( - ScannerConfig.KEY_TIME_TEST_MAX, DEFAULT_MAX_ELAPSED_SECONDS); - if (maxElapsedSeconds > 0) { - String commitMessage = event.getRevision().commitWithFooters; - ImmutableList<CopyrightReviewApi.CommitMessageFinding> findings = - trialConfig.checkCommitMessage(commitMessage); - ReviewResult result = - reviewApi.reportCommitMessageFindings( - pluginName, - allProjectsName.get(), - checkConfig == null ? null : checkConfig.scannerConfig, - trialConfig.scannerConfig, - event, - findings, - maxElapsedSeconds); - logReviewResultErrors(event, result); + try (Timer0.Context ctx = metrics.checkConfigTimer.start()) { + if (CheckConfig.hasScanner(trialConfig) + && !CheckConfig.scannersEqual(trialConfig, checkConfig)) { + long maxElapsedSeconds = + gerritConfig == null + ? DEFAULT_MAX_ELAPSED_SECONDS + : gerritConfig.getLong( + ScannerConfig.KEY_TIME_TEST_MAX, DEFAULT_MAX_ELAPSED_SECONDS); + if (maxElapsedSeconds > 0) { + String commitMessage = event.getRevision().commitWithFooters; + ImmutableList<CopyrightReviewApi.CommitMessageFinding> findings = + trialConfig.checkCommitMessage(commitMessage); + ReviewResult result = + reviewApi.reportCommitMessageFindings( + pluginName, + allProjectsName.get(), + checkConfig == null ? null : checkConfig.scannerConfig, + trialConfig.scannerConfig, + event, + findings, + maxElapsedSeconds); + logReviewResultErrors(event, result); + } } + boolean pluginEnabled = + gerritConfig != null && gerritConfig.getBoolean(ScannerConfig.KEY_ENABLE, false); + CheckConfig.checkProjectConfig(reviewApi, pluginEnabled, trialConfig); + return; } - boolean pluginEnabled = - gerritConfig != null && gerritConfig.getBoolean(ScannerConfig.KEY_ENABLE, false); - CheckConfig.checkProjectConfig(reviewApi, pluginEnabled, trialConfig); - return; } catch (RestApiException | ConfigInvalidException | IOException e) { - logger.atSevere().withCause(e).log("%s plugin unable to read new configuration", pluginName); - // throw IllegalStateException? RestApiException? + logger.atSevere().withCause(e).log("unable to read new configuration"); + metrics.configurationErrors.increment(project); + metrics.errors.increment(); return; } finally { if (trialConfig != null @@ -341,24 +286,17 @@ pluginName, project, ProjectConfig.PROJECT_CONFIG, - checkConfig.scannerConfig, + checkConfig == null ? null : checkConfig.scannerConfig, trialConfig.scannerConfig, event); logReviewResultErrors(event, result); } catch (RestApiException e) { - logger.atSevere().withCause(e).log( - "%s plugin unable to read new configuration", pluginName); - // throw IllegalStateException? RestApiException? + logger.atSevere().withCause(e).log("unable to report configuration findings"); + metrics.postReviewErrors.increment(project); + metrics.errors.increment(); return; } } - if (elapsedMicros < 0) { - elapsedMicros = (System.nanoTime() - readStart) / 1000; - metrics.readConfigTimer.record(elapsedMicros, TimeUnit.MICROSECONDS); - } else if (checkStart >= 0) { - long elapsedNanos = System.nanoTime() - checkStart; - metrics.checkConfigTimer.record(elapsedNanos, TimeUnit.NANOSECONDS); - } } } @@ -381,18 +319,21 @@ projectState = projectCache.checkedGet(Project.nameKey(project)); } catch (IOException e) { logger.atSevere().withCause(e).log("error getting project state of %s", project); - // throw IllegalStateException? RestApiException? + metrics.projectStateErrors.increment(project); + metrics.errors.increment(); return scannerConfig.defaultEnable; } if (projectState == null) { logger.atSevere().log("error getting project state of %s", project); - // throw IllegalStateException? RestApiException? + metrics.projectStateErrors.increment(project); + metrics.errors.increment(); return scannerConfig.defaultEnable; } ProjectConfig projectConfig = projectState.getConfig(); if (projectConfig == null) { logger.atWarning().log("error getting project config of %s", project); - // throw IllegalStateException? RestApiException? return? + metrics.projectConfigErrors.increment(project); + metrics.errors.increment(); return scannerConfig.defaultEnable; } PluginConfig pluginConfig = projectConfig.getPluginConfig(pluginName); @@ -403,6 +344,19 @@ return pluginConfig.getBoolean(ScannerConfig.KEY_ENABLE, scannerConfig.defaultEnable); } + private void checkForConfigErrors(CheckConfig trialConfig) throws CommitValidationException { + if (trialConfig != null + && trialConfig.scannerConfig != null + && trialConfig.scannerConfig.hasErrors()) { + StringBuilder sb = new StringBuilder(); + sb.append("\nerror in "); + sb.append(pluginName); + sb.append(" plugin configuration"); + trialConfig.scannerConfig.appendMessages(sb); + throw new CommitValidationException(sb.toString()); + } + } + /** * Loads and compiles configured patterns from {@code ref/meta/All-Projects/project.config} and * {@code gerrit.config}. @@ -411,6 +365,7 @@ * @return the new scanner configuration to check * @throws IOException if accessing the repository fails */ + @Nullable private CheckConfig readConfig(String projectConfigObjectId) throws IOException, ConfigInvalidException { CheckConfig checkConfig = null; @@ -425,7 +380,6 @@ } gerritConfig = pluginConfigFactory.getFromGerritConfig(pluginName, true); if (gerritConfig == null) { - // throw IllegalStateException? RestApiException? checkConfig.scannerConfig.messages.add( ScannerConfig.hintMessage( "missing [plugin \"" + pluginName + "\"] section in gerrit.config")); @@ -436,32 +390,31 @@ return checkConfig; } - /** Erases any prior configuration state. */ - private void clearConfig() { - checkConfig = null; - } - private void logReviewResultErrors(RevisionCreatedListener.Event event, ReviewResult result) { if (!Strings.isNullOrEmpty(result.error)) { logger.atSevere().log( - "%s plugin revision %s: error posting review: %s", - pluginName, event.getChange().currentRevision, result.error); + "revision %s: error posting review: %s", event.getChange().currentRevision, result.error); + metrics.postReviewErrors.increment(event.getChange().project); + metrics.errors.increment(); } - for (Map.Entry<String, AddReviewerResult> entry : result.reviewers.entrySet()) { - AddReviewerResult arr = entry.getValue(); - if (!Strings.isNullOrEmpty(arr.error)) { - logger.atSevere().log( - "%s plugin revision %s: error adding reviewer %s: %s", - pluginName, event.getChange().currentRevision, entry.getKey(), arr.error); + if (result.reviewers != null) { + for (Map.Entry<String, AddReviewerResult> entry : result.reviewers.entrySet()) { + AddReviewerResult arr = entry.getValue(); + if (!Strings.isNullOrEmpty(arr.error)) { + logger.atSevere().log( + "revision %s: error adding reviewer %s: %s", + event.getChange().currentRevision, entry.getKey(), arr.error); + metrics.addReviewerErrors.increment(event.getChange().project); + metrics.errors.increment(); + } } } } private String readFileContents(Repository repo, ObjectId objectId, String filename) throws IOException { - RevWalk rw = new RevWalk(repo); - RevTree tree = rw.parseTree(objectId); - try (TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(), filename, tree)) { + try (RevWalk rw = new RevWalk(repo); + TreeWalk tw = TreeWalk.forPath(rw.getObjectReader(), filename, rw.parseTree(objectId))) { ObjectLoader loader = repo.open(tw.getObjectId(0), Constants.OBJ_BLOB); return new String(loader.getCachedBytes(), UTF_8); }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/copyright/CopyrightReviewApi.java b/src/main/java/com/googlesource/gerrit/plugins/copyright/CopyrightReviewApi.java index 0c41d7d..de97d60 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/copyright/CopyrightReviewApi.java +++ b/src/main/java/com/googlesource/gerrit/plugins/copyright/CopyrightReviewApi.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -24,9 +24,10 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.base.Strings; -import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.gerrit.extensions.api.GerritApi; +import com.google.gerrit.extensions.api.changes.ChangeApi; import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput; import com.google.gerrit.extensions.api.changes.ReviewResult; @@ -34,31 +35,17 @@ import com.google.gerrit.extensions.client.ReviewerState; import com.google.gerrit.extensions.common.CommentInfo; import com.google.gerrit.extensions.events.RevisionCreatedListener; -import com.google.gerrit.extensions.restapi.IdString; -import com.google.gerrit.extensions.restapi.ResourceNotFoundException; import com.google.gerrit.extensions.restapi.RestApiException; -import com.google.gerrit.metrics.Counter0; -import com.google.gerrit.metrics.Counter1; -import com.google.gerrit.metrics.Description; -import com.google.gerrit.metrics.Description.Units; -import com.google.gerrit.metrics.Field; -import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.metrics.Timer0; import com.google.gerrit.metrics.Timer1; import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.client.PatchSet; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; -import com.google.gerrit.server.PatchSetUtil; import com.google.gerrit.server.PluginUser; -import com.google.gerrit.server.change.ChangeResource; -import com.google.gerrit.server.change.RevisionResource; import com.google.gerrit.server.git.validators.CommitValidationException; import com.google.gerrit.server.git.validators.CommitValidationMessage; -import com.google.gerrit.server.notedb.ChangeNotes; -import com.google.gerrit.server.restapi.change.ListChangeComments; -import com.google.gerrit.server.restapi.change.PostReview; +import com.google.gerrit.server.util.ManualRequestContext; +import com.google.gerrit.server.util.ThreadLocalRequestContext; import com.google.inject.Provider; import com.google.inject.Singleton; import com.googlesource.gerrit.plugins.copyright.lib.CopyrightScanner.Match; @@ -68,7 +55,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; @@ -82,61 +68,8 @@ private final Provider<PluginUser> pluginUserProvider; private final Provider<CurrentUser> userProvider; private final IdentifiedUser.GenericFactory identifiedUserFactory; - private final ChangeNotes.Factory changeNotesFactory; - private final ChangeResource.Factory changeResourceFactory; - private final PatchSetUtil psUtil; - private final PostReview postReview; - private final ListChangeComments listChangeComments; - - @Singleton - static class Metrics { - final Counter0 reviewCount; - final Counter0 commentCount; - final Timer0 reviewTimer; - final Counter1 reviewCountByProject; - final Counter1 commentCountByProject; - final Timer1 reviewTimerByProject; - - @Inject - Metrics(MetricMaker metricMaker) { - Field<String> project = Field.ofString("project", "project name"); - reviewCount = - metricMaker.newCounter( - "review_count", - new Description("Total number of posted reviews").setRate().setUnit("reviews")); - commentCount = - metricMaker.newCounter( - "comment_count", - new Description("Total number of posted review comments") - .setRate() - .setUnit("comments")); - reviewTimer = - metricMaker.newTimer( - "review_latency", - new Description("Time spent posting reviews to revisions") - .setCumulative() - .setUnit(Units.MICROSECONDS)); - reviewCountByProject = - metricMaker.newCounter( - "review_count_by_project", - new Description("Total number of posted reviews").setRate().setUnit("reviews"), - project); - commentCountByProject = - metricMaker.newCounter( - "comment_count_by_project", - new Description("Total number of posted review comments") - .setRate() - .setUnit("comments"), - project); - reviewTimerByProject = - metricMaker.newTimer( - "review_latency_by_project", - new Description("Time spent posting reviews to revisions") - .setCumulative() - .setUnit(Units.MICROSECONDS), - project); - } - } + private final ThreadLocalRequestContext requestContext; + private final GerritApi gApi; @Inject CopyrightReviewApi( @@ -144,20 +77,14 @@ Provider<PluginUser> pluginUserProvider, Provider<CurrentUser> userProvider, IdentifiedUser.GenericFactory identifiedUserFactory, - ChangeNotes.Factory changeNotesFactory, - ChangeResource.Factory changeResourceFactory, - PatchSetUtil psUtil, - PostReview postReview, - ListChangeComments listChangeComments) { + ThreadLocalRequestContext requestContext, + GerritApi gApi) { this.metrics = metrics; this.pluginUserProvider = pluginUserProvider; this.userProvider = userProvider; this.identifiedUserFactory = identifiedUserFactory; - this.changeNotesFactory = changeNotesFactory; - this.changeResourceFactory = changeResourceFactory; - this.psUtil = psUtil; - this.postReview = postReview; - this.listChangeComments = listChangeComments; + this.requestContext = requestContext; + this.gApi = gApi; } /** @@ -199,7 +126,6 @@ Preconditions.checkArgument(findings.size() != 1 || !findings.get(0).isValid()); Preconditions.checkArgument(maxElapsedSeconds > 0); - long startNanos = System.nanoTime(); metrics.reviewCount.increment(); metrics.reviewCountByProject.increment(project); @@ -207,41 +133,43 @@ oldConfig != null && oldConfig.fromAccountId > 0 ? oldConfig.fromAccountId : newConfig.fromAccountId; - ChangeResource change = getChange(event, fromAccountId); - Map<String, List<CommentInfo>> priorReviewComments = getComments(change); - if (priorReviewComments == null) { - priorReviewComments = ImmutableMap.of(); - } - HashSet<CommentInfo> priorComments = new HashSet<>(); - priorComments.addAll(priorReviewComments.get("/COMMIT_MSG")); - - ReviewInput ri = new ReviewInput(); - ri.message(getCommitMessageMessage(pluginName, findings, maxElapsedSeconds)); - ImmutableList<CommentInput> comments = - ImmutableList.copyOf( - getCommitMessageComments(pluginName, findings, maxElapsedSeconds).stream() - .filter(ci -> !priorComments.contains(ci)) - .toArray(i -> new CommentInput[i])); - if (!comments.isEmpty()) { - ri.comments = ImmutableMap.of("/COMMIT_MSG", comments); - } - String label = "Code-Review"; - if (!Strings.isNullOrEmpty(oldConfig.reviewLabel)) { - label = oldConfig.reviewLabel; - } else if (!Strings.isNullOrEmpty(newConfig.reviewLabel)) { - label = newConfig.reviewLabel; - } - int vote = (findings.size() != 1 || !findings.get(0).isValid()) ? -2 : 2; - if (vote == 2) { - long elapsedMicros = findings.get(0).elapsedMicros; - if (elapsedMicros > maxElapsedSeconds * 1000000) { - vote = -2; - } else if (elapsedMicros > 2000000) { - vote = 1; + try (ManualRequestContext ctx = getContext(fromAccountId)) { + ChangeApi cApi = gApi.changes().id(event.getChange().id); + Map<String, List<CommentInfo>> priorReviewComments = cApi.comments(); + if (priorReviewComments == null) { + priorReviewComments = ImmutableMap.of(); } + HashSet<CommentInfo> priorComments = new HashSet<>(); + priorComments.addAll(priorReviewComments.get("/COMMIT_MSG")); + + ReviewInput ri = new ReviewInput(); + ri.message(getCommitMessageMessage(pluginName, findings, maxElapsedSeconds)); + ImmutableList<CommentInput> comments = + ImmutableList.copyOf( + getCommitMessageComments(pluginName, findings, maxElapsedSeconds).stream() + .filter(ci -> !priorComments.contains(ci)) + .toArray(i -> new CommentInput[i])); + if (!comments.isEmpty()) { + ri.comments = ImmutableMap.of("/COMMIT_MSG", comments); + } + String label = "Code-Review"; + if (!Strings.isNullOrEmpty(oldConfig.reviewLabel)) { + label = oldConfig.reviewLabel; + } else if (!Strings.isNullOrEmpty(newConfig.reviewLabel)) { + label = newConfig.reviewLabel; + } + int vote = (findings.size() != 1 || !findings.get(0).isValid()) ? -2 : 2; + if (vote == 2) { + long elapsedMicros = findings.get(0).elapsedMicros; + if (elapsedMicros > maxElapsedSeconds * 1000000) { + vote = -2; + } else if (elapsedMicros > 2000000) { + vote = 1; + } + } + ri.label(label, vote); + return cApi.current().review(ri); } - ri.label(label, vote); - return review(change, ri); } /** @@ -308,22 +236,23 @@ Preconditions.checkNotNull(newConfig.messages); Preconditions.checkArgument(!newConfig.messages.isEmpty()); - long startNanos = System.nanoTime(); metrics.reviewCount.increment(); metrics.reviewCountByProject.increment(project); - try { - int fromAccountId = - oldConfig != null && oldConfig.fromAccountId > 0 - ? oldConfig.fromAccountId - : newConfig.fromAccountId; - ChangeResource change = getChange(event, fromAccountId); + int fromAccountId = + oldConfig != null && oldConfig.fromAccountId > 0 + ? oldConfig.fromAccountId + : newConfig.fromAccountId; + try (Timer0.Context t0 = metrics.reviewTimer.start(); + Timer1.Context t1 = metrics.reviewTimerByProject.start(project); + ManualRequestContext ctx = getContext(fromAccountId)) { + ChangeApi cApi = gApi.changes().id(event.getChange().id); StringBuilder message = new StringBuilder(); message.append(pluginName); message.append(" plugin issues parsing new configuration"); ReviewInput ri = new ReviewInput().message(message.toString()); - Map<String, List<CommentInfo>> priorComments = getComments(change); + Map<String, List<CommentInfo>> priorComments = cApi.comments(); if (priorComments == null) { priorComments = ImmutableMap.of(); } @@ -350,11 +279,7 @@ } metrics.commentCount.incrementBy((long) numComments); metrics.commentCountByProject.incrementBy(project, (long) numComments); - return review(change, ri); - } finally { - long elapsedMicros = (System.nanoTime() - startNanos) / 1000; - metrics.reviewTimer.record(elapsedMicros, TimeUnit.MICROSECONDS); - metrics.reviewTimerByProject.record(project, elapsedMicros, TimeUnit.MICROSECONDS); + return cApi.current().review(ri); } } @@ -373,11 +298,12 @@ RevisionCreatedListener.Event event, Map<String, ImmutableList<Match>> findings) throws RestApiException { - long startNanos = System.nanoTime(); metrics.reviewCount.increment(); metrics.reviewCountByProject.increment(project); - try { + try (Timer0.Context t0 = metrics.reviewTimer.start(); + Timer1.Context t1 = metrics.reviewTimerByProject.start(project); + ManualRequestContext ctx = getContext(scannerConfig.fromAccountId)) { boolean tpAllowed = scannerConfig.isThirdPartyAllowed(project); boolean reviewRequired = false; for (Map.Entry<String, ImmutableList<Match>> entry : findings.entrySet()) { @@ -395,7 +321,7 @@ break; } } - ChangeResource change = getChange(event, scannerConfig.fromAccountId); + ChangeApi cApi = gApi.changes().id(event.getChange().id); ReviewInput ri = new ReviewInput() .message("Copyright scan") @@ -411,7 +337,7 @@ } else { ri = ri.message("This change appears to comply with copyright requirements."); } - Map<String, List<CommentInfo>> priorComments = getComments(change); + Map<String, List<CommentInfo>> priorComments = cApi.comments(); if (priorComments == null) { priorComments = ImmutableMap.of(); } @@ -450,11 +376,7 @@ } metrics.commentCount.incrementBy((long) numCommentsAdded); metrics.commentCountByProject.incrementBy(project, (long) numCommentsAdded); - return review(change, ri); - } finally { - long elapsedMicros = (System.nanoTime() - startNanos) / 1000; - metrics.reviewTimer.record(elapsedMicros, TimeUnit.MICROSECONDS); - metrics.reviewTimerByProject.record(project, elapsedMicros, TimeUnit.MICROSECONDS); + return cApi.current().review(ri); } } @@ -631,26 +553,13 @@ return sb.toString(); } - /** - * Constructs a {@link com.google.gerrit.server.change.ChangeResource} from the notes log for the - * change onto which a new revision was pushed. - * - * @param event describes the newly pushed revision - * @param fromAccountId identifies the configured user to impersonate when sending review comments - * @throws RestApiException if an error occurs looking up the notes log for the change - */ - private ChangeResource getChange(RevisionCreatedListener.Event event, int fromAccountId) - throws RestApiException { - try { - CurrentUser fromUser = getSendingUser(fromAccountId); - ChangeNotes notes = changeNotesFactory.createChecked(Change.id(event.getChange()._number)); - return changeResourceFactory.create(notes, fromUser); - } catch (Exception e) { - Throwables.throwIfUnchecked(e); - throw e instanceof RestApiException - ? (RestApiException) e - : new RestApiException("Cannot load change", e); - } + private ManualRequestContext getContext(int fromAccountId) { + PluginUser pluginUser = pluginUserProvider.get(); + CurrentUser user = + fromAccountId <= 0 + ? userProvider.get() + : identifiedUserFactory.runAs(null, Account.id(fromAccountId), pluginUser); + return new ManualRequestContext(user, requestContext); } /** @@ -665,44 +574,6 @@ return ri; } - /** - * Retrieves all of the prior review comments already attached to {@code change}. - * - * @throws RestApiException if an error occurs retrieving the comments - */ - private Map<String, List<CommentInfo>> getComments(ChangeResource change) - throws RestApiException { - try { - return listChangeComments.apply(change); - } catch (Exception e) { - Throwables.throwIfUnchecked(e); - throw e instanceof RestApiException - ? (RestApiException) e - : new RestApiException("Cannot list comments", e); - } - } - - /** - * Adds the code review described by {@code ri} to the review thread of {@code change}. - * - * @throws RestApiException if an error occurs updating the review thread - */ - private ReviewResult review(ChangeResource change, ReviewInput ri) throws RestApiException { - try { - PatchSet ps = psUtil.current(change.getNotes()); - if (ps == null) { - throw new ResourceNotFoundException(IdString.fromDecoded("current")); - } - RevisionResource revision = RevisionResource.createNonCacheable(change, ps); - return postReview.apply(revision, ri).value(); - } catch (Exception e) { - Throwables.throwIfUnchecked(e); - throw e instanceof RestApiException - ? (RestApiException) e - : new RestApiException("Cannot post review", e); - } - } - /** Returns true if {@code priorComments} already includes a comment identical to {@code ci}. */ @VisibleForTesting boolean containsComment(Iterable<? extends Comment> priorComments, CommentInput ci) {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/copyright/CopyrightValidator.java b/src/main/java/com/googlesource/gerrit/plugins/copyright/CopyrightValidator.java index 0a77834..7ed50dd 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/copyright/CopyrightValidator.java +++ b/src/main/java/com/googlesource/gerrit/plugins/copyright/CopyrightValidator.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -26,12 +26,6 @@ import com.google.gerrit.extensions.events.RevisionCreatedListener; import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.extensions.restapi.RestApiException; -import com.google.gerrit.metrics.Counter0; -import com.google.gerrit.metrics.Counter1; -import com.google.gerrit.metrics.Description; -import com.google.gerrit.metrics.Description.Units; -import com.google.gerrit.metrics.Field; -import com.google.gerrit.metrics.MetricMaker; import com.google.gerrit.metrics.Timer0; import com.google.gerrit.metrics.Timer1; import com.google.gerrit.reviewdb.client.Project; @@ -40,14 +34,12 @@ import com.google.gerrit.server.config.PluginConfigFactory; import com.google.gerrit.server.git.GitRepositoryManager; import com.google.inject.AbstractModule; -import com.google.inject.Singleton; import com.googlesource.gerrit.plugins.copyright.lib.CopyrightScanner.Match; import com.googlesource.gerrit.plugins.copyright.lib.IndexedLineReader; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.TimeUnit; import javax.inject.Inject; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; @@ -81,79 +73,6 @@ }; } - @Singleton - private static class Metrics { - final Counter0 scanCount; - final Timer0 scanRevisionTimer; - final Timer0 scanFileTimer; - final Counter1 scanCountByProject; - final Timer1 scanRevisionTimerByProject; - final Timer1 scanFileTimerByProject; - final Counter1 scanCountByBranch; - final Timer1 scanRevisionTimerByBranch; - final Timer1 scanFileTimerByBranch; - - @Inject - Metrics(MetricMaker metricMaker) { - Field<String> project = Field.ofString("project", "project name"); - Field<String> branch = Field.ofString("branch", "branch name"); - scanCount = - metricMaker.newCounter( - "scan_count", - new Description("Total number of copyright scans").setRate().setUnit("scans")); - scanRevisionTimer = - metricMaker.newTimer( - "scan_revision_latency", - new Description("Time spent scanning entire revisions") - .setCumulative() - .setUnit(Units.MILLISECONDS)); - scanFileTimer = - metricMaker.newTimer( - "scan_file_latency", - new Description("Time spent scanning each file") - .setCumulative() - .setUnit(Units.MICROSECONDS)); - scanCountByProject = - metricMaker.newCounter( - "scan_count_by_project", - new Description("Total number of copyright scans").setRate().setUnit("scans"), - project); - scanRevisionTimerByProject = - metricMaker.newTimer( - "scan_revision_latency_by_project", - new Description("Time spent scanning entire revisions") - .setCumulative() - .setUnit(Units.MILLISECONDS), - project); - scanFileTimerByProject = - metricMaker.newTimer( - "scan_file_latency_by_project", - new Description("Time spent scanning each file") - .setCumulative() - .setUnit(Units.MICROSECONDS), - project); - scanCountByBranch = - metricMaker.newCounter( - "scan_count_by_branch", - new Description("Total number of copyright scans").setRate().setUnit("scans"), - branch); - scanRevisionTimerByBranch = - metricMaker.newTimer( - "scan_revision_latency_by_branch", - new Description("Time spent scanning entire revisions") - .setCumulative() - .setUnit(Units.MILLISECONDS), - branch); - scanFileTimerByBranch = - metricMaker.newTimer( - "scan_file_latency_by_branch", - new Description("Time spent scanning each file") - .setCumulative() - .setUnit(Units.MICROSECONDS), - branch); - } - } - @Inject CopyrightValidator( Metrics metrics, @@ -189,16 +108,18 @@ if (scannerConfig == null) { // error already reported during configuration load logger.atWarning().log( - "%s plugin enabled with no configuration -- not scanning revision %s", - pluginName, event.getChange().currentRevision); + "plugin enabled with no configuration -- not scanning revision %s", + event.getChange().currentRevision); + metrics.skippedReviewWarnings.increment(project); return; } if (scannerConfig.scanner == null) { if (scannerConfig.hasErrors()) { // error already reported during configuration load logger.atWarning().log( - "%s plugin enabled with errors in configuration -- not scanning revision %s", - pluginName, event.getChange().currentRevision); + "plugin enabled with errors in configuration -- not scanning revision %s", + event.getChange().currentRevision); + metrics.skippedReviewWarnings.increment(project); } // else plugin not enabled return; } @@ -210,7 +131,9 @@ scanRevision(project, branch, event); } catch (IOException | RestApiException e) { logger.atSevere().withCause(e).log( - "%s plugin cannot scan revision %s", pluginName, event.getChange().currentRevision); + "cannot scan revision %s", event.getChange().currentRevision); + metrics.scanErrors.increment(project); + metrics.errors.increment(); return; } } @@ -228,12 +151,13 @@ throws IOException, RestApiException { Map<String, ImmutableList<Match>> findings = new HashMap<>(); ArrayList<String> containedPaths = new ArrayList<>(); - long scanStart = System.nanoTime(); - metrics.scanCount.increment(); metrics.scanCountByProject.increment(project); metrics.scanCountByBranch.increment(branch); - try (Repository repo = repoManager.openRepository(Project.nameKey(project)); + try (Timer0.Context t0 = metrics.scanRevisionTimer.start(); + Timer1.Context t1project = metrics.scanRevisionTimerByProject.start(project); + Timer1.Context t1branch = metrics.scanRevisionTimerByBranch.start(branch); + Repository repo = repoManager.openRepository(Project.nameKey(project)); RevWalk revWalk = new RevWalk(repo); TreeWalk tw = new TreeWalk(revWalk.getObjectReader())) { RevCommit commit = repo.parseCommit(ObjectId.fromString(event.getRevision().commit.commit)); @@ -259,8 +183,10 @@ && !FileMode.REGULAR_FILE.equals(tw.getFileMode())) { continue; } - long fileStart = System.nanoTime(); - try (ObjectReader reader = tw.getObjectReader(); + try (Timer0.Context tf0 = metrics.scanFileTimer.start(); + Timer1.Context tf1project = metrics.scanFileTimerByProject.start(project); + Timer1.Context tf1branch = metrics.scanFileTimerByBranch.start(branch); + ObjectReader reader = tw.getObjectReader(); ObjectStream stream = reader.open(tw.getObjectId(0)).openStream()) { IndexedLineReader lineReader = new IndexedLineReader(fullPath, -1, stream); ImmutableList<Match> matches = @@ -268,31 +194,26 @@ if (!matches.isEmpty()) { findings.put(tw.getPathString(), matches); } - long fileTime = (System.nanoTime() - fileStart) / 1000; - metrics.scanFileTimer.record(fileTime, TimeUnit.MICROSECONDS); - metrics.scanFileTimerByProject.record(project, fileTime, TimeUnit.MICROSECONDS); - metrics.scanFileTimerByBranch.record(branch, fileTime, TimeUnit.MICROSECONDS); } } } - long scanTime = (System.nanoTime() - scanStart) / 1000000; - metrics.scanRevisionTimer.record(scanTime, TimeUnit.MILLISECONDS); - metrics.scanRevisionTimerByProject.record(project, scanTime, TimeUnit.MILLISECONDS); - metrics.scanRevisionTimerByBranch.record(branch, scanTime, TimeUnit.MILLISECONDS); ReviewResult result = reviewApi.reportScanFindings(project, scannerConfig, event, findings); if (result != null && result.error != null && !result.error.equals("")) { logger.atSevere().log( - "%s plugin revision %s: error posting review: %s", - pluginName, event.getChange().currentRevision, result.error); + "revision %s: error posting review: %s", event.getChange().currentRevision, result.error); + metrics.postReviewErrors.increment(project); + metrics.errors.increment(); } if (result != null && result.reviewers != null) { for (Map.Entry<String, AddReviewerResult> entry : result.reviewers.entrySet()) { AddReviewerResult arr = entry.getValue(); if (arr.error != null && !arr.error.equals("")) { logger.atSevere().log( - "%s plugin revision %s: error adding reviewer %s: %s", - pluginName, event.getChange().currentRevision, entry.getKey(), arr.error); + "revision %s: error adding reviewer %s: %s", + event.getChange().currentRevision, entry.getKey(), arr.error); + metrics.addReviewerErrors.increment(project); + metrics.errors.increment(); } } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/copyright/Metrics.java b/src/main/java/com/googlesource/gerrit/plugins/copyright/Metrics.java new file mode 100644 index 0000000..1e22006 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/copyright/Metrics.java
@@ -0,0 +1,216 @@ +// Copyright (C) 2019 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.googlesource.gerrit.plugins.copyright; + +import com.google.gerrit.metrics.Counter0; +import com.google.gerrit.metrics.Counter1; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Description.Units; +import com.google.gerrit.metrics.Field; +import com.google.gerrit.metrics.MetricMaker; +import com.google.gerrit.metrics.Timer0; +import com.google.gerrit.metrics.Timer1; +import com.google.inject.Singleton; +import javax.inject.Inject; + +@Singleton +class Metrics { + final Timer0 readConfigTimer; + final Timer0 checkConfigTimer; + final Timer0 testConfigTimer; + final Counter1<String> postReviewErrors; + final Counter1<String> addReviewerErrors; + final Counter1<String> projectStateErrors; + final Counter1<String> projectConfigErrors; + final Counter1<String> configurationErrors; + final Counter0 reviewCount; + final Counter0 commentCount; + final Timer0 reviewTimer; + final Counter1<String> reviewCountByProject; + final Counter1<String> commentCountByProject; + final Timer1<String> reviewTimerByProject; + final Timer0 scanRevisionTimer; + final Timer0 scanFileTimer; + final Counter1<String> scanCountByProject; + final Timer1<String> scanRevisionTimerByProject; + final Timer1<String> scanFileTimerByProject; + final Counter1<String> scanCountByBranch; + final Timer1<String> scanRevisionTimerByBranch; + final Timer1<String> scanFileTimerByBranch; + final Counter1<String> skippedReviewWarnings; + final Counter1<String> scanErrors; + final Counter0 errors; + + @Inject + Metrics(MetricMaker metricMaker) { + Field<String> project = Field.ofString("project", "project name"); + Field<String> branch = Field.ofString("branch", "branch name"); + + readConfigTimer = + metricMaker.newTimer( + "read_config_latency", + new Description("Time spent reading and parsing plugin configurations") + .setCumulative() + .setUnit(Units.MICROSECONDS)); + checkConfigTimer = + metricMaker.newTimer( + "check_config_latency", + new Description("Time spent testing proposed plugin configurations") + .setCumulative() + .setUnit(Units.MICROSECONDS)); + testConfigTimer = + metricMaker.newTimer( + "test_config_latency", + new Description("Time spent testing configurations against difficult file pattern") + .setCumulative() + .setUnit(Units.MICROSECONDS)); + postReviewErrors = + metricMaker.newCounter( + "post_review_error_count", + new Description("Number of failed attempts to post reviews") + .setRate() + .setUnit("errors"), + project); + addReviewerErrors = + metricMaker.newCounter( + "add_reviewer_error_count", + new Description("Number of failed attempts to add a reviewer") + .setRate() + .setUnit("errors"), + project); + projectStateErrors = + metricMaker.newCounter( + "read_project_state_error_count", + new Description("Number of failed attempts to read project state") + .setRate() + .setUnit("errors"), + project); + projectConfigErrors = + metricMaker.newCounter( + "get_project_config_error_count", + new Description("Number of failed attempts to get config from project state") + .setRate() + .setUnit("errors"), + project); + configurationErrors = + metricMaker.newCounter( + "read_configuration_error_count", + new Description("Number of failed attempts to read configuration") + .setRate() + .setUnit("errors"), + project); + reviewCount = + metricMaker.newCounter( + "review_count", + new Description("Total number of posted reviews").setRate().setUnit("reviews")); + commentCount = + metricMaker.newCounter( + "comment_count", + new Description("Total number of posted review comments") + .setRate() + .setUnit("comments")); + reviewTimer = + metricMaker.newTimer( + "review_latency", + new Description("Time spent posting reviews to revisions") + .setCumulative() + .setUnit(Units.MICROSECONDS)); + reviewCountByProject = + metricMaker.newCounter( + "review_count_by_project", + new Description("Total number of posted reviews").setRate().setUnit("reviews"), + project); + commentCountByProject = + metricMaker.newCounter( + "comment_count_by_project", + new Description("Total number of posted review comments").setRate().setUnit("comments"), + project); + reviewTimerByProject = + metricMaker.newTimer( + "review_latency_by_project", + new Description("Time spent posting reviews to revisions") + .setCumulative() + .setUnit(Units.MICROSECONDS), + project); + scanRevisionTimer = + metricMaker.newTimer( + "scan_revision_latency", + new Description("Time spent scanning entire revisions") + .setCumulative() + .setUnit(Units.MILLISECONDS)); + scanFileTimer = + metricMaker.newTimer( + "scan_file_latency", + new Description("Time spent scanning each file") + .setCumulative() + .setUnit(Units.MICROSECONDS)); + scanCountByProject = + metricMaker.newCounter( + "scan_count_by_project", + new Description("Total number of copyright scans").setRate().setUnit("scans"), + project); + scanRevisionTimerByProject = + metricMaker.newTimer( + "scan_revision_latency_by_project", + new Description("Time spent scanning entire revisions") + .setCumulative() + .setUnit(Units.MILLISECONDS), + project); + scanFileTimerByProject = + metricMaker.newTimer( + "scan_file_latency_by_project", + new Description("Time spent scanning each file") + .setCumulative() + .setUnit(Units.MICROSECONDS), + project); + scanCountByBranch = + metricMaker.newCounter( + "scan_count_by_branch", + new Description("Total number of copyright scans").setRate().setUnit("scans"), + branch); + scanRevisionTimerByBranch = + metricMaker.newTimer( + "scan_revision_latency_by_branch", + new Description("Time spent scanning entire revisions") + .setCumulative() + .setUnit(Units.MILLISECONDS), + branch); + scanFileTimerByBranch = + metricMaker.newTimer( + "scan_file_latency_by_branch", + new Description("Time spent scanning each file") + .setCumulative() + .setUnit(Units.MICROSECONDS), + branch); + skippedReviewWarnings = + metricMaker.newCounter( + "skipped_scan_warning_count", + new Description("Number revision scans skipped due to configuration problems") + .setRate() + .setUnit("warnings"), + project); + scanErrors = + metricMaker.newCounter( + "failed_scan_error_count", + new Description("Number of failed attempts to scan revisions") + .setRate() + .setUnit("errors"), + project); + errors = + metricMaker.newCounter( + "error_count", + new Description("Number of errors of any kind").setRate().setUnit("errors")); + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/copyright/Module.java b/src/main/java/com/googlesource/gerrit/plugins/copyright/Module.java index af872c7..7f7bd82 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/copyright/Module.java +++ b/src/main/java/com/googlesource/gerrit/plugins/copyright/Module.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/copyright/ScannerConfig.java b/src/main/java/com/googlesource/gerrit/plugins/copyright/ScannerConfig.java index 815b28c..c53ee1f 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/copyright/ScannerConfig.java +++ b/src/main/java/com/googlesource/gerrit/plugins/copyright/ScannerConfig.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -33,7 +33,7 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -/** Configuration state for {@link CopyrightValidator}. */ +/** Configuration state for {@link CopyrightValidator} from All-Projects project.config file. */ class ScannerConfig { private static final FluentLogger logger = FluentLogger.forEnclosingClass(); @@ -88,6 +88,7 @@ this.fromAccountId = 0; } + /** True when {@link CopyrightValidator} will behave the same with either config. */ @Override public boolean equals(Object other) { if (this == other) { @@ -99,6 +100,7 @@ if (other instanceof ScannerConfig) { ScannerConfig otherConfig = (ScannerConfig) other; return defaultEnable == otherConfig.defaultEnable + && fromAccountId == otherConfig.fromAccountId && messages.equals(otherConfig.messages) && alwaysReviewPath.equals(otherConfig.alwaysReviewPath) && matchProjects.equals(otherConfig.matchProjects) @@ -116,6 +118,7 @@ public int hashCode() { return Objects.hash( defaultEnable, + fromAccountId, messages, alwaysReviewPath, matchProjects,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/Archive.java b/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/Archive.java index 8d6ed0b..8d8fab7 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/Archive.java +++ b/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/Archive.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightPatterns.java b/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightPatterns.java index 060dffc..d2bf243 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightPatterns.java +++ b/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightPatterns.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightScanner.java b/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightScanner.java index ddc5f78..736a537 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightScanner.java +++ b/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightScanner.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/IndexedLineReader.java b/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/IndexedLineReader.java index 015863c..f4749fc 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/IndexedLineReader.java +++ b/src/main/java/com/googlesource/gerrit/plugins/copyright/lib/IndexedLineReader.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/copyright/tools/AndroidScan.java b/src/main/java/com/googlesource/gerrit/plugins/copyright/tools/AndroidScan.java index b5591a4..37fed5e 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/copyright/tools/AndroidScan.java +++ b/src/main/java/com/googlesource/gerrit/plugins/copyright/tools/AndroidScan.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/src/main/java/com/googlesource/gerrit/plugins/copyright/tools/ScanTool.java b/src/main/java/com/googlesource/gerrit/plugins/copyright/tools/ScanTool.java index 0a4bb04..087f2c3 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/copyright/tools/ScanTool.java +++ b/src/main/java/com/googlesource/gerrit/plugins/copyright/tools/ScanTool.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md index e700891..a165149 100644 --- a/src/main/resources/Documentation/about.md +++ b/src/main/resources/Documentation/about.md
@@ -14,6 +14,7 @@ It will have false-positives--especially in the unknown category. It may also have false negatives. + ## First-party Licensed or owned by the owner of the gerrit repository.
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md index 81eec27..b682c71 100644 --- a/src/main/resources/Documentation/build.md +++ b/src/main/resources/Documentation/build.md
@@ -84,6 +84,7 @@ bazel test --test_tag_filters=@PLUGIN@ //... ```` + This project can be imported into the Eclipse IDE. Add the plugin name to the `CUSTOM_PLUGINS` set in Gerrit core in `tools/bzl/plugins.bzl`, and execute:
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md index 32b4ef9..5163a2d 100644 --- a/src/main/resources/Documentation/config.md +++ b/src/main/resources/Documentation/config.md
@@ -43,6 +43,7 @@ the command-line tool, to take heed of any warnings or errors it prints, and to copy+paste the token. + ## Project Configuration Each project's `project.config` has a configuration parameter to enable or to @@ -216,7 +217,8 @@ Review comments will appear to come from the given account. Recommend creating a non-interactive user with a descriptive name like - "Copyright Scanner". + "Copyright Scanner". Must have read access to refs/heads/* and to + refs/meta/config in all repositories. plugin.@PLUGIN@.matchProjects : List of Project Patterns to Scan
diff --git a/src/main/resources/Documentation/known-patterns.md b/src/main/resources/Documentation/known-patterns.md index fae7a2d..9499a1e 100644 --- a/src/main/resources/Documentation/known-patterns.md +++ b/src/main/resources/Documentation/known-patterns.md
@@ -15,3 +15,4 @@ The names do not have to match an exact license or entity per se. For example, the Creative Commons folks do not define a CC_BY_C license. The named pattern matches any of several Creative Commons licenses that allow commercial use. +
diff --git a/src/main/resources/Documentation/modified-regex.md b/src/main/resources/Documentation/modified-regex.md index 9e1bd9e..8abc2df 100644 --- a/src/main/resources/Documentation/modified-regex.md +++ b/src/main/resources/Documentation/modified-regex.md
@@ -10,6 +10,7 @@ expressions](https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html) with modifications to assist the 3 requirements above. + ## Unlimited Wildcards If you run the ScanTool.java command-line tool against the files in your
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/CopyrightConfigIT.java b/src/test/java/com/googlesource/gerrit/plugins/copyright/CopyrightConfigIT.java index 7a31f07..af7278a 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/CopyrightConfigIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/CopyrightConfigIT.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -25,6 +25,7 @@ import static com.googlesource.gerrit.plugins.copyright.TestConfig.LOCAL_BRANCH; import com.google.common.collect.ImmutableList; +import com.google.common.flogger.FluentLogger; import com.google.common.truth.Correspondence; import com.google.gerrit.acceptance.GerritConfig; import com.google.gerrit.acceptance.GerritConfigs; @@ -47,6 +48,8 @@ @TestPlugin(name = "copyright", sysModule = "com.googlesource.gerrit.plugins.copyright.Module") public class CopyrightConfigIT extends LightweightPluginDaemonTest { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + @Test @GerritConfigs({ @GerritConfig(name = "plugin.copyright.enable", value = "true"), @@ -99,7 +102,9 @@ }) public void testCopyrightConfig_pushWithSenderNoName() throws Exception { TestAccount anonymous = - accountCreator.create("copyright-scan", "", "", "Non-Interactive Users", expertGroup.name); + accountCreator.create( + "copyright-scan", "", "", "Administrators", "Non-Interactive Users", expertGroup.name); + testConfig.updatePlugin( cfg -> { cfg.setInt(KEY_FROM, anonymous.id().get()); @@ -317,6 +322,7 @@ "copyright-scanner", "copyright-scanner@example.com", "Copyright Scanner", + "Administrators", "Non-Interactive Users", expertGroup.name); reviewer = @@ -360,16 +366,9 @@ } private static Correspondence<Comment, String> warningContains() { - return new Correspondence<Comment, String>() { - @Override - public boolean compare(Comment actual, String expected) { - return actual.message.startsWith("WARNING ") && actual.message.contains(expected); - } - - @Override - public String toString() { - return "matches regex"; - } - }; + return Correspondence.from( + (actual, expected) -> + actual.message.startsWith("WARNING ") && actual.message.contains(expected), + "matches regex"); } }
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/CopyrightReviewApiTest.java b/src/test/java/com/googlesource/gerrit/plugins/copyright/CopyrightReviewApiTest.java index f71ea0f..efea843 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/CopyrightReviewApiTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/CopyrightReviewApiTest.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -39,6 +39,7 @@ import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.PluginUser; import com.google.gerrit.server.account.GroupMembership; +import com.google.gerrit.server.util.ThreadLocalRequestContext; import com.googlesource.gerrit.plugins.copyright.lib.CopyrightScanner.Match; import org.junit.Before; import org.junit.Test; @@ -53,21 +54,15 @@ private IdentifiedUser.GenericFactory identifiedUserFactory = createMock(IdentifiedUser.GenericFactory.class); + private ThreadLocalRequestContext requestContext = createMock(ThreadLocalRequestContext.class); + private CopyrightReviewApi reviewApi; @Before public void setUp() throws Exception { reviewApi = new CopyrightReviewApi( - null, - () -> pluginUser, - () -> currentUser, - identifiedUserFactory, - null, - null, - null, - null, - null); + null, () -> pluginUser, () -> currentUser, identifiedUserFactory, requestContext, null); } @Test @@ -90,7 +85,7 @@ CurrentUser from = reviewApi.getSendingUser(0); verify(identifiedUserFactory); - assertThat(from).isSameAs(currentUser); + assertThat(from).isSameInstanceAs(currentUser); } @Test @@ -353,33 +348,18 @@ } private static Correspondence<CommentInput, CommentInput> startsWithAndRangesMatch() { - return new Correspondence<CommentInput, CommentInput>() { - @Override - public boolean compare(CommentInput actual, CommentInput expected) { - return actual.range.startLine == expected.range.startLine - && actual.range.endLine == expected.range.endLine - && actual.message.startsWith(expected.message); - } - - @Override - public String toString() { - return "starts with and ranges match"; - } - }; + return Correspondence.from( + (actual, expected) -> + actual.range.startLine == expected.range.startLine + && actual.range.endLine == expected.range.endLine + && actual.message.startsWith(expected.message), + "starts with and ranges match"); } private static Correspondence<AddReviewerInput, String> addressedTo() { - return new Correspondence<AddReviewerInput, String>() { - @Override - public boolean compare(AddReviewerInput actual, String expected) { - return expected.equals(actual.state().toString() + ":" + actual.reviewer); - } - - @Override - public String toString() { - return "addressed to"; - } - }; + return Correspondence.from( + (actual, expected) -> expected.equals(actual.state().toString() + ":" + actual.reviewer), + "addressed to"); } /** Comment input {@code text} from {@code start} line to {@code end} line. */
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/CopyrightValidatorIT.java b/src/test/java/com/googlesource/gerrit/plugins/copyright/CopyrightValidatorIT.java index 304e87f..a629e9f 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/CopyrightValidatorIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/CopyrightValidatorIT.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -434,17 +434,10 @@ } private static Correspondence<Comment, CommentMatch> commentContains() { - return new Correspondence<Comment, CommentMatch>() { - @Override - public boolean compare(Comment actual, CommentMatch expected) { - return actual.unresolved != expected.resolved && actual.message.contains(expected.content); - } - - @Override - public String toString() { - return "comment resolution status and content matches"; - } - }; + return Correspondence.from( + (actual, expected) -> + actual.unresolved != expected.resolved && actual.message.contains(expected.content), + "comment resolution status and content matches"); } private static class CommentMatch {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/TestConfig.java b/src/test/java/com/googlesource/gerrit/plugins/copyright/TestConfig.java index f35cbc0..346c53b 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/TestConfig.java +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/TestConfig.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/ArchiveTest.java b/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/ArchiveTest.java index c5aeb8d..87e4023 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/ArchiveTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/ArchiveTest.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightPatternsTest.java b/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightPatternsTest.java index a7bd486..394eb76 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightPatternsTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightPatternsTest.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightScannerTest.java b/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightScannerTest.java index 3aa379b..6966975 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightScannerTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/CopyrightScannerTest.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -36,17 +36,7 @@ public class CopyrightScannerTest { private static final Correspondence<String, String> CONTAINS_STRING = - new Correspondence<String, String>() { - @Override - public boolean compare(String actual, String expected) { - return actual.contains(expected); - } - - @Override - public String toString() { - return "contains"; - } - }; + Correspondence.from((actual, expected) -> actual.contains(expected), "contains"); private CopyrightPatterns.RuleSet.Builder builder;
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/IndexedLineReaderTest.java b/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/IndexedLineReaderTest.java index a337027..cdbe089 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/IndexedLineReaderTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/lib/IndexedLineReaderTest.java
@@ -4,7 +4,7 @@ // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/ANDROID.txt b/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/ANDROID.txt index 71a183b..8ae0bfd 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/ANDROID.txt +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/ANDROID.txt
@@ -1,3 +1,3 @@ /* - * Copyright (C) 2019 Android Open-Source Project + * Copyright (C) 2019 Android Open-Source Project */
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/APACHE2.txt b/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/APACHE2.txt index d645695..cb12cf1 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/APACHE2.txt +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/APACHE2.txt
@@ -176,6 +176,7 @@ END OF TERMS AND CONDITIONS + APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/BSD2.txt b/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/BSD2.txt index 453967f..5b4d707 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/BSD2.txt +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/BSD2.txt
@@ -4,6 +4,7 @@ Copyright (c) 2019, Jane Doe <jdoe@example.com> All rights reserved. + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/MIT.txt b/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/MIT.txt index af63709..2a1d858 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/MIT.txt +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/MIT.txt
@@ -12,6 +12,7 @@ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/UNKNOWN.txt b/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/UNKNOWN.txt index 67a5ebe..87124eb 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/UNKNOWN.txt +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/testdata/licenses/UNKNOWN.txt
@@ -1,4 +1,4 @@ #!/bin/sh # Copyright (C) 2019 -# Jane Doe <jdoe@example.com> +# Jane Doe <jdoe@example.com> exit 0
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/tools/AndroidScanTest.sh b/src/test/java/com/googlesource/gerrit/plugins/copyright/tools/AndroidScanTest.sh index cb3a3eb..0e8e5d0 100755 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/tools/AndroidScanTest.sh +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/tools/AndroidScanTest.sh
@@ -6,7 +6,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/src/test/java/com/googlesource/gerrit/plugins/copyright/tools/ScanToolTest.sh b/src/test/java/com/googlesource/gerrit/plugins/copyright/tools/ScanToolTest.sh index 89ab77e..1db9039 100755 --- a/src/test/java/com/googlesource/gerrit/plugins/copyright/tools/ScanToolTest.sh +++ b/src/test/java/com/googlesource/gerrit/plugins/copyright/tools/ScanToolTest.sh
@@ -6,7 +6,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/tools/android_scan.sh b/tools/android_scan.sh index a3a51b2..1771f97 100755 --- a/tools/android_scan.sh +++ b/tools/android_scan.sh
@@ -6,7 +6,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS,
diff --git a/tools/check_new_config.sh b/tools/check_new_config.sh index 01cb7a4..29a7b27 100755 --- a/tools/check_new_config.sh +++ b/tools/check_new_config.sh
@@ -6,7 +6,7 @@ # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # -# http://www.apache.org/licenses/LICENSE-2.0 +# http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, @@ -34,7 +34,7 @@ echo else echo "The project.config full path must end with project.config -- not $(basename $2)" - ehco + echo fi fi }