blob: 681ed26bb2022d3c9465eec769fa11272844593b [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_CC;
import static com.googlesource.gerrit.plugins.copyright.ScannerConfig.KEY_FROM;
import static com.googlesource.gerrit.plugins.copyright.ScannerConfig.KEY_REVIEWER;
import static com.googlesource.gerrit.plugins.copyright.TestConfig.LOCAL_BRANCH;
import com.google.common.collect.ImmutableList;
import com.google.common.truth.Correspondence;
import com.google.gerrit.acceptance.GerritConfig;
import com.google.gerrit.acceptance.GerritConfigs;
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestAccount;
import com.google.gerrit.acceptance.TestPlugin;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.ServerInitiated;
import com.google.gerrit.server.group.InternalGroup;
import com.google.gerrit.server.group.db.GroupsUpdate;
import com.google.gerrit.server.group.db.InternalGroupCreation;
import com.google.gerrit.server.group.db.InternalGroupUpdate;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.util.Optional;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository;
import org.junit.Before;
import org.junit.Test;
@TestPlugin(name = "copyright", sysModule = "com.googlesource.gerrit.plugins.copyright.Module")
public class CopyrightValidatorIT extends LightweightPluginDaemonTest {
@Test
@GerritConfigs({
@GerritConfig(name = "plugin.copyright.enable", value = "true"),
@GerritConfig(name = "plugin.copyright.timeTestMax", value = "0")
})
public void testCopyrightValidator_pushNoLicense() throws Exception {
PushOneCommit.Result result =
pushFactory
.create(author.newIdent(), testRepo, "subject", "filename", "content")
.to("refs/for/master");
assertNoReviewerAdded(result);
assertThat(result.getChange().notes().getComments()).isEmpty();
}
@Test
@GerritConfigs({
@GerritConfig(name = "plugin.copyright.enable", value = "true"),
@GerritConfig(name = "plugin.copyright.timeTestMax", value = "0")
})
public void testCopyrightValidator_pushAlwaysReview() throws Exception {
PushOneCommit.Result result =
pushFactory
.create(author.newIdent(), testRepo, "subject", "PATENT", "content")
.to("refs/for/master");
assertReviewerAdded(result);
assertThat(result.getChange().notes().getComments().values())
.comparingElementsUsing(commentContains())
.containsExactly(unresolved("PATENT always requires"));
}
@Test
@GerritConfigs({
@GerritConfig(name = "plugin.copyright.enable", value = "true"),
@GerritConfig(name = "plugin.copyright.timeTestMax", value = "0")
})
public void testCopyrightValidator_pushFirstPartyOwner() throws Exception {
PushOneCommit.Result result =
pushFactory
.create(author.newIdent(), testRepo, "subject", "source.cpp", FIRST_PARTY_OWNER)
.to("refs/for/master");
assertNoReviewerAdded(result);
assertThat(result.getChange().notes().getComments().values())
.comparingElementsUsing(commentContains())
.containsExactly(resolved("First-party author or owner"));
}
@Test
@GerritConfigs({
@GerritConfig(name = "plugin.copyright.enable", value = "true"),
@GerritConfig(name = "plugin.copyright.timeTestMax", value = "0")
})
public void testCopyrightValidator_pushFirstPartyHeader() throws Exception {
PushOneCommit.Result result =
pushFactory
.create(author.newIdent(), testRepo, "subject", "source.cpp", FIRST_PARTY_HEADER)
.to("refs/for/master");
assertNoReviewerAdded(result);
assertThat(result.getChange().notes().getComments().values())
.comparingElementsUsing(commentContains())
.containsExactly(resolved("First-party license"));
}
@Test
@GerritConfigs({
@GerritConfig(name = "plugin.copyright.enable", value = "true"),
@GerritConfig(name = "plugin.copyright.timeTestMax", value = "0")
})
public void testCopyrightValidator_pushFirstPartyOwnerAndHeader() throws Exception {
PushOneCommit.Result result =
pushFactory
.create(
author.newIdent(),
testRepo,
"subject",
"source.cpp",
FIRST_PARTY_OWNER + FIRST_PARTY_HEADER)
.to("refs/for/master");
assertNoReviewerAdded(result);
assertThat(result.getChange().notes().getComments().values())
.comparingElementsUsing(commentContains())
.containsExactly(resolved("First-party license")); // owner folds into license
}
@Test
@GerritConfigs({
@GerritConfig(name = "plugin.copyright.enable", value = "true"),
@GerritConfig(name = "plugin.copyright.timeTestMax", value = "0")
})
public void testCopyrightValidator_pushFirstPartyHeaderAndOwner() throws Exception {
PushOneCommit.Result result =
pushFactory
.create(
author.newIdent(),
testRepo,
"subject",
"source.cpp",
FIRST_PARTY_HEADER + FIRST_PARTY_OWNER)
.to("refs/for/master");
assertNoReviewerAdded(result);
assertThat(result.getChange().notes().getComments().values())
.comparingElementsUsing(commentContains())
.containsExactly(resolved("First-party license")); // owner folds into license
}
@Test
@GerritConfigs({
@GerritConfig(name = "plugin.copyright.enable", value = "true"),
@GerritConfig(name = "plugin.copyright.timeTestMax", value = "0")
})
public void testCopyrightValidator_pushNotAContribHeader() throws Exception {
PushOneCommit.Result result =
pushFactory
.create(author.newIdent(), testRepo, "subject", "source.cpp", NOT_A_CONTRIB_HEADER)
.to("refs/for/master");
assertReviewerAdded(result);
assertThat(result.getChange().notes().getComments().values())
.comparingElementsUsing(commentContains())
.containsExactly(resolved("First-party license"), unresolved("Disapproved license"));
}
@Test
@GerritConfigs({
@GerritConfig(name = "plugin.copyright.enable", value = "true"),
@GerritConfig(name = "plugin.copyright.timeTestMax", value = "0")
})
public void testCopyrightValidator_pushThirdPartyAllowed() throws Exception {
PushOneCommit.Result result =
pushFactory
.create(author.newIdent(), testRepo, "subject", "LICENSE", THIRD_PARTY_MIT)
.to("refs/for/master");
assertNoReviewerAdded(result);
assertThat(result.getChange().notes().getComments().values())
.comparingElementsUsing(commentContains())
.containsExactly(resolved("Third-party license allowed"));
}
@Test
@GerritConfigs({
@GerritConfig(name = "plugin.copyright.enable", value = "true"),
@GerritConfig(name = "plugin.copyright.timeTestMax", value = "0")
})
public void testCopyrightValidator_pushThirdPartyNotAllowed() throws Exception {
PushOneCommit.Result result =
pushFactory
.create(author.newIdent(), testRepo, "subject", "LICENSE", THIRD_PARTY_MIT)
.to("refs/for/master");
assertReviewerAdded(result);
assertThat(result.getChange().notes().getComments().values())
.comparingElementsUsing(commentContains())
.containsExactly(unresolved("Third-party license disallowed"));
}
@Test
@GerritConfigs({
@GerritConfig(name = "plugin.copyright.enable", value = "true"),
@GerritConfig(name = "plugin.copyright.timeTestMax", value = "0")
})
public void testCopyrightValidator_pushThirdPartyOwnerAllowed() throws Exception {
PushOneCommit.Result result =
pushFactory
.create(author.newIdent(), testRepo, "subject", "COPYING", THIRD_PARTY_OWNER)
.to("refs/for/master");
assertNoReviewerAdded(result);
assertThat(result.getChange().notes().getComments().values())
.comparingElementsUsing(commentContains())
.containsExactly(resolved("Third-party author or owner allowed"));
}
@Test
@GerritConfigs({
@GerritConfig(name = "plugin.copyright.enable", value = "true"),
@GerritConfig(name = "plugin.copyright.timeTestMax", value = "0")
})
public void testCopyrightValidator_pushThirdPartyOwnerNotAllowed() throws Exception {
PushOneCommit.Result result =
pushFactory
.create(author.newIdent(), testRepo, "subject", "COPYING", THIRD_PARTY_OWNER)
.to("refs/for/master");
assertReviewerAdded(result);
assertThat(result.getChange().notes().getComments().values())
.comparingElementsUsing(commentContains())
.containsExactly(unresolved("Third-party author or owner disallowed"));
}
@Test
@GerritConfigs({
@GerritConfig(name = "plugin.copyright.enable", value = "true"),
@GerritConfig(name = "plugin.copyright.timeTestMax", value = "0")
})
public void testCopyrightValidator_pushFirstPartyLicenseThirdPartyOwner() throws Exception {
PushOneCommit.Result result =
pushFactory
.create(
author.newIdent(),
testRepo,
"subject",
"COPYING",
FIRST_PARTY_HEADER + THIRD_PARTY_OWNER)
.to("refs/for/master");
assertNoReviewerAdded(result);
assertThat(result.getChange().notes().getComments().values())
.comparingElementsUsing(commentContains())
.containsExactly(
resolved("First-party license"),
resolved("Third-party author or owner")); // 1p license from 3p author is 1p
}
@Test
@GerritConfigs({
@GerritConfig(name = "plugin.copyright.enable", value = "true"),
@GerritConfig(name = "plugin.copyright.timeTestMax", value = "0")
})
public void testCopyrightValidator_pushFirstPartyOwnerThirdPartyOwner() throws Exception {
PushOneCommit.Result result =
pushFactory
.create(
author.newIdent(),
testRepo,
"subject",
"COPYING",
FIRST_PARTY_OWNER + THIRD_PARTY_OWNER)
.to("refs/for/master");
assertReviewerAdded(result);
assertThat(result.getChange().notes().getComments().values())
.comparingElementsUsing(commentContains())
.containsExactly(
resolved("First-party author or owner"),
unresolved("Third-party author or owner disallowed"));
}
@Test
@GerritConfigs({
@GerritConfig(name = "plugin.copyright.enable", value = "true"),
@GerritConfig(name = "plugin.copyright.timeTestMax", value = "0")
})
public void testCopyrightValidator_pushThirdPartyLicenseFirstPartyOwner() throws Exception {
PushOneCommit.Result result =
pushFactory
.create(
author.newIdent(),
testRepo,
"subject",
"COPYING",
THIRD_PARTY_MIT + FIRST_PARTY_OWNER)
.to("refs/for/master");
assertReviewerAdded(result);
assertThat(result.getChange().notes().getComments().values())
.comparingElementsUsing(commentContains())
.containsExactly(
unresolved("Third-party license disallowed"), resolved("First-party author or owner"));
}
private static final String FIRST_PARTY_OWNER =
"// Copyright (C) 2019 The Android Open Source Project\n";
private static final String FIRST_PARTY_HEADER =
"// Licensed under the Apache License, Version 2.0 (the \"License\");\n"
+ "// you may not use this file except in compliance with the License.\n"
+ "// You may obtain a copy of the License at\n//\n"
+ "// http://www.apache.org/licenses/LICENSE-2.0\n//\n"
+ "// Unless required by applicable law or agreed to in writing, software\n"
+ "// distributed under the License is distributed on an \"AS IS\" BASIS,\n"
+ "// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n"
+ "// See the License for the specific language governing permissions and\n"
+ "// limitations under the License.\n";
private static final String NOT_A_CONTRIB_HEADER = FIRST_PARTY_HEADER + "Not a contribution.\n";
private static final String THIRD_PARTY_MIT =
"MIT License\n\nCopyright (c) Jane Doe\n\n"
+ "Permission is hereby granted, free of charge, to any person obtaining a copy\n"
+ "of this software and associated documentation files (the \"Software\"), to deal\n"
+ "in the Software without restriction, including without limitation the rights\n"
+ "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n"
+ "copies of the Software, and to permit persons to whom the Software is\n"
+ "furnished to do so, subject to the following conditions:\n\n"
+ "The above copyright notice and this permission notice shall be included in all\n"
+ "copies or substantial portions of the Software.\n\n"
+ "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
+ "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
+ "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n"
+ "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
+ "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"
+ "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
+ "SOFTWARE.\n";
private static final String THIRD_PARTY_OWNER = "Copyright (c) 2019 Acme Other Corp.\n";
private static int nextId = 123;
@Inject @ServerInitiated private Provider<GroupsUpdate> groupsUpdateProvider;
private InternalGroup botGroup;
private InternalGroup expertGroup;
private TestAccount pluginAccount;
private TestAccount reviewer;
private TestAccount observer;
private TestAccount author;
private String projectConfigContent;
@Before
public void setUp() throws Exception {
botGroup = testGroup("Non-Interactive Users");
expertGroup = testGroup("Copyright Experts");
pluginAccount =
accountCreator.create(
"copyright-scanner",
"copyright-scanner@example.com",
"Copyright Scanner",
"Non-Interactive Users",
expertGroup.getName());
reviewer =
accountCreator.create(
"lawyercat", "legal@example.com", "J. Doe J.D. LL.M. Esq.", expertGroup.getName());
observer = accountCreator.create("my-team", "my-team@example.com", "My Team");
author = accountCreator.create("author", "author@example.com", "J. Doe");
TestRepository<InMemoryRepository> testRepo = getTestRepo(allProjects);
TestConfig testConfig = new TestConfig(allProjects, plugin.getName(), admin, testRepo);
testConfig.copyLabel("Code-Review", "Copyright-Review");
testConfig.setVoters(
RefNames.REFS_HEADS + "*",
"Copyright-Review",
new TestConfig.Voter("Administrators", -2, +2),
new TestConfig.Voter(expertGroup.getNameKey().get(), -2, +2),
new TestConfig.Voter("Registered Users", -2, 0));
testConfig.addGroups(botGroup, expertGroup);
testConfig.updatePlugin(
TestConfig.BASE_CONFIG,
TestConfig.ENABLE_CONFIG,
cfg -> {
cfg.setStringList(KEY_REVIEWER, ImmutableList.of(reviewer.username()));
},
cfg -> {
cfg.setStringList(KEY_CC, ImmutableList.of(observer.username()));
},
cfg -> {
cfg.setInt(KEY_FROM, pluginAccount.id().get());
});
PushOneCommit.Result result = testConfig.push(pushFactory);
result.assertOkStatus();
assertThat(result.getChange().publishedComments()).isEmpty();
merge(result);
}
private AccountGroup.Id nextGroupId() {
return new AccountGroup.Id(nextId++);
}
private TestRepository<InMemoryRepository> getTestRepo(Project.NameKey projectName)
throws Exception {
TestRepository<InMemoryRepository> testRepo = cloneProject(projectName, admin);
GitUtil.fetch(testRepo, RefNames.REFS_CONFIG + ":" + LOCAL_BRANCH);
testRepo.reset(LOCAL_BRANCH);
return testRepo;
}
private InternalGroup testGroup(String name) throws Exception {
AccountGroup.NameKey nameKey = new AccountGroup.NameKey(name);
Optional<InternalGroup> g = groupCache.get(nameKey);
if (g.isPresent()) {
return g.get();
}
GroupsUpdate groupsUpdate = groupsUpdateProvider.get();
InternalGroupCreation gc =
InternalGroupCreation.builder()
.setGroupUUID(new AccountGroup.UUID("users-" + name.replace(" ", "_")))
.setNameKey(nameKey)
.setId(nextGroupId())
.build();
InternalGroupUpdate gu = InternalGroupUpdate.builder().setName(nameKey).build();
return groupsUpdate.createGroup(gc, gu);
}
private void assertReviewerAdded(PushOneCommit.Result result) throws Exception {
result.assertOkStatus();
result.assertChange(
Change.Status.NEW,
null,
ImmutableList.of(author, reviewer),
ImmutableList.of(pluginAccount, observer));
}
private void assertNoReviewerAdded(PushOneCommit.Result result) throws Exception {
result.assertOkStatus();
result.assertChange(
Change.Status.NEW, null, ImmutableList.of(author), ImmutableList.of(pluginAccount));
}
private CommentMatch resolved(String content) {
return new CommentMatch(true /* resolved */, content);
}
private CommentMatch unresolved(String content) {
return new CommentMatch(false /* resolved */, content);
}
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";
}
};
}
private static class CommentMatch {
boolean resolved;
String content;
CommentMatch(boolean resolved, String content) {
this.resolved = resolved;
this.content = content;
}
@Override
public String toString() {
return (resolved ? "" : "un") + "resolved(\"" + content + "\")";
}
}
}