Merge "Add backend support for re-running checks."
diff --git a/java/com/google/gerrit/plugins/checks/api/ApiModule.java b/java/com/google/gerrit/plugins/checks/api/ApiModule.java
index ad1e7e3..88fb5cf 100644
--- a/java/com/google/gerrit/plugins/checks/api/ApiModule.java
+++ b/java/com/google/gerrit/plugins/checks/api/ApiModule.java
@@ -50,7 +50,7 @@
postOnCollection(CHECK_KIND).to(PostCheck.class);
get(CHECK_KIND).to(GetCheck.class);
post(CHECK_KIND).to(UpdateCheck.class);
-
+ post(CHECK_KIND, "rerun").to(RerunCheck.class);
DynamicMap.mapOf(binder(), PENDING_CHECK_KIND);
}
});
diff --git a/java/com/google/gerrit/plugins/checks/api/CheckApi.java b/java/com/google/gerrit/plugins/checks/api/CheckApi.java
index d137645..14acb41 100644
--- a/java/com/google/gerrit/plugins/checks/api/CheckApi.java
+++ b/java/com/google/gerrit/plugins/checks/api/CheckApi.java
@@ -26,6 +26,8 @@
/** Updates a check and returns the {@link CheckInfo} for the updated resource. */
CheckInfo update(CheckInput input) throws RestApiException;
+ /** Reruns the check and returns the {@link CheckInfo} for the updated check. */
+ CheckInfo rerun() throws RestApiException;
/**
* A default implementation which allows source compatibility when adding new methods to the
* interface.
@@ -40,5 +42,10 @@
public CheckInfo update(CheckInput input) throws RestApiException {
throw new NotImplementedException();
}
+
+ @Override
+ public CheckInfo rerun() throws RestApiException {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/java/com/google/gerrit/plugins/checks/api/CheckApiImpl.java b/java/com/google/gerrit/plugins/checks/api/CheckApiImpl.java
index 5cd20f6..3be0a74 100644
--- a/java/com/google/gerrit/plugins/checks/api/CheckApiImpl.java
+++ b/java/com/google/gerrit/plugins/checks/api/CheckApiImpl.java
@@ -30,12 +30,18 @@
private final GetCheck getCheck;
private final UpdateCheck updateCheck;
private final CheckResource checkResource;
+ private final RerunCheck rerunCheck;
@Inject
- CheckApiImpl(GetCheck getCheck, UpdateCheck updateCheck, @Assisted CheckResource checkResource) {
+ CheckApiImpl(
+ GetCheck getCheck,
+ UpdateCheck updateCheck,
+ @Assisted CheckResource checkResource,
+ RerunCheck rerunCheck) {
this.getCheck = getCheck;
this.updateCheck = updateCheck;
this.checkResource = checkResource;
+ this.rerunCheck = rerunCheck;
}
@Override
@@ -56,4 +62,13 @@
throw asRestApiException("Cannot update check", e);
}
}
+
+ @Override
+ public CheckInfo rerun() throws RestApiException {
+ try {
+ return rerunCheck.apply(checkResource, null).value();
+ } catch (Exception e) {
+ throw asRestApiException("Cannot rerun check", e);
+ }
+ }
}
diff --git a/java/com/google/gerrit/plugins/checks/api/RerunCheck.java b/java/com/google/gerrit/plugins/checks/api/RerunCheck.java
new file mode 100644
index 0000000..4c6b0a4
--- /dev/null
+++ b/java/com/google/gerrit/plugins/checks/api/RerunCheck.java
@@ -0,0 +1,119 @@
+// 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.google.gerrit.plugins.checks.api;
+
+import com.google.gerrit.extensions.common.Input;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.plugins.checks.AdministrateCheckersPermission;
+import com.google.gerrit.plugins.checks.Check;
+import com.google.gerrit.plugins.checks.CheckJson;
+import com.google.gerrit.plugins.checks.CheckKey;
+import com.google.gerrit.plugins.checks.CheckUpdate;
+import com.google.gerrit.plugins.checks.Checker;
+import com.google.gerrit.plugins.checks.CheckerUuid;
+import com.google.gerrit.plugins.checks.Checkers;
+import com.google.gerrit.plugins.checks.Checks;
+import com.google.gerrit.plugins.checks.Checks.GetCheckOptions;
+import com.google.gerrit.plugins.checks.ChecksUpdate;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.UserInitiated;
+import com.google.gerrit.server.permissions.PermissionBackend;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.util.Optional;
+import javax.inject.Provider;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+
+@Singleton
+public class RerunCheck implements RestModifyView<CheckResource, Input> {
+ private final Provider<CurrentUser> self;
+ private final PermissionBackend permissionBackend;
+ private final AdministrateCheckersPermission permission;
+ private final Checks checks;
+ private final Provider<ChecksUpdate> checksUpdate;
+ private final CheckJson.Factory checkJsonFactory;
+ private final Checkers checkers;
+
+ @Inject
+ RerunCheck(
+ Provider<CurrentUser> self,
+ PermissionBackend permissionBackend,
+ AdministrateCheckersPermission permission,
+ Checks checks,
+ @UserInitiated Provider<ChecksUpdate> checksUpdate,
+ CheckJson.Factory checkJsonFactory,
+ Checkers checkers) {
+ this.self = self;
+ this.permissionBackend = permissionBackend;
+ this.permission = permission;
+ this.checks = checks;
+ this.checksUpdate = checksUpdate;
+ this.checkJsonFactory = checkJsonFactory;
+ this.checkers = checkers;
+ }
+
+ @Override
+ public Response<CheckInfo> apply(CheckResource checkResource, Input input)
+ throws RestApiException, IOException, PermissionBackendException, ConfigInvalidException {
+ if (!self.get().isIdentifiedUser()) {
+ throw new AuthException("Authentication required");
+ }
+ permissionBackend.currentUser().check(permission);
+ if (checkResource.getRevisionResource().getEdit().isPresent()) {
+ throw new ResourceConflictException("checks are not supported on a change edit");
+ }
+ CheckKey key =
+ CheckKey.create(
+ checkResource.getRevisionResource().getProject(),
+ checkResource.getRevisionResource().getPatchSet().id(),
+ checkResource.getCheckerUuid());
+ Optional<Check> check = checks.getCheck(key, GetCheckOptions.defaults());
+ CheckerUuid checkerUuid = checkResource.getCheckerUuid();
+ Check updatedCheck;
+ if (!check.isPresent()) {
+ Checker checker =
+ checkers
+ .getChecker(checkerUuid)
+ .orElseThrow(
+ () ->
+ new ResourceNotFoundException(
+ String.format("checker %s not found", checkerUuid)));
+ // This error should not be thrown since this case is filtered before reaching this code.
+ // Also return a backfilled check for checkers that do not apply to the change.
+ updatedCheck =
+ Check.newBackfilledCheck(
+ checkResource.getRevisionResource().getProject(),
+ checkResource.getRevisionResource().getPatchSet(),
+ checker);
+ } else {
+ CheckUpdate.Builder builder = CheckUpdate.builder();
+ builder
+ .setState(CheckState.NOT_STARTED)
+ .unsetFinished()
+ .unsetStarted()
+ .setMessage("")
+ .setUrl("");
+ updatedCheck = checksUpdate.get().updateCheck(key, builder.build());
+ }
+ return Response.ok(checkJsonFactory.noOptions().format(updatedCheck));
+ }
+}
diff --git a/javatests/com/google/gerrit/plugins/checks/acceptance/ChecksRestApiBindingsIT.java b/javatests/com/google/gerrit/plugins/checks/acceptance/ChecksRestApiBindingsIT.java
index 557aab0..03a9e7d 100644
--- a/javatests/com/google/gerrit/plugins/checks/acceptance/ChecksRestApiBindingsIT.java
+++ b/javatests/com/google/gerrit/plugins/checks/acceptance/ChecksRestApiBindingsIT.java
@@ -47,7 +47,8 @@
private static final ImmutableList<RestCall> SCOPED_CHECK_ENDPOINTS =
ImmutableList.of(
RestCall.get("/changes/%s/revisions/%s/checks~checks/%s"),
- RestCall.post("/changes/%s/revisions/%s/checks~checks/%s"));
+ RestCall.post("/changes/%s/revisions/%s/checks~checks/%s"),
+ RestCall.post("/changes/%s/revisions/%s/checks~checks/%s/rerun"));
@Test
public void rootEndpoints() throws Exception {
diff --git a/javatests/com/google/gerrit/plugins/checks/acceptance/api/RerunCheckIT.java b/javatests/com/google/gerrit/plugins/checks/acceptance/api/RerunCheckIT.java
new file mode 100644
index 0000000..9cc539f
--- /dev/null
+++ b/javatests/com/google/gerrit/plugins/checks/acceptance/api/RerunCheckIT.java
@@ -0,0 +1,150 @@
+// 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.google.gerrit.plugins.checks.acceptance.api;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.plugins.checks.CheckKey;
+import com.google.gerrit.plugins.checks.CheckerUuid;
+import com.google.gerrit.plugins.checks.acceptance.AbstractCheckersTest;
+import com.google.gerrit.plugins.checks.api.CheckInfo;
+import com.google.gerrit.plugins.checks.api.CheckState;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.gerrit.testing.TestTimeUtil;
+import com.google.inject.Inject;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.util.concurrent.TimeUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class RerunCheckIT extends AbstractCheckersTest {
+ @Inject private RequestScopeOperations requestScopeOperations;
+ @Inject private ProjectOperations projectOperations;
+
+ private PatchSet.Id patchSetId;
+ private CheckKey checkKey;
+
+ @Before
+ public void setUp() throws Exception {
+ TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS);
+ TestTimeUtil.setClock(Timestamp.from(Instant.EPOCH));
+
+ patchSetId = createChange().getPatchSetId();
+
+ CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create();
+ checkKey = CheckKey.create(project, patchSetId, checkerUuid);
+ }
+
+ @After
+ public void resetTime() {
+ TestTimeUtil.useSystemTime();
+ }
+
+ @Test
+ public void rerunResetsCheckInfo() throws Exception {
+ checkOperations
+ .newCheck(checkKey)
+ .state(CheckState.FAILED)
+ .started(TimeUtil.nowTs())
+ .finished(TimeUtil.nowTs())
+ .message("message")
+ .url("url.com")
+ .upsert();
+ Timestamp created = checkOperations.check(checkKey).get().created();
+ Timestamp updated = checkOperations.check(checkKey).get().updated();
+ CheckInfo info = checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun();
+ assertThat(info.state).isEqualTo(CheckState.NOT_STARTED);
+ assertThat(info.message).isEqualTo(null);
+ assertThat(info.url).isEqualTo(null);
+ assertThat(info.started).isEqualTo(null);
+ assertThat(info.finished).isEqualTo(null);
+ assertThat(info.created).isEqualTo(created);
+ assertThat(info.updated).isGreaterThan(updated);
+ }
+
+ @Test
+ public void rerunNotStartedCheck() throws Exception {
+ checkOperations.newCheck(checkKey).state(CheckState.NOT_STARTED).upsert();
+ CheckInfo info = checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun();
+ assertThat(info.state).isEqualTo(CheckState.NOT_STARTED);
+ }
+
+ @Test
+ public void rerunFinishedCheck() throws Exception {
+ checkOperations.newCheck(checkKey).state(CheckState.SUCCESSFUL).upsert();
+ CheckInfo info = checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun();
+ assertThat(info.state).isEqualTo(CheckState.NOT_STARTED);
+ assertThat(info.updated).isGreaterThan(info.created);
+ }
+
+ @Test
+ public void rerunCheckNotExistingButBackfilled() throws Exception {
+ CheckInfo info = checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun();
+ assertThat(info.state).isEqualTo(CheckState.NOT_STARTED);
+ assertThat(checkOperations.check(checkKey).exists()).isFalse();
+ }
+
+ @Test
+ public void rerunExistingCheckWithCheckerNotAppliedToChange() throws Exception {
+ Project.NameKey otherProject = projectOperations.newProject().create();
+ checkerOperations.checker(checkKey.checkerUuid()).forUpdate().repository(otherProject).update();
+ checkOperations.newCheck(checkKey).upsert();
+ CheckInfo info = checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun();
+ assertThat(info.state).isEqualTo(CheckState.NOT_STARTED);
+ }
+
+ @Test
+ public void rerunNonExistingCheckWithCheckerNotAppliedToChange() throws Exception {
+ Project.NameKey otherProject = projectOperations.newProject().create();
+ checkerOperations.checker(checkKey.checkerUuid()).forUpdate().repository(otherProject).update();
+ assertThrows(
+ ResourceNotFoundException.class,
+ () -> checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun());
+ assertThat(checkOperations.check(checkKey).exists()).isFalse();
+ }
+
+ @Test
+ public void cannotUpdateCheckWithoutAdministrateCheckers() throws Exception {
+ requestScopeOperations.setApiUser(user.id());
+ checkOperations.newCheck(checkKey).state(CheckState.SUCCESSFUL).upsert();
+
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun());
+ assertThat(thrown).hasMessageThat().contains("not permitted");
+ }
+
+ @Test
+ public void cannotUpdateCheckAnonymously() throws Exception {
+ requestScopeOperations.setApiUserAnonymous();
+ checkOperations.newCheck(checkKey).state(CheckState.SUCCESSFUL).upsert();
+
+ AuthException thrown =
+ assertThrows(
+ AuthException.class,
+ () -> checksApiFactory.revision(patchSetId).id(checkKey.checkerUuid()).rerun());
+ assertThat(thrown).hasMessageThat().contains("Authentication required");
+ }
+}
diff --git a/resources/Documentation/rest-api-checks.md b/resources/Documentation/rest-api-checks.md
index 048c868..4395644 100644
--- a/resources/Documentation/rest-api-checks.md
+++ b/resources/Documentation/rest-api-checks.md
@@ -157,6 +157,17 @@
the URL, it must either match the value provided in the request body via
[CheckInput](#check-input) or the value in the request body is omitted.
+### <a id="rerun-check"> Rerun Check
+
+_'POST /changes/1/revisions/1/checks/test:my-checker/rerun'_
+
+Reruns a check. As response the [CheckInfo](#check-info) entity is returned that
+describes the created check.
+
+
+This REST endpoint supports rerunning a check. It also resets all relevant check
+fields such as `message`, `url`, `started` and `finished`.
+
## <a id="json-entities"> JSON Entities
### <a id="check-info"> CheckInfo