| // 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; |
| } |
| } |
| } |