blob: f8aab70e519849ab4bdd45266fff457840e66499 [file] [log] [blame]
// 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 static com.google.common.truth.Truth.assertThat;
import static com.googlesource.gerrit.plugins.copyright.ScannerConfig.KEY_ALWAYS_REVIEW_PATH;
import static com.googlesource.gerrit.plugins.copyright.ScannerConfig.KEY_ENABLE;
import static com.googlesource.gerrit.plugins.copyright.ScannerConfig.KEY_EXCLUDE;
import static com.googlesource.gerrit.plugins.copyright.ScannerConfig.KEY_FIRST_PARTY;
import static com.googlesource.gerrit.plugins.copyright.ScannerConfig.KEY_FORBIDDEN;
import static com.googlesource.gerrit.plugins.copyright.ScannerConfig.KEY_FORBIDDEN_PATTERN;
import static com.googlesource.gerrit.plugins.copyright.ScannerConfig.KEY_REVIEW_LABEL;
import static com.googlesource.gerrit.plugins.copyright.ScannerConfig.KEY_THIRD_PARTY;
import static com.googlesource.gerrit.plugins.copyright.ScannerConfig.KEY_THIRD_PARTY_ALLOWED_PROJECTS;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.PluginConfig;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.project.GroupList;
import com.google.gerrit.server.project.ProjectConfig;
import java.util.Arrays;
import java.util.function.Consumer;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevBlob;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
/** Test helper for copyright plugin All-Projects configurations. */
class TestConfig {
static final String LOCAL_BRANCH = "config";
private static final String ACCESS = "access";
private static final String LABEL = "label";
private static final String PLUGIN = "plugin";
private Project.NameKey project;
private String pluginName;
private TestAccount owner;
private TestRepository<InMemoryRepository> testRepo;
private Config configProject;
private Config configPlugin;
private PluginConfig pluginConfig;
private GroupList groupList;
private String commitMsg;
TestConfig(
Project.NameKey project,
String pluginName,
TestAccount owner,
TestRepository<InMemoryRepository> testRepo)
throws Exception {
this.project = project;
this.pluginName = pluginName;
this.owner = owner;
this.testRepo = testRepo;
readConfigProject();
extractPlugin();
this.commitMsg = "This is a commit.";
}
static final Consumer<PluginConfig> BASE_CONFIG =
pCfg -> {
pCfg.setStringList(KEY_ALWAYS_REVIEW_PATH, ImmutableList.of("PATENT$"));
pCfg.setStringList(
KEY_THIRD_PARTY_ALLOWED_PROJECTS, ImmutableList.of("ThirdParty(?:Owner)?Allowed"));
pCfg.setString(KEY_REVIEW_LABEL, "Copyright-Review");
pCfg.setStringList(KEY_EXCLUDE, ImmutableList.of("EXAMPLES"));
pCfg.setStringList(
KEY_FIRST_PARTY, ImmutableList.of("APACHE2", "ANDROID", "GOOGLE", "EXAMPLES"));
pCfg.setStringList(
KEY_THIRD_PARTY, ImmutableList.of("BSD", "MIT", "EPL", "GPL2", "GPL3", "PSFL"));
pCfg.setStringList(
KEY_FORBIDDEN,
ImmutableList.of(
"AGPL",
"NOT_A_CONTRIBUTION",
"WTFPL",
"CC_BY_NC",
"NON_COMMERCIAL",
"COMMONS_CLAUSE"));
pCfg.setStringList(
KEY_FORBIDDEN_PATTERN,
ImmutableList.of("license .*(?:Previously|formerly) licen[cs]ed under.*"));
};
static final Consumer<PluginConfig> ENABLE_CONFIG =
pCfg -> {
pCfg.setBoolean(KEY_ENABLE, true);
};
static final Consumer<PluginConfig> DISABLE_CONFIG =
pCfg -> {
pCfg.setBoolean(KEY_ENABLE, false);
};
/** Adds {@code groups} to the groups file. */
void addGroups(InternalGroup... groups) throws Exception {
if (groups == null) {
return;
}
for (InternalGroup group : groups) {
groupList.put(
group.getGroupUUID(), new GroupReference(group.getGroupUUID(), group.getNameKey().get()));
}
}
/**
* Make a copy of an existing label configuration {@code fromLabel} under a different name, {@code
* toLabel}.
*
* <p>Useful shortcut for configuring a Copyright-Review label based on the default Code-Review
* label.
*/
void copyLabel(String fromLabel, String toLabel) throws Exception {
for (String name : configProject.getNames(LABEL, fromLabel)) {
configProject.setStringList(
LABEL, toLabel, name, Arrays.asList(configProject.getStringList(LABEL, fromLabel, name)));
}
for (String sub : configProject.getSubsections(ACCESS)) {
for (String name : configProject.getNames(ACCESS, sub)) {
if (!name.endsWith("-" + fromLabel)) {
continue;
}
String[] values = configProject.getStringList(ACCESS, sub, name);
if (values == null || values.length == 0) {
continue;
}
configProject.setStringList(
ACCESS,
sub,
name.substring(0, name.length() - fromLabel.length()) + toLabel,
Arrays.asList(values));
}
}
}
/** Creates a commit for the current configuration and pushes the commit to trigger validation. */
PushOneCommit.Result push(PushOneCommit.Factory pushFactory) throws Exception {
savePlugin();
String subject = "Change All-Projects project.config\n\n" + commitMsg;
PersonIdent author = owner.newIdent();
PushOneCommit pushCommit =
pushFactory.create(
author,
testRepo,
subject,
ImmutableMap.<String, String>of(
ProjectConfig.PROJECT_CONFIG,
configProject.toText(),
GroupList.FILE_NAME,
groupList.asText()));
return pushCommit.to("refs/for/" + RefNames.REFS_CONFIG);
}
/** Adds {@code message} to the /COMMIT_MSG text for the push. */
void commitMessage(String message) throws Exception {
commitMsg = commitMsg + message;
}
/** Deletes the label section and voter configurations for {@code label}. */
void removeLabel(String label) throws Exception {
for (String sub : configProject.getSubsections(ACCESS)) {
configProject.setStringList(ACCESS, sub, "label-" + label, ImmutableList.of());
configProject.setStringList(ACCESS, sub, "labelAs-" + label, ImmutableList.of());
}
configProject.unsetSection(LABEL, label);
}
/** Deletes the voter configurations for {@code label} from the access section for {@code ref}. */
void removeVoters(String ref, String label) throws Exception {
configProject.setStringList(ACCESS, ref, "label-" + label, ImmutableList.of());
}
/**
* Replaces the voter configuration for {@code label} in the access section for {@code ref} with
* {@code voters}.
*/
void setVoters(String ref, String label, Voter... voters) throws Exception {
ImmutableList.Builder<String> builder = ImmutableList.builder();
for (Voter v : voters) {
builder.add(vString(v.minVote) + ".." + vString(v.maxVote) + " group " + v.groupName);
}
configProject.setStringList(ACCESS, ref, "label-" + label, builder.build());
}
/** Apply {@code updates} to {@code this.pluginConfig}. */
void updatePlugin(Consumer<PluginConfig>... updates) throws Exception {
for (Consumer<PluginConfig> update : updates) {
update.accept(pluginConfig);
}
}
/** Extracts the plugin section from the project config and creates a PluginConfig view of it. */
private void extractPlugin() throws Exception {
configPlugin = new Config();
for (String name : configProject.getNames(PLUGIN, pluginName)) {
configPlugin.setStringList(
PLUGIN,
pluginName,
name,
Arrays.asList(configProject.getStringList(PLUGIN, pluginName, name)));
}
pluginConfig = new PluginConfig(pluginName, configPlugin);
}
/** Reads the project.config and groups files from {@code this.testRepo}. */
private void readConfigProject() throws Exception {
Ref ref = testRepo.getRepository().exactRef(LOCAL_BRANCH);
configProject = new Config();
configProject.fromText(readFileContents(ref, ProjectConfig.PROJECT_CONFIG));
groupList = GroupList.parse(project, readFileContents(ref, GroupList.FILE_NAME), e -> {});
}
/** Reads the contents of {@code ref}/{@code path} from {@code this.restRepo}. */
private String readFileContents(Ref ref, String path) throws Exception {
RevWalk rw = testRepo.getRevWalk();
RevObject obj = testRepo.get(rw.parseTree(ref.getObjectId()), path);
assertThat(obj).isInstanceOf(RevBlob.class);
ObjectLoader loader = rw.getObjectReader().open(obj);
return new String(loader.getCachedBytes(), UTF_8);
}
/**
* Replaces the plugin section in {@code this.configProject} with the contents of {@code
* this.configPlugin}.
*/
private void savePlugin() throws Exception {
configProject.unsetSection(PLUGIN, pluginName);
for (String name : configPlugin.getNames(PLUGIN, pluginName)) {
configProject.setStringList(
PLUGIN,
pluginName,
name,
Arrays.asList(configPlugin.getStringList(PLUGIN, pluginName, name)));
}
}
/** Formats integers with an explicit sign for both positive and negative values. */
private String vString(int vote) throws Exception {
return (vote > 0 ? "+" : "") + Integer.toString(vote);
}
/** A group name and allowed vote range. */
static class Voter {
String groupName;
int minVote;
int maxVote;
Voter(String groupName, int minVote, int maxVote) {
this.groupName = groupName;
this.minVote = minVote;
this.maxVote = maxVote;
}
}
}