Merge "Move dependency of gr-comment, which was forgotten"
diff --git a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 8c64aa9..b435bcc 100644
--- a/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -38,6 +38,7 @@
 import com.google.common.primitives.Chars;
 import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
 import com.google.gerrit.acceptance.testsuite.account.TestSshKeys;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GroupDescription;
@@ -291,6 +292,7 @@
   @Inject private AccountIndexer accountIndexer;
   @Inject private Groups groups;
   @Inject private GroupIndexer groupIndexer;
+  @Inject private ProjectOperations projectOperations;
 
   private ProjectResetter resetter;
   private List<Repository> toClose;
diff --git a/java/com/google/gerrit/acceptance/BUILD b/java/com/google/gerrit/acceptance/BUILD
index 861372d..b0a39cf 100644
--- a/java/com/google/gerrit/acceptance/BUILD
+++ b/java/com/google/gerrit/acceptance/BUILD
@@ -115,6 +115,7 @@
         "//lib:gwtorm",
         "//lib:jsch",
         "//lib:servlet-api-3_1",
+        "//lib/commons:lang",
         "//lib/greenmail",
         "//lib/guice",
         "//lib/guice:guice-assistedinject",
diff --git a/java/com/google/gerrit/acceptance/GerritServer.java b/java/com/google/gerrit/acceptance/GerritServer.java
index be8932e..063b298 100644
--- a/java/com/google/gerrit/acceptance/GerritServer.java
+++ b/java/com/google/gerrit/acceptance/GerritServer.java
@@ -28,6 +28,8 @@
 import com.google.gerrit.acceptance.testsuite.account.AccountOperationsImpl;
 import com.google.gerrit.acceptance.testsuite.group.GroupOperations;
 import com.google.gerrit.acceptance.testsuite.group.GroupOperationsImpl;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperationsImpl;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.config.FactoryModule;
 import com.google.gerrit.lucene.LuceneIndexModule;
@@ -498,6 +500,7 @@
             bind(AccountCreator.class);
             bind(AccountOperations.class).to(AccountOperationsImpl.class);
             bind(GroupOperations.class).to(GroupOperationsImpl.class);
+            bind(ProjectOperations.class).to(ProjectOperationsImpl.class);
             factory(PushOneCommit.Factory.class);
             install(InProcessProtocol.module());
             install(new NoSshModule());
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperations.java b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperations.java
new file mode 100644
index 0000000..16dc6c6
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperations.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2018 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;
+
+/**
+ * Operations for constructing projects in tests. This does not necessarily use the project REST
+ * API, so don't use it for testing that.
+ */
+public interface ProjectOperations {
+
+  /** Starts a fluent chain for creating a new project. */
+  TestProjectCreation.Builder newProject();
+}
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
new file mode 100644
index 0000000..6bf4114
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImpl.java
@@ -0,0 +1,57 @@
+// Copyright (C) 2018 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.gerrit.acceptance.testsuite.project.TestProjectCreation.Builder;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.ProjectOwnerGroupsProvider;
+import com.google.gerrit.server.project.CreateProjectArgs;
+import com.google.gerrit.server.project.ProjectCreator;
+import java.util.ArrayList;
+import java.util.Collections;
+import javax.inject.Inject;
+import org.apache.commons.lang.RandomStringUtils;
+import org.eclipse.jgit.lib.Constants;
+
+public class ProjectOperationsImpl implements ProjectOperations {
+  private final ProjectCreator projectCreator;
+  private final ProjectOwnerGroupsProvider.Factory projectOwnerGroups;
+
+  @Inject
+  ProjectOperationsImpl(
+      ProjectOwnerGroupsProvider.Factory projectOwnerGroups, ProjectCreator projectCreator) {
+    this.projectCreator = projectCreator;
+    this.projectOwnerGroups = projectOwnerGroups;
+  }
+
+  @Override
+  public Builder newProject() {
+    return TestProjectCreation.builder(this::createNewProject);
+  }
+
+  private Project.NameKey createNewProject(TestProjectCreation projectCreation) throws Exception {
+    String name = projectCreation.name().orElse(RandomStringUtils.randomAlphabetic(8));
+
+    CreateProjectArgs args = new CreateProjectArgs();
+    args.setProjectName(name);
+    args.branch = Collections.singletonList(Constants.R_HEADS + Constants.MASTER);
+    args.createEmptyCommit = projectCreation.createEmptyCommit().orElse(true);
+    projectCreation.parent().ifPresent(p -> args.newParent = p);
+    args.ownerIds = new ArrayList<>(projectOwnerGroups.create(args.getProject()).get());
+    projectCreation.submitType().ifPresent(st -> args.submitType = st);
+    projectCreator.createProject(args);
+    return new Project.NameKey(name);
+  }
+}
diff --git a/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java
new file mode 100644
index 0000000..aef2625
--- /dev/null
+++ b/java/com/google/gerrit/acceptance/testsuite/project/TestProjectCreation.java
@@ -0,0 +1,77 @@
+// Copyright (C) 2018 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.gerrit.acceptance.testsuite.ThrowingFunction;
+import com.google.gerrit.extensions.client.SubmitType;
+import com.google.gerrit.reviewdb.client.Project;
+import java.util.Optional;
+
+@AutoValue
+public abstract class TestProjectCreation {
+
+  public abstract Optional<String> name();
+
+  public abstract Optional<Project.NameKey> parent();
+
+  public abstract Optional<Boolean> createEmptyCommit();
+
+  public abstract Optional<SubmitType> submitType();
+
+  abstract ThrowingFunction<TestProjectCreation, Project.NameKey> projectCreator();
+
+  public static Builder builder(
+      ThrowingFunction<TestProjectCreation, Project.NameKey> projectCreator) {
+    return new AutoValue_TestProjectCreation.Builder().projectCreator(projectCreator);
+  }
+
+  @AutoValue.Builder
+  public abstract static class Builder {
+    public abstract TestProjectCreation.Builder name(String name);
+
+    public abstract TestProjectCreation.Builder parent(Project.NameKey parent);
+
+    public abstract TestProjectCreation.Builder submitType(SubmitType submitType);
+
+    public abstract TestProjectCreation.Builder createEmptyCommit(boolean value);
+
+    /**
+     * Creates empty commit on creation. This is necessary for the project's branches to be born.
+     */
+    public TestProjectCreation.Builder withEmptyCommit() {
+      return createEmptyCommit(true);
+    }
+
+    public TestProjectCreation.Builder noEmptyCommit() {
+      return createEmptyCommit(false);
+    }
+
+    abstract TestProjectCreation.Builder projectCreator(
+        ThrowingFunction<TestProjectCreation, Project.NameKey> projectCreator);
+
+    abstract TestProjectCreation autoBuild();
+
+    /**
+     * Executes the project creation as specified.
+     *
+     * @return the name of the created project
+     */
+    public Project.NameKey create() throws Exception {
+      TestProjectCreation creation = autoBuild();
+      return creation.projectCreator().apply(creation);
+    }
+  }
+}
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java b/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
index f306932..2633569 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
@@ -121,11 +121,6 @@
     return cloneProject(createProjectForPush(name, parent, createEmptyCommit, submitType));
   }
 
-  protected TestRepository<?> createProjectWithPush(String name, boolean createEmptyCommit)
-      throws Exception {
-    return cloneProject(createProjectForPush(name, null, createEmptyCommit, getSubmitType()));
-  }
-
   private static AtomicInteger contentCounter = new AtomicInteger(0);
   protected TestRepository<?> superRepo;
   protected Project.NameKey superKey;
@@ -198,13 +193,6 @@
     return Iterables.getLast(res).getRemoteUpdate(remoteBranch).getNewObjectId();
   }
 
-  protected void allowSubmoduleSubscription(
-      String submodule, String subBranch, String superproject, String superBranch, boolean match)
-      throws Exception {
-    allowSubmoduleSubscription(
-        nameKey(submodule), subBranch, nameKey(superproject), superBranch, match);
-  }
-
   protected void allowMatchingSubmoduleSubscription(
       Project.NameKey submodule, String subBranch, Project.NameKey superproject, String superBranch)
       throws Exception {
@@ -246,18 +234,6 @@
     }
   }
 
-  protected void allowMatchingSubmoduleSubscription(
-      String submodule, String subBranch, String superproject, String superBranch)
-      throws Exception {
-    allowSubmoduleSubscription(submodule, subBranch, superproject, superBranch, true);
-  }
-
-  protected void createSubmoduleSubscription(
-      TestRepository<?> repo, String branch, String subscribeToRepo, String subscribeToBranch)
-      throws Exception {
-    createSubmoduleSubscription(repo, branch, nameKey(subscribeToRepo), subscribeToBranch);
-  }
-
   protected void createSubmoduleSubscription(
       TestRepository<?> repo,
       String branch,
@@ -273,7 +249,7 @@
       TestRepository<?> repo,
       String branch,
       String subscribeToRepoPrefix,
-      String subscribeToRepo,
+      Project.NameKey subscribeToRepo,
       String subscribeToBranch)
       throws Exception {
     Config config = new Config();
@@ -285,15 +261,6 @@
   protected void prepareRelativeSubmoduleConfigEntry(
       Config config,
       String subscribeToRepoPrefix,
-      String subscribeToRepo,
-      String subscribeToBranch) {
-    prepareRelativeSubmoduleConfigEntry(
-        config, subscribeToRepoPrefix, nameKey(subscribeToRepo), subscribeToBranch);
-  }
-
-  protected void prepareRelativeSubmoduleConfigEntry(
-      Config config,
-      String subscribeToRepoPrefix,
       Project.NameKey subscribeToRepo,
       String subscribeToBranch) {
     String url = subscribeToRepoPrefix + subscribeToRepo.get();
@@ -305,15 +272,6 @@
   }
 
   protected void prepareSubmoduleConfigEntry(
-      Config config, String subscribeToRepo, String subscribeToBranch) {
-    // The submodule subscription module checks for gerrit.canonicalWebUrl to
-    // detect if it's configured for automatic updates. It doesn't matter if
-    // it serves from that URL.
-    prepareSubmoduleConfigEntry(
-        config, nameKey(subscribeToRepo), nameKey(subscribeToRepo), subscribeToBranch);
-  }
-
-  protected void prepareSubmoduleConfigEntry(
       Config config, Project.NameKey subscribeToRepo, String subscribeToBranch) {
     // The submodule subscription module checks for gerrit.canonicalWebUrl to
     // detect if it's configured for automatic updates. It doesn't matter if
@@ -321,16 +279,6 @@
     prepareSubmoduleConfigEntry(config, subscribeToRepo, subscribeToRepo, subscribeToBranch);
   }
 
-  protected Project.NameKey nameKey(String s) {
-    return new Project.NameKey(name(s));
-  }
-
-  protected void prepareSubmoduleConfigEntry(
-      Config config, String subscribeToRepo, String subscribeToRepoPath, String subscribeToBranch) {
-    prepareSubmoduleConfigEntry(
-        config, nameKey(subscribeToRepo), nameKey(subscribeToRepoPath), subscribeToBranch);
-  }
-
   protected void prepareSubmoduleConfigEntry(
       Config config,
       Project.NameKey subscribeToRepo,
@@ -367,16 +315,6 @@
   protected void expectToHaveSubmoduleState(
       TestRepository<?> repo,
       String branch,
-      String submodule,
-      TestRepository<?> subRepo,
-      String subBranch)
-      throws Exception {
-    expectToHaveSubmoduleState(repo, branch, nameKey(submodule), subRepo, subBranch);
-  }
-
-  protected void expectToHaveSubmoduleState(
-      TestRepository<?> repo,
-      String branch,
       Project.NameKey submodule,
       TestRepository<?> subRepo,
       String subBranch)
@@ -410,12 +348,6 @@
   }
 
   protected void expectToHaveSubmoduleState(
-      TestRepository<?> repo, String branch, String submodule, ObjectId expectedId)
-      throws Exception {
-    expectToHaveSubmoduleState(repo, branch, nameKey(submodule), expectedId);
-  }
-
-  protected void expectToHaveSubmoduleState(
       TestRepository<?> repo, String branch, Project.NameKey submodule, ObjectId expectedId)
       throws Exception {
     ObjectId commitId =
@@ -490,11 +422,6 @@
     assertThat(actualId).isEqualTo(expectedId);
   }
 
-  protected boolean hasSubmodule(TestRepository<?> repo, String branch, String submodule)
-      throws Exception {
-    return hasSubmodule(repo, branch, new Project.NameKey(name(submodule)));
-  }
-
   protected boolean hasSubmodule(TestRepository<?> repo, String branch, Project.NameKey submodule)
       throws Exception {
     Ref branchTip =
@@ -549,11 +476,6 @@
     return c.getAuthorIdent();
   }
 
-  protected void directUpdateSubmodule(String project, String refName, String path, AnyObjectId id)
-      throws Exception {
-    directUpdateSubmodule(nameKey(project), refName, nameKey(path), id);
-  }
-
   protected void directUpdateSubmodule(
       Project.NameKey project, String refName, Project.NameKey path, AnyObjectId id)
       throws Exception {
diff --git a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
index d76b310..94e3c0a 100644
--- a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
@@ -452,12 +452,11 @@
     allowMatchingSubmoduleSubscription(nest, "refs/heads/master", superKey, null);
 
     pushChangeTo(subRepo, "master");
-    createRelativeSubmoduleSubscription(
-        superRepo, "master", "../", "nested/subscribed-to-project", "master");
+    createRelativeSubmoduleSubscription(superRepo, "master", "../", nest, "master");
 
     ObjectId subHEAD = pushChangeTo(subRepo, "master");
 
-    expectToHaveSubmoduleState(superRepo, "master", "nested/subscribed-to-project", subHEAD);
+    expectToHaveSubmoduleState(superRepo, "master", nest, subHEAD);
   }
 
   @Test
@@ -617,22 +616,22 @@
     allowMatchingSubmoduleSubscription(subkey2, "refs/heads/master", superKey, "refs/heads/master");
 
     Config config = new Config();
-    prepareSubmoduleConfigEntry(config, "subscribed-to-project-1", "master");
-    prepareSubmoduleConfigEntry(config, "subscribed-to-project-2", "master");
+    prepareSubmoduleConfigEntry(config, subkey1, "master");
+    prepareSubmoduleConfigEntry(config, subkey2, "master");
     pushSubmoduleConfig(superRepo, "master", config);
 
     // Push once to initialize submodules.
     ObjectId subTip2 = pushChangeTo(subRepo2, "master");
     ObjectId subTip1 = pushChangeTo(subRepo1, "master");
 
-    expectToHaveSubmoduleState(superRepo, "master", "subscribed-to-project-1", subTip1);
-    expectToHaveSubmoduleState(superRepo, "master", "subscribed-to-project-2", subTip2);
+    expectToHaveSubmoduleState(superRepo, "master", subkey1, subTip1);
+    expectToHaveSubmoduleState(superRepo, "master", subkey2, subTip2);
 
-    directUpdateRef("subscribed-to-project-2", "refs/heads/master");
+    directUpdateRef(subkey2, "refs/heads/master");
     subTip1 = pushChangeTo(subRepo1, "master");
 
-    expectToHaveSubmoduleState(superRepo, "master", "subscribed-to-project-1", subTip1);
-    expectToHaveSubmoduleState(superRepo, "master", "subscribed-to-project-2", subTip2);
+    expectToHaveSubmoduleState(superRepo, "master", subkey1, subTip1);
+    expectToHaveSubmoduleState(superRepo, "master", subkey2, subTip2);
   }
 
   @Test
@@ -655,8 +654,8 @@
     expectToHaveSubmoduleState(superRepo, "master", subKey, badId);
   }
 
-  private ObjectId directUpdateRef(String project, String ref) throws Exception {
-    try (Repository serverRepo = repoManager.openRepository(nameKey(project))) {
+  private ObjectId directUpdateRef(Project.NameKey project, String ref) throws Exception {
+    try (Repository serverRepo = repoManager.openRepository(project)) {
       return new TestRepository<>(serverRepo).branch(ref).commit().create().copy();
     }
   }
diff --git a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
index 0b583bf..96b545c 100644
--- a/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
+++ b/javatests/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
@@ -616,9 +616,9 @@
 
     approve(changeId);
     exception.expectMessage("Branch level circular subscriptions detected");
-    exception.expectMessage("top-project,refs/heads/master");
-    exception.expectMessage("mid-project,refs/heads/master");
-    exception.expectMessage("bottom-project,refs/heads/master");
+    exception.expectMessage(topKey.get() + ",refs/heads/master");
+    exception.expectMessage(midKey.get() + ",refs/heads/master");
+    exception.expectMessage(botKey.get() + ",refs/heads/master");
     return changeId;
   }
 
@@ -661,8 +661,11 @@
 
   @Test
   public void projectNoSubscriptionWholeTopic() throws Exception {
-    TestRepository<?> repoA = createProjectWithPush("project-a", null, true, getSubmitType());
-    TestRepository<?> repoB = createProjectWithPush("project-b", null, true, getSubmitType());
+    Project.NameKey keyA = createProjectForPush("project-a", null, true, getSubmitType());
+    Project.NameKey keyB = createProjectForPush("project-b", null, true, getSubmitType());
+
+    TestRepository<?> repoA = cloneProject(keyA);
+    TestRepository<?> repoB = cloneProject(keyB);
     // bootstrap the dev branch
     ObjectId a0 = pushChangeTo(repoA, "dev");
 
@@ -717,33 +720,33 @@
     approve(getChangeId(repoB, bDevHead).get());
 
     gApi.changes().id(getChangeId(repoA, aDevHead).get()).current().submit();
-    assertThat(getRemoteHead(name("project-a"), "refs/heads/master").getShortMessage())
+    assertThat(getRemoteHead(keyA, "refs/heads/master").getShortMessage())
         .contains("some message in a master.txt");
-    assertThat(getRemoteHead(name("project-a"), "refs/heads/dev").getShortMessage())
+    assertThat(getRemoteHead(keyA, "refs/heads/dev").getShortMessage())
         .contains("some message in a dev.txt");
-    assertThat(getRemoteHead(name("project-b"), "refs/heads/master").getShortMessage())
+    assertThat(getRemoteHead(keyB, "refs/heads/master").getShortMessage())
         .contains("some message in b master.txt");
-    assertThat(getRemoteHead(name("project-b"), "refs/heads/dev").getShortMessage())
+    assertThat(getRemoteHead(keyB, "refs/heads/dev").getShortMessage())
         .contains("some message in b dev.txt");
   }
 
   @Test
   public void twoProjectsMultipleBranchesWholeTopic() throws Exception {
-    TestRepository<?> repoA = createProjectWithPush("project-a", null, true, getSubmitType());
-    TestRepository<?> repoB = createProjectWithPush("project-b", null, true, getSubmitType());
+    Project.NameKey keyA = createProjectForPush("project-a", null, true, getSubmitType());
+    Project.NameKey keyB = createProjectForPush("project-b", null, true, getSubmitType());
+    TestRepository<?> repoA = cloneProject(keyA);
+    TestRepository<?> repoB = cloneProject(keyB);
     // bootstrap the dev branch
     pushChangeTo(repoA, "dev");
 
     // bootstrap the dev branch
     ObjectId b0 = pushChangeTo(repoB, "dev");
 
-    allowMatchingSubmoduleSubscription(
-        "project-b", "refs/heads/master", "project-a", "refs/heads/master");
-    allowMatchingSubmoduleSubscription(
-        "project-b", "refs/heads/dev", "project-a", "refs/heads/dev");
+    allowMatchingSubmoduleSubscription(keyB, "refs/heads/master", keyA, "refs/heads/master");
+    allowMatchingSubmoduleSubscription(keyB, "refs/heads/dev", keyA, "refs/heads/dev");
 
-    createSubmoduleSubscription(repoA, "master", "project-b", "master");
-    createSubmoduleSubscription(repoA, "dev", "project-b", "dev");
+    createSubmoduleSubscription(repoA, "master", keyB, "master");
+    createSubmoduleSubscription(repoA, "dev", keyB, "dev");
 
     // create a change for master branch in repo b
     ObjectId bHead =
@@ -770,8 +773,8 @@
     approve(getChangeId(repoB, bDevHead).get());
     gApi.changes().id(getChangeId(repoB, bHead).get()).current().submit();
 
-    expectToHaveSubmoduleState(repoA, "master", "project-b", repoB, "master");
-    expectToHaveSubmoduleState(repoA, "dev", "project-b", repoB, "dev");
+    expectToHaveSubmoduleState(repoA, "master", keyB, repoB, "master");
+    expectToHaveSubmoduleState(repoA, "dev", keyB, repoB, "dev");
   }
 
   @Test
@@ -876,4 +879,8 @@
     expectToHaveSubmoduleState(superRepo, "master", subKey1, badId);
     expectToHaveSubmoduleState(superRepo, "master", subKey2, sub2, "master");
   }
+
+  private Project.NameKey nameKey(String s) {
+    return new Project.NameKey(name(s));
+  }
 }
diff --git a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
index a64305c..6ffc179 100644
--- a/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/project/AccessIT.java
@@ -21,6 +21,7 @@
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.GitUtil;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.Permission;
@@ -63,8 +64,6 @@
 
 public class AccessIT extends AbstractDaemonTest {
 
-  private static final String PROJECT_NAME = "newProject";
-
   private static final String REFS_ALL = Constants.R_REFS + "*";
   private static final String REFS_HEADS = Constants.R_HEADS + "*";
 
@@ -73,10 +72,11 @@
   private Project.NameKey newProjectName;
 
   @Inject private DynamicSet<FileHistoryWebLink> fileHistoryWebLinkDynamicSet;
+  @Inject protected ProjectOperations projectOperations;
 
   @Before
   public void setUp() throws Exception {
-    newProjectName = createProject(PROJECT_NAME);
+    newProjectName = projectOperations.newProject().withEmptyCommit().create();
   }
 
   @Test
@@ -412,7 +412,7 @@
   @Test
   public void updateParentAsUser() throws Exception {
     // Create child
-    String newParentProjectName = createProject(PROJECT_NAME + "PA").get();
+    String newParentProjectName = projectOperations.newProject().create().get();
 
     // Set new parent
     ProjectAccessInput accessInput = newProjectAccessInput();
@@ -427,7 +427,7 @@
   @Test
   public void updateParentAsAdministrator() throws Exception {
     // Create parent
-    String newParentProjectName = createProject(PROJECT_NAME + "PA").get();
+    String newParentProjectName = projectOperations.newProject().create().get();
 
     // Set new parent
     ProjectAccessInput accessInput = newProjectAccessInput();
diff --git a/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java b/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
new file mode 100644
index 0000000..6dc69be
--- /dev/null
+++ b/javatests/com/google/gerrit/acceptance/testsuite/project/ProjectOperationsImplTest.java
@@ -0,0 +1,56 @@
+// Copyright (C) 2018 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 static com.google.common.truth.Truth.assertThat;
+import static java.util.stream.Collectors.toList;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.extensions.api.projects.BranchInfo;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.inject.Inject;
+import java.util.List;
+import org.junit.Test;
+
+public class ProjectOperationsImplTest extends AbstractDaemonTest {
+
+  @Inject private ProjectOperations projectOperations;
+
+  @Test
+  public void defaultName() throws Exception {
+    Project.NameKey name = projectOperations.newProject().create();
+    // check that the project was created (throws exception if not found.)
+    gApi.projects().name(name.get());
+    Project.NameKey name2 = projectOperations.newProject().create();
+    assertThat(name2).isNotEqualTo(name);
+  }
+
+  @Test
+  public void specifiedName() throws Exception {
+    String name = "somename";
+    Project.NameKey key = projectOperations.newProject().name(name).create();
+    assertThat(key.get()).isEqualTo(name);
+  }
+
+  @Test
+  public void emptyCommit() throws Exception {
+    Project.NameKey key = projectOperations.newProject().withEmptyCommit().create();
+    List<BranchInfo> branches = gApi.projects().name(key.get()).branches().get();
+    assertThat(branches).isNotEmpty();
+    assertThat(branches.stream().map(x -> x.ref).collect(toList()))
+        .isEqualTo(ImmutableList.of("HEAD", "refs/meta/config", "refs/heads/master"));
+  }
+}