blob: 53bc4f3d2900dc34241f1059f13592fd2c47a777 [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.findowners;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.flogger.FluentLogger;
import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.TestPlugin;
import com.google.gerrit.entities.Project;
import org.junit.Rule;
import org.junit.Test;
/** Test find-owners plugin features related to include and file statements. */
@TestPlugin(name = "find-owners", sysModule = "com.googlesource.gerrit.plugins.findowners.Module")
public class IncludeIT extends FindOwners {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
@Rule public Watcher watcher = new Watcher(logger);
private static String getRepoFileLog(String msg1, String msg2) {
return "getRepoFile:" + msg1 + ",getFile:" + msg2 + ",";
}
private static String concat(String s1, String s2) {
return s1 + s2;
}
private static String concat(String s1, String s2, String s3) {
return s1 + s2 + s3;
}
@Test
public void includeNotFoundTest() throws Exception {
// c2 and c1 are both submitted before existence of OWNERS.
PushOneCommit.Result c2 = addFile("1", "t.c", "##");
// Submitted c2 still finds no owners before c1 is submitted.
assertThat(getOwnersResponse(c2)).contains("owners:[],files:[t.c]");
PushOneCommit.Result c1 =
addFile("2", "OWNERS", "x@x\na@a\ninclude P1/P2 : f1\ninclude ./d1/d2/../../f2\n");
// Now c2 should find owners, but include directives find no repository or file.
String ownersAX = "owners:[" + ownerJson("a@a") + "," + ownerJson("x@x") + "]";
String path2owners = "path2owners:{./:[a@a,x@x]}";
String owner2paths = "owner2paths:{a@a:[./],x@x:[./]}";
String projectName = project.get();
String expectedInLog =
concat("project:", projectName, ",")
+ "ownersFileName:OWNERS,"
+ "getBranchId:refs/heads/master(FOUND),"
+ "findOwnersFileFor:./t.c,"
+ "findOwnersFileIn:.,"
+ getRepoFileLog(projectName + ":refs/heads/master:./OWNERS", "OWNERS:(...)")
+ "parseLine:include:P1/P2:f1,"
+ "getRepoFile:P1/P2:refs/heads/master:f1,"
+ "hasReadAccessException:project'P1/P2'isunavailable," // cannot read
+ "parseLine:include:()," // missing file is treated as empty
+ concat("parseLine:include:", projectName, ":./d1/d2/../../f2,")
+ getRepoFileLog(projectName + ":refs/heads/master:f2", "f2(NOTFOUND)")
+ "parseLine:include:()," // missing file is treated as empty
+ "countNumOwners,"
+ "findOwners,"
+ "checkFile:./t.c,"
+ "checkDir:.,"
+ "addOwnerWeightsIn:./"
+ "]";
String c2Response = getOwnersDebugResponse(c2);
assertThat(c2Response).contains(path2owners);
assertThat(c2Response).contains(owner2paths);
assertThat(c2Response).contains("file2owners:{./t.c:[a@a,x@x]}");
assertThat(c2Response).contains(ownersAX);
assertThat(c2Response).contains(expectedInLog);
// A submitted change gets owners info from current repository.
String c1Response = getOwnersDebugResponse(c1);
assertThat(c1Response).contains(path2owners);
assertThat(c1Response).contains(owner2paths);
assertThat(c1Response).contains("file2owners:{./OWNERS:[a@a,x@x]}");
assertThat(c1Response).contains(ownersAX);
}
@Test
public void includeFoundTest() throws Exception {
// Compared with includeNotFoundTest, this one has file "f2" to include.
addFile("c0", "f2", "g1@g\ng2@g\n");
// c2 and c1 are both submitted before existence of OWNERS.
PushOneCommit.Result c2 = addFile("c2", "t.c", "##");
PushOneCommit.Result c1 =
addFile("c1", "OWNERS", "x@x\na@a\ninclude P1/P2 : f1\ninclude ./d1/d2/../../f2\n");
String ownerA = ownerJson("a@a");
String ownerX = ownerJson("x@x");
String ownerG1 = ownerJson("g1@g");
String ownerG2 = ownerJson("g2@g");
String ownersAG1G2X = "owners:[" + ownerA + "," + ownerG1 + "," + ownerG2 + "," + ownerX + "]";
String path2owners = "path2owners:{./:[a@a,g1@g,g2@g,x@x]}";
String owner2paths = "owner2paths:{a@a:[./],g1@g:[./],g2@g:[./],x@x:[./]}";
String projectName = project.get();
String expectedInLog =
concat("project:", projectName, ",")
+ "ownersFileName:OWNERS,"
+ "getBranchId:refs/heads/master(FOUND),"
+ "findOwnersFileFor:./t.c,"
+ "findOwnersFileIn:.,"
+ getRepoFileLog(projectName + ":refs/heads/master:./OWNERS", "OWNERS:(...)")
+ "parseLine:include:P1/P2:f1,"
+ "getRepoFile:P1/P2:refs/heads/master:f1,"
+ "hasReadAccessException:project'P1/P2'isunavailable,"
+ "parseLine:include:()," // P1/P2 is still not found
+ concat("parseLine:include:", projectName, ":./d1/d2/../../f2,")
+ getRepoFileLog(projectName + ":refs/heads/master:f2", "f2:(...)")
+ "countNumOwners,"
+ "findOwners,"
+ "checkFile:./t.c,"
+ "checkDir:.,"
+ "addOwnerWeightsIn:./"
+ "]";
String c2Response = getOwnersDebugResponse(c2);
assertThat(c2Response).contains(path2owners);
assertThat(c2Response).contains(owner2paths);
assertThat(c2Response).contains("file2owners:{./t.c:[a@a,g1@g,g2@g,x@x]}");
assertThat(c2Response).contains(ownersAG1G2X);
assertThat(c2Response).contains(expectedInLog);
// A submitted change gets owners info from current repository.
String c1Response = getOwnersDebugResponse(c1);
assertThat(c1Response).contains(path2owners);
assertThat(c1Response).contains(owner2paths);
assertThat(c1Response).contains("file2owners:{./OWNERS:[a@a,g1@g,g2@g,x@x]}");
assertThat(c1Response).contains(ownersAG1G2X);
}
@Test
public void includeIndirectFileTest() throws Exception {
// Test indirectly included file and relative file path.
addFile("1", "d1/f2", "d1f2@g\n");
addFile("2", "d2/f2", "d2f2@g\n");
addFile("3", "d3/f2", "d3f2@g\n");
addFile("4", "d1/d2/owners", "d1d2@g\ninclude ../f2\n");
addFile("5", "d2/d2/owners", "d2d2@g\ninclude ../f2\n");
addFile("6", "d3/d2/owners", "d3d2@g\ninclude ../f2\n");
addFile("7", "d3/OWNERS", "d3@g\ninclude ../d2/d2/owners\n");
addFile("8", "OWNERS", "x@g\n");
PushOneCommit.Result c1 = createChange("c1", "d3/t.c", "Hello!");
// d3's owners are in d3/OWNERS, d2/d2/owners, d2/f2, OWNERS,
// If the include directories are based on original directory d3,
// then the included files will be d2/d2/owners and d3/f2.
String ownerD3 = ownerJson("d3@g");
String ownerD2 = ownerJson("d2d2@g");
String ownerF2 = ownerJson("d2f2@g");
String ownerX = ownerJson("x@g", 0, 1, 0);
assertThat(getOwnersResponse(c1))
.contains(
"owners:["
+ concat(ownerD2, ",")
+ concat(ownerF2, ",")
+ concat(ownerD3, ",")
+ concat(ownerX, "],files:[d3/t.c]"));
}
@Test
public void includeVsFileTest() throws Exception {
// Test difference between include and file statements.
// The file statement skips "set noparent" and "per-file" statements.
addFile("d1", "d1/OWNERS", "d1@g\n");
addFile("d1/d1", "d1/d1/OWNERS", "per-file *.c=d1d1p@g\nd1d1@g\nfile: d1/OWNERS\n");
addFile("d1/d1/d1", "d1/d1/d1/OWNERS", "set noparent\nper-file *.c=d1d1d1p@g\nd1d1d1@g\n");
addFile("d1/d2", "d1/d2/OWNERS", "per-file *.c=d1d2p@g\nd1d2@g\ninclude d1/OWNERS\n");
addFile("d1/d2/d1", "d1/d2/d1/OWNERS", "set noparent\nper-file *.c=d1d2d1p@g\nd1d2d1@g\n");
addFile("d2", "d2/OWNERS", "d2@g\n");
addFile("d2/d1", "d2/d1/OWNERS", "per-file *.c=d2d1p@g\nd2d1@g\nfile: ./d1/OWNERS\n");
addFile("d2/d1/d1", "d2/d1/d1/OWNERS", "set noparent\nper-file *.c=d2d1d1p@g\nd2d1d1@g\n");
addFile("d2/d2", "d2/d2/OWNERS", "per-file *.c=d2d2p@g\nd2d2@g\ninclude ./d1/OWNERS\n");
addFile("d2/d2/d1", "d2/d2/d1/OWNERS", "set noparent\nper-file *.c=d2d2d1p@g\nd2d2d1@g\n");
addFile("d3", "d3/OWNERS", "d3@g\n");
addFile("d3/d1/d1", "d3/d1/d1/OWNERS", "d3d1d1@g\nfile: ../../../d1/d1/OWNERS\n");
addFile("d3/d1/d2", "d3/d1/d2/OWNERS", "d3d1d2@g\nfile: //d1/d2/OWNERS\n");
addFile("d3/d2/d1", "d3/d2/d1/OWNERS", "d3d2d1@g\ninclude /d2/d1/OWNERS\n");
addFile("d3/d2/d2", "d3/d2/d2/OWNERS", "d3d2d2@g\ninclude //d2/d2/OWNERS\n");
PushOneCommit.Result c11 = createChange("c11", "d3/d1/d1/t.c", "test");
PushOneCommit.Result c12 = createChange("c12", "d3/d1/d2/t.c", "test");
PushOneCommit.Result c21 = createChange("c21", "d3/d2/d1/t.c", "test");
PushOneCommit.Result c22 = createChange("c22", "d3/d2/d2/t.c", "test");
// file and file
String owners11 = "file2owners:{./d3/d1/d1/t.c:" + "[d1d1@g,d1d1d1@g,d3@g,d3d1d1@g]}";
// file and include
String owners12 = "file2owners:{./d3/d1/d2/t.c:" + "[d1d2@g,d1d2d1@g,d3@g,d3d1d2@g]}";
// include and file
String owners21 = "file2owners:{./d3/d2/d1/t.c:" + "[d2d1@g,d2d1d1@g,d2d1p@g,d3@g,d3d2d1@g]}";
// include and include
String owners22 =
"file2owners:{./d3/d2/d2/t.c:" + "[d2d2@g,d2d2d1@g,d2d2d1p@g,d2d2p@g,d3d2d2@g]}";
assertThat(getOwnersDebugResponse(c11)).contains(owners11);
assertThat(getOwnersDebugResponse(c12)).contains(owners12);
assertThat(getOwnersDebugResponse(c21)).contains(owners21);
assertThat(getOwnersDebugResponse(c22)).contains(owners22);
}
@Test
public void multipleIncludeTest() throws Exception {
// Now "include" and "file:" statements can share parsed results.
addFile("d1", "d1/OWNERS", "d1@g\n");
addFile("d2/d1", "d2/d1/OWNERS", "include /d1/OWNERS\nfile://d1/OWNERS\n");
addFile("d2/d2", "d2/d2/OWNERS", "file: //d1/OWNERS\ninclude /d1/OWNERS\n");
PushOneCommit.Result c1 = createChange("c1", "d2/d1/t.c", "test");
PushOneCommit.Result c2 = createChange("c2", "d2/d2/t.c", "test");
String projectName = project.get();
String log1 = "parseLine:useSaved:file:" + projectName + "://d1/OWNERS,";
String log2 = "parseLine:useSaved:include:" + projectName + ":/d1/OWNERS,";
String response1 = getOwnersDebugResponse(c1);
String response2 = getOwnersDebugResponse(c2);
assertThat(response1).contains(log1);
assertThat(response1).doesNotContain(log2);
assertThat(response2).doesNotContain(log1);
assertThat(response2).contains(log2);
}
@Test
public void includeCycleTest() throws Exception {
// f1 includes f2, f2 includes f3, f3 includes f4, f4 includes f2, OWNERS includes f1.
// All files are in the root directory, but could be referred with relative paths.
addFile("1", "f1", "f1@g\ninclude ./f2\n");
addFile("2", "f2", "f2@g\ninclude d1/../f3\n");
addFile("3", "f3", "f3@g\ninclude /f4\n");
addFile("4", "f4", "f4@g\ninclude d2/../f2\n");
addFile("5", "OWNERS", "x@g\ninclude ./d1/../f1\n");
PushOneCommit.Result c = createChange("6", "t.c", "#\n");
String response = getOwnersDebugResponse(c);
String projectName = project.get();
String expectedInLog =
concat("project:", projectName, ",")
+ "ownersFileName:OWNERS,"
+ "getBranchId:refs/heads/master(FOUND),"
+ "findOwnersFileFor:./t.c,"
+ "findOwnersFileIn:.,"
+ getRepoFileLog(projectName + ":refs/heads/master:./OWNERS", "OWNERS:(...)")
+ concat("parseLine:include:", projectName, ":./d1/../f1,")
+ getRepoFileLog(projectName + ":refs/heads/master:f1", "f1:(...)")
+ concat("parseLine:include:", projectName, ":./f2,")
+ getRepoFileLog(projectName + ":refs/heads/master:f2", "f2:(...)")
+ concat("parseLine:include:", projectName, ":d1/../f3,")
+ getRepoFileLog(projectName + ":refs/heads/master:f3", "f3:(...)")
+ concat("parseLine:include:", projectName, ":/f4,")
+ getRepoFileLog(projectName + ":refs/heads/master:f4", "f4:(...)")
+ concat("parseLine:errorRecursion:include:", projectName, ":d2/../f2,")
+ "countNumOwners,"
+ "findOwners,"
+ "checkFile:./t.c,"
+ "checkDir:.,"
+ "addOwnerWeightsIn:./"
+ "]";
assertThat(response).contains("path2owners:{./:[f1@g,f2@g,f3@g,f4@g,x@g]}");
assertThat(response).contains("owner2paths:{f1@g:[./],f2@g:[./],f3@g:[./],f4@g:[./],x@g:[./]}");
assertThat(response).contains(expectedInLog);
}
@Test
public void includeDuplicationTest() throws Exception {
// f0 is included into f1, f2, f3,
// f2 is included into f4 and f5; f4 is included into f5.
// f0, f1, f2, f3, f5 are included into d6/OWNERS.
addFile("0", "d0/f0", "f0@g\n");
addFile("1", "d1/d2/f1", "f1@g\ninclude ../../d0/f0\n");
addFile("2", "d2/f2", "f2@g\ninclude ../d0/f0\n");
addFile("3", "d2/d3/f3", "f3@g\ninclude /d0/f0\n");
addFile("4", "d4/f4", "f4@g\ninclude ../d2/f2\n");
addFile("5", "d4/d5/f5", "f5@g\ninclude /d2/f2\ninclude ../f4\n");
PushOneCommit.Result c =
addFile(
"6",
"d6/OWNERS",
"f6@g\ninclude /d0/f0\ninclude ../d1/d2/f1\n"
+ "include ../d2/f2\ninclude /d2/d3/f3\ninclude /d2/../d4/d5/f5\ninclude /d4/f4\n");
String result = getOwnersDebugResponse(c);
assertThat(result).contains("{./d6/OWNERS:[f0@g,f1@g,f2@g,f3@g,f4@g,f5@g,f6@g]}");
String projectName = project.get();
String skipLog = "parseLine:useSaved:include:" + projectName + ":";
for (String path : new String[] {"../../d0/f0", "../d0/f0", "../d2/f2", "/d2/f2", "/d4/f4"}) {
assertThat(result).contains(skipLog + path);
}
String expectedInLog =
concat("project:", projectName, ",")
+ "ownersFileName:OWNERS,"
+ "getBranchId:refs/heads/master(FOUND),"
+ "findOwnersFileFor:./d6/OWNERS,"
+ "findOwnersFileIn:./d6,"
+ getRepoFileLog(projectName + ":refs/heads/master:./d6/OWNERS", "d6/OWNERS:(...)")
+ concat("parseLine:include:", projectName, ":/d0/f0,")
+ getRepoFileLog(projectName + ":refs/heads/master:d0/f0", "d0/f0:(...)")
+ concat("parseLine:include:", projectName, ":../d1/d2/f1,")
+ getRepoFileLog(projectName + ":refs/heads/master:d1/d2/f1", "d1/d2/f1:(...)")
+ concat("parseLine:useSaved:include:", projectName, ":../../d0/f0,")
+ concat("parseLine:include:", projectName, ":../d2/f2,")
+ getRepoFileLog(projectName + ":refs/heads/master:d2/f2", "d2/f2:(...)")
+ concat("parseLine:useSaved:include:", projectName, ":../d0/f0,")
+ concat("parseLine:include:", projectName, ":/d2/d3/f3,")
+ getRepoFileLog(projectName + ":refs/heads/master:d2/d3/f3", "d2/d3/f3:(...)")
+ concat("parseLine:useSaved:include:", projectName, ":/d0/f0,")
+ concat("parseLine:include:", projectName, ":/d2/../d4/d5/f5,")
+ getRepoFileLog(projectName + ":refs/heads/master:d4/d5/f5", "d4/d5/f5:(...)")
+ concat("parseLine:useSaved:include:", projectName, ":/d2/f2,")
+ concat("parseLine:include:", projectName, ":../f4,")
+ getRepoFileLog(projectName + ":refs/heads/master:d4/f4", "d4/f4:(...)")
+ concat("parseLine:useSaved:include:", projectName, ":../d2/f2,")
+ concat("parseLine:useSaved:include:", projectName, ":/d4/f4,")
+ "findOwnersFileIn:.,"
+ getRepoFileLog(projectName + ":refs/heads/master:./OWNERS", "OWNERS(NOTFOUND)")
+ "countNumOwners,"
+ "findOwners,"
+ "checkFile:./d6/OWNERS,"
+ "checkDir:./d6,"
+ "checkDir:.,"
+ "addOwnerWeightsIn:./d6/"
+ "]";
assertThat(result).contains(expectedInLog);
}
@Test
public void includeProjectOwnerTest() throws Exception {
// Test include directive with other project name.
Project.NameKey pA = newProject("PA");
Project.NameKey pB = newProject("PB");
String nameA = pA.get();
String nameB = pB.get();
switchProject(pA);
addFile("1", "f1", "pAf1@g\ninclude ./d1/f1\n");
addFile("2", "d1/f1", "pAd1f1@g\ninclude " + nameB + ":" + "/d2/f2\n");
addFile("3", "d2/OWNERS", "pAd2@g\n include " + nameA + " : " + "../f1\n");
addFile("4", "OWNERS", "pA@g\n");
switchProject(pB);
addFile("5", "f1", "pBf1@g\ninclude ./d1/f1\n");
addFile("6", "f2", "pBf2@g\n");
addFile("7", "d1/f1", "pBd1f1@g\n");
addFile("8", "d2/f2", "pBd2f2@g\ninclude ../f1\n");
switchProject(pA);
PushOneCommit.Result c1 = createChange("c1", "d2/t.c", "Hello!");
// included: pA:d2/OWNERS, pA:d2/../f1, pA:d1/f1, pB:d2/f2, pB:d2/../f1, pB:./d1/f1
// inherited: pA:OWNERS
String owners =
"owners:["
+ concat(ownerJson("pAd1f1@g"), ",")
+ concat(ownerJson("pAd2@g"), ",")
+ concat(ownerJson("pAf1@g"), ",")
+ concat(ownerJson("pBd1f1@g"), ",")
+ concat(ownerJson("pBd2f2@g"), ",")
+ concat(ownerJson("pBf1@g"), ",")
+ concat(ownerJson("pA@g", 0, 1, 0), "]");
assertThat(getOwnersResponse(c1)).contains(owners);
}
@Test
public void includeProjectOwnerACLTest() throws Exception {
// Test include directive with other unreadable project.
Project.NameKey pA = newProject("PA2");
Project.NameKey pB = newProject("PB2");
String nameA = pA.get();
String nameB = pB.get();
switchProject(pA);
addFile("1", "f1", "pAf1@g\ninclude ./d1/f1\n");
addFile("2", "d1/f1", "pAd1f1@g\ninclude " + nameB + ":" + "/d2/f2\n");
addFile("3", "d2/OWNERS", "pAd2@g\n include " + nameA + " : " + "../f1\n");
addFile("4", "OWNERS", "pA@g\n");
switchProject(pB);
addFile("5", "f1", "pBf1@g\ninclude ./d1/f1\n");
addFile("6", "f2", "pBf2@g\n");
addFile("7", "d1/f1", "pBd1f1@g\n");
addFile("8", "d2/f2", "pBd2f2@g\ninclude ../f1\n");
blockRead(project); // now cannot read pB
switchProject(pA);
PushOneCommit.Result c1 = createChange("c1", "d2/t.c", "Hello!");
// included: pA:d2/OWNERS, pA:d2/../f1, pA:d1/f1
// inherited: pA:OWNERS
// pB's OWNERS files are not readable
String owners =
"owners:["
+ concat(ownerJson("pAd1f1@g"), ",")
+ concat(ownerJson("pAd2@g"), ",")
+ concat(ownerJson("pAf1@g"), ",")
+ concat(ownerJson("pA@g", 0, 1, 0), "]");
// The "owners:[...]" substring contains only owners from pA.
assertThat(getOwnersResponse(c1)).contains(owners);
}
}