ProjectOperations: Allow arbitrary updates of project.config

This allows tests to insert invalid entries in the project.config file.

Signed-off-by: Edwin Kempin <ekempin@google.com>
Change-Id: I01440867b295e843507ca41bcd703af659db32dd
This commit is contained in:
Edwin Kempin
2020-03-11 10:06:19 +01:00
parent 9763044457
commit d43fbecbaa
5 changed files with 140 additions and 19 deletions

View File

@@ -14,6 +14,7 @@ java_library(
"//java/com/google/gerrit/server",
"//lib:guava",
"//lib:jgit",
"//lib:jgit-junit",
"//lib/auto:auto-value",
"//lib/auto:auto-value-annotations",
"//lib/commons:lang",

View File

@@ -73,5 +73,25 @@ public interface ProjectOperations {
* @return a builder to update the check.
*/
TestProjectUpdate.Builder forUpdate();
/**
* Starts the fluent chain to invalidate a project. The returned builder can be used to specify
* how the project should be made invalid. To invalidate the project for real, {@link
* TestProjectInvalidation.Builder#invalidate()} must be called.
*
* <p>Example:
*
* <pre>
* projectOperations.forInvalidation()
* .addProjectConfigUpdater(cfg -> cfg.setString("invalidSection", null, "foo", "bar"))
* .invalidate();
* </pre>
*
* <p><strong>Note:</strong> The invalidation will fail with an exception if the project to
* invalidate doesn't exist.
*
* @return a builder to invalidate the project
*/
TestProjectInvalidation.Builder forInvalidation();
}
}

View File

@@ -19,6 +19,7 @@ import static com.google.gerrit.server.project.ProjectConfig.PROJECT_CONFIG;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.testsuite.project.TestProjectCreation.Builder;
@@ -45,6 +46,7 @@ import java.util.ArrayList;
import java.util.Collections;
import org.apache.commons.lang.RandomStringUtils;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectLoader;
@@ -255,6 +257,48 @@ public class ProjectOperationsImpl implements ProjectOperations {
throw new IllegalStateException(e);
}
}
private void setConfig(Config projectConfig) {
try (TestRepository<Repository> repo =
new TestRepository<>(repoManager.openRepository(nameKey))) {
repo.update(
RefNames.REFS_CONFIG,
repo.commit()
.message("Update project.config from test")
.parent(getHead(RefNames.REFS_CONFIG))
.add(ProjectConfig.PROJECT_CONFIG, projectConfig.toText()));
} catch (Exception e) {
throw new IllegalStateException(
"updating project.config of project " + nameKey + " failed", e);
}
}
@Override
public TestProjectInvalidation.Builder forInvalidation() {
return TestProjectInvalidation.builder(this::invalidateProject);
}
private void invalidateProject(TestProjectInvalidation testProjectInvalidation)
throws Exception {
if (!testProjectInvalidation.projectConfigUpdater().isEmpty()) {
Config projectConfig = new Config();
projectConfig.fromText(getConfig().toText());
testProjectInvalidation.projectConfigUpdater().forEach(c -> c.accept(projectConfig));
setConfig(projectConfig);
try {
projectCache.evict(nameKey);
} catch (Exception e) {
// Evicting the project from the cache, also triggers a reindex of the project.
// The reindex step fails if the project config is invalid. That's fine, since it was our
// intention to make the project config invalid. Hence we ignore exceptions that are cause
// by an invalid project config here.
if (!Throwables.getCausalChain(e).stream()
.anyMatch(ConfigInvalidException.class::isInstance)) {
throw e;
}
}
}
}
}
private static PermissionRule newRule(ProjectConfig project, AccountGroup.UUID groupUUID) {

View File

@@ -0,0 +1,64 @@
// Copyright (C) 2020 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.acceptance.testsuite.project;
import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.testsuite.ThrowingConsumer;
import java.util.function.Consumer;
import org.eclipse.jgit.lib.Config;
/**
* API to invalidate projects in tests.
*
* <p>This allows to test Gerrit behavior when there is invalid project data in NoteDb (e.g. an
* invalid {@code project.config} file).
*/
@AutoValue
public abstract class TestProjectInvalidation {
public abstract ImmutableList<Consumer<Config>> projectConfigUpdater();
abstract ThrowingConsumer<TestProjectInvalidation> projectInvalidator();
public static Builder builder(ThrowingConsumer<TestProjectInvalidation> projectInvalidator) {
return new AutoValue_TestProjectInvalidation.Builder().projectInvalidator(projectInvalidator);
}
@AutoValue.Builder
public abstract static class Builder {
/**
* Adds a consumer that can update the project config.
*
* <p>This allows tests to set arbitrary values in the project config.
*/
public Builder addProjectConfigUpdater(Consumer<Config> projectConfigUpdater) {
projectConfigUpdaterBuilder().add(projectConfigUpdater);
return this;
}
protected abstract ImmutableList.Builder<Consumer<Config>> projectConfigUpdaterBuilder();
abstract Builder projectInvalidator(
ThrowingConsumer<TestProjectInvalidation> projectInvalidator);
abstract TestProjectInvalidation autoBuild();
/** Executes the project invalidation as specified. */
public void invalidate() {
TestProjectInvalidation projectInvalidation = autoBuild();
projectInvalidation.projectInvalidator().acceptAndThrowSilently(projectInvalidation);
}
}
}