Convert unit tests to integration tests.

Change-Id: I26f848ec0903f230afa99fba5cf4e3437741a0da
diff --git a/src/test/java/com/googlesource/gerrit/plugins/automerger/ConfigLoaderIT.java b/src/test/java/com/googlesource/gerrit/plugins/automerger/ConfigLoaderIT.java
new file mode 100644
index 0000000..86ae78b
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/automerger/ConfigLoaderIT.java
@@ -0,0 +1,213 @@
+// Copyright (C) 2017 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.automerger;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.CharStreams;
+import com.google.gerrit.acceptance.GitUtil;
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.inject.Inject;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.HashSet;
+import java.util.Set;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
+import org.junit.Test;
+
+@TestPlugin(
+  name = "automerger",
+  sysModule = "com.googlesource.gerrit.plugins.automerger.AutomergerModule"
+)
+public class ConfigLoaderIT extends LightweightPluginDaemonTest {
+  private ConfigLoader configLoader;
+  @Inject private AllProjectsName allProjectsName;
+  @Inject private PluginConfigFactory cfgFactory;
+  private Project.NameKey manifestNameKey;
+
+  @Test
+  public void getProjectsInScopeTest_addProjects() throws Exception {
+    defaultSetup("automerger.config");
+    Set<String> expectedProjects = new HashSet<String>();
+    expectedProjects.add("platform/whee");
+    expectedProjects.add("platform/added/project");
+    assertThat(configLoader.getProjectsInScope("master", "ds_one")).isEqualTo(expectedProjects);
+  }
+
+  @Test
+  public void getProjectsInScopeTest_setProjects() throws Exception {
+    defaultSetup("automerger.config");
+    Set<String> otherExpectedProjects = new HashSet<String>();
+    otherExpectedProjects.add("platform/some/project");
+    otherExpectedProjects.add("platform/other/project");
+    assertThat(configLoader.getProjectsInScope("master", "ds_two"))
+        .isEqualTo(otherExpectedProjects);
+  }
+
+  @Test
+  public void getProjectsInScope_missingSourceManifest() throws Exception {
+    createProject("All-Projects");
+    manifestNameKey = createProject("platform/manifest");
+    setupTestRepo("ds_one.xml", manifestNameKey, "ds_one", "default.xml");
+    setupTestRepo("ds_two.xml", manifestNameKey, "ds_two", "default.xml");
+    loadConfig("alternate.config");
+    assertThat(configLoader.getProjectsInScope("master", "ds_one").isEmpty()).isTrue();
+  }
+
+  @Test
+  public void getProjectsInScope_ignoreSourceManifest() throws Exception {
+    defaultSetup("alternate.config");
+    Set<String> expectedProjects = new HashSet<String>();
+    expectedProjects.add("platform/whee");
+    expectedProjects.add("whuu");
+    assertThat(configLoader.getProjectsInScope("master", "ds_two")).isEqualTo(expectedProjects);
+  }
+
+  @Test
+  public void getProjectsInScope_ignoreSourceManifestWithMissingDestManifest() throws Exception {
+    defaultSetup("alternate.config");
+    assertThat(configLoader.getProjectsInScope("master", "ds_four").isEmpty()).isTrue();
+  }
+
+  @Test
+  public void isSkipMergeTest_noSkip() throws Exception {
+    defaultSetup("automerger.config");
+    assertThat(configLoader.isSkipMerge("ds_two", "ds_three", "bla")).isFalse();
+  }
+
+  @Test
+  public void isSkipMergeTest_blankMerge() throws Exception {
+    defaultSetup("automerger.config");
+    assertThat(configLoader.isSkipMerge("ds_two", "ds_three", "test test \n \n DO NOT MERGE lala"))
+        .isTrue();
+  }
+
+  @Test
+  public void isSkipMergeTest_blankMergeWithMergeAll() throws Exception {
+    defaultSetup("automerger.config");
+    assertThat(configLoader.isSkipMerge("master", "ds_two", "test test \n \n DO NOT MERGE"))
+        .isFalse();
+  }
+
+  @Test
+  public void isSkipMergeTest_alwaysBlankMerge() throws Exception {
+    defaultSetup("automerger.config");
+    assertThat(
+            configLoader.isSkipMerge("master", "ds_one", "test test \n \n DO NOT MERGE ANYWHERE"))
+        .isTrue();
+  }
+
+  @Test
+  public void isSkipMergeTest_alwaysBlankMergeDummy() throws Exception {
+    defaultSetup("alternate.config");
+    assertThat(configLoader.isSkipMerge("master", "ds_two", "test test")).isFalse();
+  }
+
+  @Test
+  public void isSkipMergeTest_alwaysBlankMergeNull() throws Exception {
+    defaultSetup("alternate.config");
+    assertThat(configLoader.isSkipMerge("master", "ds_two", "test test \n \n BLANK ANYWHERE"))
+        .isTrue();
+  }
+
+  @Test
+  public void isSkipMergeTest_noBlankMergeSpecified() throws Exception {
+    defaultSetup("empty_blank.config");
+    assertThat(configLoader.isSkipMerge("master", "ds_one", "test test \n \n DO NOT MERGE"))
+        .isFalse();
+  }
+
+  @Test
+  public void downstreamBranchesTest() throws Exception {
+    defaultSetup("automerger.config");
+    Set<String> expectedBranches = new HashSet<String>();
+    expectedBranches.add("ds_two");
+    assertThat(configLoader.getDownstreamBranches("master", "platform/some/project"))
+        .isEqualTo(expectedBranches);
+  }
+
+  @Test
+  public void downstreamBranchesTest_nonexistentBranch() throws Exception {
+    defaultSetup("automerger.config");
+    Set<String> expectedBranches = new HashSet<String>();
+    assertThat(configLoader.getDownstreamBranches("idontexist", "platform/some/project"))
+        .isEqualTo(expectedBranches);
+  }
+
+  @Test
+  public void downstreamBranchesTest_configException() throws Exception {
+    defaultSetup("wrong.config");
+
+    exception.expect(ConfigInvalidException.class);
+    exception.expectMessage("Automerger config branch pair malformed: master..ds_one");
+    configLoader.getDownstreamBranches("master", "platform/some/project");
+  }
+
+  private void defaultSetup(String resourceName) throws Exception {
+    createProject("All-Projects");
+    manifestNameKey = createProject("platform/manifest");
+    setupTestRepo("default.xml", manifestNameKey, "master", "default.xml");
+    setupTestRepo("ds_one.xml", manifestNameKey, "ds_one", "default.xml");
+    setupTestRepo("ds_two.xml", manifestNameKey, "ds_two", "default.xml");
+    loadConfig(resourceName);
+  }
+
+  private void setupTestRepo(
+      String resourceName, Project.NameKey projectNameKey, String branchName, String filename)
+      throws Exception {
+    TestRepository<InMemoryRepository> repo = cloneProject(projectNameKey, admin);
+    try (InputStream in = getClass().getResourceAsStream(resourceName)) {
+      String resourceString = CharStreams.toString(new InputStreamReader(in, Charsets.UTF_8));
+
+      PushOneCommit push =
+          pushFactory.create(db, admin.getIdent(), repo, "some subject", filename, resourceString);
+      push.to("refs/heads/" + branchName).assertOkStatus();
+    }
+  }
+
+  private void pushConfig(String resourceName) throws Exception {
+    TestRepository<InMemoryRepository> allProjectRepo = cloneProject(allProjects, admin);
+    GitUtil.fetch(allProjectRepo, RefNames.REFS_CONFIG + ":config");
+    allProjectRepo.reset("config");
+    try (InputStream in = getClass().getResourceAsStream(resourceName)) {
+      String resourceString = CharStreams.toString(new InputStreamReader(in, Charsets.UTF_8));
+
+      Config cfg = new Config();
+      cfg.fromText(resourceString);
+      // Update manifest project path to the result of createProject(resourceName), since it is
+      // scoped to the test method
+      cfg.setString("global", null, "manifestProject", manifestNameKey.get());
+      PushOneCommit push =
+          pushFactory.create(
+              db, admin.getIdent(), allProjectRepo, "Subject", "automerger.config", cfg.toText());
+      push.to("refs/meta/config").assertOkStatus();
+    }
+  }
+
+  private void loadConfig(String configFilename) throws Exception {
+    pushConfig(configFilename);
+    configLoader = new ConfigLoader(gApi, allProjectsName, "automerger", cfgFactory);
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/automerger/ConfigLoaderTest.java b/src/test/java/com/googlesource/gerrit/plugins/automerger/ConfigLoaderTest.java
deleted file mode 100644
index 56348db..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/automerger/ConfigLoaderTest.java
+++ /dev/null
@@ -1,210 +0,0 @@
-// Copyright (C) 2016 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.automerger;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.common.base.Charsets;
-import com.google.common.io.CharStreams;
-import com.google.gerrit.extensions.api.GerritApi;
-import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
-import com.google.gerrit.reviewdb.client.RefNames;
-import com.google.gerrit.server.config.AllProjectsName;
-import com.google.gerrit.server.config.PluginConfigFactory;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.HashSet;
-import java.util.Set;
-import org.eclipse.jgit.errors.ConfigInvalidException;
-import org.eclipse.jgit.lib.Config;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.runners.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class ConfigLoaderTest {
-  @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-  private GerritApi gApiMock;
-
-  private ConfigLoader configLoader;
-  private AllProjectsName allProjectsName;
-  @Mock private PluginConfigFactory cfgFactory;
-
-  @Rule public ExpectedException thrown = ExpectedException.none();
-
-  @Before
-  public void setUp() throws Exception {
-    allProjectsName = new AllProjectsName("All-Projects");
-    mockFile("automerger.config", allProjectsName.get(), RefNames.REFS_CONFIG, "automerger.config");
-    mockFile("default.xml", "platform/manifest", "master", "default.xml");
-    mockFile("ds_one.xml", "platform/manifest", "ds_one", "default.xml");
-    mockFile("ds_two.xml", "platform/manifest", "ds_two", "default.xml");
-  }
-
-  private void mockFile(String resourceName, String projectName, String branchName, String filename)
-      throws Exception {
-    try (InputStream in = getClass().getResourceAsStream(resourceName)) {
-      String resourceString = CharStreams.toString(new InputStreamReader(in, Charsets.UTF_8));
-      Mockito.when(
-              gApiMock.projects().name(projectName).branch(branchName).file(filename).asString())
-          .thenReturn(resourceString);
-    }
-  }
-
-  private void loadConfig() throws Exception {
-    Config cfg = new Config();
-    cfg.fromText(
-        gApiMock
-            .projects()
-            .name(allProjectsName.get())
-            .branch(RefNames.REFS_CONFIG)
-            .file("automerger.config")
-            .asString());
-    Mockito.when(cfgFactory.getProjectPluginConfig(allProjectsName, "automerger")).thenReturn(cfg);
-    configLoader = new ConfigLoader(gApiMock, allProjectsName, "automerger", cfgFactory);
-  }
-
-  @Test
-  public void getProjectsInScopeTest_addProjects() throws Exception {
-    loadConfig();
-    Set<String> expectedProjects = new HashSet<String>();
-    expectedProjects.add("platform/whee");
-    expectedProjects.add("platform/added/project");
-    assertThat(configLoader.getProjectsInScope("master", "ds_one")).isEqualTo(expectedProjects);
-  }
-
-  @Test
-  public void getProjectsInScopeTest_setProjects() throws Exception {
-    loadConfig();
-    Set<String> otherExpectedProjects = new HashSet<String>();
-    otherExpectedProjects.add("platform/some/project");
-    otherExpectedProjects.add("platform/other/project");
-    assertThat(configLoader.getProjectsInScope("master", "ds_two"))
-        .isEqualTo(otherExpectedProjects);
-  }
-
-  @Test
-  public void getProjectsInScope_missingSourceManifest() throws Exception {
-    mockFile("alternate.config", allProjectsName.get(), RefNames.REFS_CONFIG, "automerger.config");
-    Mockito.when(gApiMock.projects().name("platform/manifest").branch("master"))
-        .thenThrow(new ResourceNotFoundException());
-    loadConfig();
-    assertThat(configLoader.getProjectsInScope("master", "ds_one").isEmpty()).isTrue();
-  }
-
-  @Test
-  public void getProjectsInScope_ignoreSourceManifest() throws Exception {
-    mockFile("alternate.config", allProjectsName.get(), RefNames.REFS_CONFIG, "automerger.config");
-    loadConfig();
-    Set<String> expectedProjects = new HashSet<String>();
-    expectedProjects.add("platform/whee");
-    expectedProjects.add("whuu");
-    assertThat(configLoader.getProjectsInScope("master", "ds_two")).isEqualTo(expectedProjects);
-  }
-
-  @Test
-  public void getProjectsInScope_ignoreSourceManifestWithMissingDestManifest() throws Exception {
-    mockFile("alternate.config", allProjectsName.get(), RefNames.REFS_CONFIG, "automerger.config");
-    Mockito.when(gApiMock.projects().name("platform/manifest").branch("ds_four"))
-        .thenThrow(new ResourceNotFoundException());
-    loadConfig();
-    assertThat(configLoader.getProjectsInScope("master", "ds_four").isEmpty()).isTrue();
-  }
-
-  @Test
-  public void isSkipMergeTest_noSkip() throws Exception {
-    loadConfig();
-    assertThat(configLoader.isSkipMerge("ds_two", "ds_three", "bla")).isFalse();
-  }
-
-  @Test
-  public void isSkipMergeTest_blankMerge() throws Exception {
-    loadConfig();
-    assertThat(configLoader.isSkipMerge("ds_two", "ds_three", "test test \n \n DO NOT MERGE lala"))
-        .isTrue();
-  }
-
-  @Test
-  public void isSkipMergeTest_blankMergeWithMergeAll() throws Exception {
-    loadConfig();
-    assertThat(configLoader.isSkipMerge("master", "ds_two", "test test \n \n DO NOT MERGE"))
-        .isFalse();
-  }
-
-  @Test
-  public void isSkipMergeTest_alwaysBlankMerge() throws Exception {
-    loadConfig();
-    assertThat(
-            configLoader.isSkipMerge("master", "ds_one", "test test \n \n DO NOT MERGE ANYWHERE"))
-        .isTrue();
-  }
-
-  @Test
-  public void isSkipMergeTest_alwaysBlankMergeDummy() throws Exception {
-    mockFile("alternate.config", allProjectsName.get(), RefNames.REFS_CONFIG, "automerger.config");
-    loadConfig();
-    assertThat(configLoader.isSkipMerge("master", "ds_two", "test test")).isFalse();
-  }
-
-  @Test
-  public void isSkipMergeTest_alwaysBlankMergeNull() throws Exception {
-    mockFile("alternate.config", allProjectsName.get(), RefNames.REFS_CONFIG, "automerger.config");
-    loadConfig();
-    assertThat(configLoader.isSkipMerge("master", "ds_two", "test test \n \n BLANK ANYWHERE"))
-        .isTrue();
-  }
-
-  @Test
-  public void isSkipMergeTest_noBlankMergeSpecified() throws Exception {
-    mockFile(
-        "empty_blank.config", allProjectsName.get(), RefNames.REFS_CONFIG, "automerger.config");
-    loadConfig();
-    assertThat(configLoader.isSkipMerge("master", "ds_one", "test test \n \n DO NOT MERGE"))
-        .isFalse();
-  }
-
-  @Test
-  public void downstreamBranchesTest() throws Exception {
-    loadConfig();
-    Set<String> expectedBranches = new HashSet<String>();
-    expectedBranches.add("ds_two");
-    assertThat(configLoader.getDownstreamBranches("master", "platform/some/project"))
-        .isEqualTo(expectedBranches);
-  }
-
-  @Test
-  public void downstreamBranchesTest_nonexistentBranch() throws Exception {
-    loadConfig();
-    Set<String> expectedBranches = new HashSet<String>();
-    assertThat(configLoader.getDownstreamBranches("idontexist", "platform/some/project"))
-        .isEqualTo(expectedBranches);
-  }
-
-  @Test
-  public void downstreamBranchesTest_configException() throws Exception {
-    mockFile("wrong.config", allProjectsName.get(), RefNames.REFS_CONFIG, "automerger.config");
-    loadConfig();
-
-    thrown.expect(ConfigInvalidException.class);
-    thrown.expectMessage("Automerger config branch pair malformed: master..ds_one");
-    configLoader.getDownstreamBranches("master", "platform/some/project");
-  }
-}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/automerger/DownstreamCreatorIT.java b/src/test/java/com/googlesource/gerrit/plugins/automerger/DownstreamCreatorIT.java
new file mode 100644
index 0000000..4a7fc24
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/automerger/DownstreamCreatorIT.java
@@ -0,0 +1,215 @@
+// Copyright (C) 2017 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.automerger;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Charsets;
+import com.google.common.io.CharStreams;
+import com.google.gerrit.acceptance.GitUtil;
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.extensions.api.changes.ChangeApi;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.List;
+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.ObjectId;
+import org.junit.Test;
+
+@TestPlugin(
+  name = "automerger",
+  sysModule = "com.googlesource.gerrit.plugins.automerger.AutomergerModule"
+)
+public class DownstreamCreatorIT extends LightweightPluginDaemonTest {
+  @Test
+  public void testExpectedFlow() throws Exception {
+    Project.NameKey manifestNameKey = defaultSetup();
+    // Create initial change
+    PushOneCommit.Result result = createChange("subject", "filename", "content", "testtopic");
+    // Project name is scoped by test, so we need to get it from our initial change
+    String projectName = result.getChange().project().get();
+    createBranch(new Branch.NameKey(projectName, "ds_one"));
+    createBranch(new Branch.NameKey(projectName, "ds_two"));
+    pushConfig("automerger.config", manifestNameKey.get(), projectName);
+    // After we upload our config, we upload a new patchset to create the downstreams
+    amendChange(result.getChangeId());
+    result.assertOkStatus();
+    // Check that there are the correct number of changes in the topic
+    List<ChangeInfo> changesInTopic =
+        gApi.changes().query("topic: " + gApi.changes().id(result.getChangeId()).topic()).get();
+    assertThat(changesInTopic).hasSize(3);
+    // +2 and submit
+    merge(result);
+  }
+
+  @Test
+  public void testBlankMerge() throws Exception {
+    Project.NameKey manifestNameKey = defaultSetup();
+    // Create initial change
+    PushOneCommit.Result result =
+        createChange("DO NOT MERGE subject", "filename", "content", "testtopic");
+    // Project name is scoped by test, so we need to get it from our initial change
+    String projectName = result.getChange().project().get();
+    createBranch(new Branch.NameKey(projectName, "ds_one"));
+    createBranch(new Branch.NameKey(projectName, "ds_two"));
+    pushConfig("automerger.config", manifestNameKey.get(), projectName);
+    // After we upload our config, we upload a new patchset to create the downstreams
+    amendChange(result.getChangeId(), "DO NOT MERGE subject", "filename", "content");
+    result.assertOkStatus();
+
+    ChangeApi change = gApi.changes().id(result.getChangeId());
+    BinaryResult content = change.current().file("filename").content();
+
+    List<ChangeInfo> changesInTopic = gApi.changes().query("topic: " + change.topic()).get();
+    assertThat(changesInTopic).hasSize(3);
+    for (ChangeInfo c : changesInTopic) {
+      ChangeApi downstreamChange = gApi.changes().id(c._number);
+      // It should skip ds_one, since this is a DO NOT MERGE
+      if (c.branch.equals("ds_one")) {
+        assertThat(downstreamChange.get().subject).contains("skipped:");
+        assertThat(downstreamChange.current().files().keySet()).contains("filename");
+        assertThat(downstreamChange.current().files().get("filename").linesDeleted).isEqualTo(1);
+      } else if (c.branch.equals("ds_two")) {
+        // It should not skip ds_two, since it is marked with mergeAll: true
+        assertThat(downstreamChange.get().subject).doesNotContain("skipped:");
+        BinaryResult downstreamContent = downstreamChange.current().file("filename").content();
+        assertThat(downstreamContent.asString()).isEqualTo(content.asString());
+      } else {
+        assertThat(c.branch).isEqualTo("master");
+      }
+    }
+  }
+
+  @Test
+  public void testAlwaysBlankMerge() throws Exception {
+    Project.NameKey manifestNameKey = defaultSetup();
+    // Create initial change
+    PushOneCommit.Result result =
+        createChange("DO NOT MERGE ANYWHERE subject", "filename", "content", "testtopic");
+    // Project name is scoped by test, so we need to get it from our initial change
+    String projectName = result.getChange().project().get();
+    createBranch(new Branch.NameKey(projectName, "ds_one"));
+    createBranch(new Branch.NameKey(projectName, "ds_two"));
+    pushConfig("automerger.config", manifestNameKey.get(), projectName);
+    // After we upload our config, we upload a new patchset to create the downstreams
+    amendChange(result.getChangeId(), "DO NOT MERGE ANYWHERE subject", "filename", "content");
+    result.assertOkStatus();
+
+    ChangeApi change = gApi.changes().id(result.getChangeId());
+
+    List<ChangeInfo> changesInTopic = gApi.changes().query("topic: " + change.topic()).get();
+    assertThat(changesInTopic).hasSize(3);
+    for (ChangeInfo c : changesInTopic) {
+      ChangeApi downstreamChange = gApi.changes().id(c._number);
+      // It should skip ds_one and ds_two, since this is a DO NOT MERGE ANYWHERE
+      if (c.branch.equals("ds_one") || c.branch.equals("ds_two")) {
+        assertThat(downstreamChange.get().subject).contains("skipped:");
+        assertThat(downstreamChange.current().files().keySet()).contains("filename");
+        assertThat(downstreamChange.current().files().get("filename").linesDeleted).isEqualTo(1);
+      } else {
+        assertThat(c.branch).isEqualTo("master");
+      }
+    }
+  }
+
+  @Test
+  public void testDownstreamMergeConflict() throws Exception {
+    Project.NameKey manifestNameKey = defaultSetup();
+    // Create initial change
+    PushOneCommit.Result result = createChange("subject", "filename", "echo Hello");
+    // Project name is scoped by test, so we need to get it from our initial change
+    String projectName = result.getChange().project().get();
+    createBranch(new Branch.NameKey(projectName, "ds_one"));
+    createBranch(new Branch.NameKey(projectName, "ds_two"));
+    result.assertOkStatus();
+    merge(result);
+    // Reset to create a sibling
+    ObjectId initial = repo().exactRef("HEAD").getLeaf().getObjectId();
+    testRepo.reset(initial);
+    // Set up a merge conflict between master and ds_one
+    PushOneCommit.Result ds1Result =
+        createChange(
+            testRepo, "ds_one", "subject", "filename", "echo \"Hello asdfsd World\"", "randtopic");
+    ds1Result.assertOkStatus();
+    merge(ds1Result);
+    // Reset to allow our merge conflict to come
+    testRepo.reset(initial);
+    pushConfig("automerger.config", manifestNameKey.get(), projectName);
+    // After we upload our config, we upload a new change to create the downstreams
+    PushOneCommit.Result masterResult =
+        pushFactory
+            .create(db, admin.getIdent(), testRepo, "subject", "filename", "echo 'Hello World!'")
+            .to("refs/for/master");
+    masterResult.assertOkStatus();
+    // Since there's a conflict with ds_one, there should only be two changes in the topic
+    List<ChangeInfo> changesInTopic =
+        gApi.changes()
+            .query("topic: " + gApi.changes().id(masterResult.getChangeId()).topic())
+            .get();
+    assertThat(changesInTopic).hasSize(2);
+  }
+
+  private Project.NameKey defaultSetup() throws Exception {
+    Project.NameKey manifestNameKey = createProject("platform/manifest");
+    setupTestRepo("default.xml", manifestNameKey, "master", "default.xml");
+    setupTestRepo("ds_one.xml", manifestNameKey, "ds_one", "default.xml");
+    setupTestRepo("ds_two.xml", manifestNameKey, "ds_two", "default.xml");
+    return manifestNameKey;
+  }
+
+  private void setupTestRepo(
+      String resourceName, Project.NameKey projectNameKey, String branchName, String filename)
+      throws Exception {
+    TestRepository<InMemoryRepository> repo = cloneProject(projectNameKey, admin);
+    try (InputStream in = getClass().getResourceAsStream(resourceName)) {
+      String resourceString = CharStreams.toString(new InputStreamReader(in, Charsets.UTF_8));
+
+      PushOneCommit push =
+          pushFactory.create(db, admin.getIdent(), repo, "some subject", filename, resourceString);
+      push.to("refs/heads/" + branchName).assertOkStatus();
+    }
+  }
+
+  private void pushConfig(String resourceName, String manifestName, String project)
+      throws Exception {
+    TestRepository<InMemoryRepository> allProjectRepo = cloneProject(allProjects, admin);
+    GitUtil.fetch(allProjectRepo, RefNames.REFS_CONFIG + ":config");
+    allProjectRepo.reset("config");
+    try (InputStream in = getClass().getResourceAsStream(resourceName)) {
+      String resourceString = CharStreams.toString(new InputStreamReader(in, Charsets.UTF_8));
+
+      Config cfg = new Config();
+      cfg.fromText(resourceString);
+      // Update manifest project path to the result of createProject(resourceName), since it is
+      // scoped to the test method
+      cfg.setString("global", null, "manifestProject", manifestName);
+      cfg.setString("automerger", "master:ds_one", "setProjects", project);
+      cfg.setString("automerger", "master:ds_two", "setProjects", project);
+      PushOneCommit push =
+          pushFactory.create(
+              db, admin.getIdent(), allProjectRepo, "Subject", "automerger.config", cfg.toText());
+      push.to(RefNames.REFS_CONFIG).assertOkStatus();
+    }
+  }
+}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/automerger/DownstreamCreatorTest.java b/src/test/java/com/googlesource/gerrit/plugins/automerger/DownstreamCreatorTest.java
deleted file mode 100644
index 54198f8..0000000
--- a/src/test/java/com/googlesource/gerrit/plugins/automerger/DownstreamCreatorTest.java
+++ /dev/null
@@ -1,246 +0,0 @@
-// Copyright (C) 2016 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.automerger;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import com.google.common.base.Function;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.gerrit.extensions.api.GerritApi;
-import com.google.gerrit.extensions.api.changes.ChangeApi;
-import com.google.gerrit.extensions.api.changes.RevisionApi;
-import com.google.gerrit.extensions.client.ListChangesOption;
-import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.common.ChangeInput;
-import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.extensions.common.MergePatchSetInput;
-import com.google.gerrit.extensions.common.RevisionInfo;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.runners.MockitoJUnitRunner;
-
-@RunWith(MockitoJUnitRunner.class)
-public class DownstreamCreatorTest {
-  private final String changeId = "testid";
-  private final String changeProject = "testproject";
-  private final String changeTopic = "testtopic";
-  private final String changeSubject = "testmessage";
-
-  @Mock(answer = Answers.RETURNS_DEEP_STUBS)
-  private GerritApi gApiMock;
-
-  private DownstreamCreator ds;
-
-  @Before
-  public void setUp() throws Exception {
-    ds = new DownstreamCreator(gApiMock, Mockito.mock(ConfigLoader.class));
-  }
-
-  private List<ChangeInfo> mockChangeInfoList(String upstreamBranch) {
-    return ImmutableList.of(
-        mockChangeInfo(upstreamBranch, 1),
-        mockChangeInfo("testwhee", 2),
-        mockChangeInfo(upstreamBranch, 3));
-  }
-
-  private ChangeInfo mockChangeInfo(String upstreamRevision, int number) {
-    CommitInfo parent1 = Mockito.mock(CommitInfo.class);
-    parent1.commit = "infoparent" + number;
-    CommitInfo parent2 = Mockito.mock(CommitInfo.class);
-    parent2.commit = upstreamRevision;
-
-    ChangeInfo info = Mockito.mock(ChangeInfo.class);
-    info._number = number;
-    info.currentRevision = "info" + number;
-    info.revisions = new HashMap<>();
-
-    RevisionInfo revisionInfoMock = Mockito.mock(RevisionInfo.class);
-    CommitInfo commit = Mockito.mock(CommitInfo.class);
-    commit.parents = ImmutableList.of(parent1, parent2);
-    revisionInfoMock.commit = commit;
-
-    info.revisions.put(info.currentRevision, revisionInfoMock);
-    return info;
-  }
-
-  @Test
-  public void testCreateDownstreamMerge() throws Exception {
-    String currentRevision = "testCurrentRevision";
-
-    ChangeInfo changeInfoMock = Mockito.mock(ChangeInfo.class);
-    changeInfoMock.id = "testnewchangeid";
-    ChangeApi changeApiMock = Mockito.mock(ChangeApi.class);
-    Mockito.when(changeApiMock.get(EnumSet.of(ListChangesOption.CURRENT_REVISION)))
-        .thenReturn(changeInfoMock);
-    Mockito.when(gApiMock.changes().create(Mockito.any(ChangeInput.class)))
-        .thenReturn(changeApiMock);
-    RevisionApi revisionApiMock = Mockito.mock(RevisionApi.class);
-    Mockito.when(gApiMock.changes().id(Mockito.anyString()).revision(Mockito.anyString()))
-        .thenReturn(revisionApiMock);
-
-    SingleDownstreamMergeInput dsMergeInput = new SingleDownstreamMergeInput();
-    dsMergeInput.currentRevision = currentRevision;
-    dsMergeInput.sourceId = changeId;
-    dsMergeInput.project = changeProject;
-    dsMergeInput.topic = changeTopic;
-    dsMergeInput.subject = changeSubject;
-    dsMergeInput.downstreamBranch = "testds";
-    dsMergeInput.doMerge = true;
-
-    ds.createSingleDownstreamMerge(dsMergeInput);
-
-    // Check ChangeInput is the right project, branch, topic, subject
-    ArgumentCaptor<ChangeInput> changeInputCaptor = ArgumentCaptor.forClass(ChangeInput.class);
-    Mockito.verify(gApiMock.changes()).create(changeInputCaptor.capture());
-    ChangeInput changeInput = changeInputCaptor.getValue();
-    assertThat(changeInput.project).isEqualTo(changeProject);
-    assertThat(changeInput.branch).isEqualTo("testds");
-    assertThat(changeInput.topic).isEqualTo(changeTopic);
-    assertThat(changeInput.merge.source).isEqualTo(currentRevision);
-    assertThat(changeInput.merge.strategy).isEqualTo("recursive");
-
-    String expectedSubject = changeSubject + " am: " + currentRevision.substring(0, 10);
-    assertThat(expectedSubject).isEqualTo(changeInput.subject);
-  }
-
-  @Test
-  public void testCreateDownstreamMerge_skipMerge() throws Exception {
-    String currentRevision = "testCurrentRevision";
-
-    ChangeInfo changeInfoMock = Mockito.mock(ChangeInfo.class);
-    changeInfoMock.id = "testnewchangeid";
-    ChangeApi changeApiMock = Mockito.mock(ChangeApi.class);
-    Mockito.when(changeApiMock.get(EnumSet.of(ListChangesOption.CURRENT_REVISION)))
-        .thenReturn(changeInfoMock);
-    Mockito.when(gApiMock.changes().create(Mockito.any(ChangeInput.class)))
-        .thenReturn(changeApiMock);
-    RevisionApi revisionApiMock = Mockito.mock(RevisionApi.class);
-    Mockito.when(gApiMock.changes().id(Mockito.anyString()).revision(Mockito.anyString()))
-        .thenReturn(revisionApiMock);
-
-    SingleDownstreamMergeInput dsMergeInput = new SingleDownstreamMergeInput();
-    dsMergeInput.currentRevision = currentRevision;
-    dsMergeInput.sourceId = changeId;
-    dsMergeInput.project = changeProject;
-    dsMergeInput.topic = changeTopic;
-    dsMergeInput.subject = changeSubject;
-    dsMergeInput.downstreamBranch = "testds";
-    dsMergeInput.doMerge = false;
-
-    ds.createSingleDownstreamMerge(dsMergeInput);
-
-    // Check ChangeInput is the right project, branch, topic, subject
-    ArgumentCaptor<ChangeInput> changeInputCaptor = ArgumentCaptor.forClass(ChangeInput.class);
-    Mockito.verify(gApiMock.changes()).create(changeInputCaptor.capture());
-    ChangeInput changeInput = changeInputCaptor.getValue();
-    assertThat(changeInput.project).isEqualTo(changeProject);
-    assertThat(changeInput.branch).isEqualTo("testds");
-    assertThat(changeInput.topic).isEqualTo(changeTopic);
-    assertThat(changeInput.merge.source).isEqualTo(currentRevision);
-
-    // Check that it was actually skipped
-    String expectedSubject = changeSubject + " skipped: " + currentRevision.substring(0, 10);
-    assertThat(changeInput.merge.strategy).isEqualTo("ours");
-    assertThat(expectedSubject).isEqualTo(changeInput.subject);
-  }
-
-  @Test
-  public void testCreateDownstreamMerges() throws Exception {
-    Map<String, Boolean> downstreamBranchMap = new HashMap<>();
-    downstreamBranchMap.put("testone", true);
-    downstreamBranchMap.put("testtwo", true);
-
-    MultipleDownstreamMergeInput mdsMergeInput = new MultipleDownstreamMergeInput();
-    mdsMergeInput.dsBranchMap = downstreamBranchMap;
-    mdsMergeInput.sourceId = changeId;
-    mdsMergeInput.project = changeProject;
-    mdsMergeInput.topic = changeTopic;
-    mdsMergeInput.subject = changeSubject;
-    mdsMergeInput.obsoleteRevision = null;
-    mdsMergeInput.currentRevision = "testCurrent";
-
-    ds.createDownstreamMerges(mdsMergeInput);
-
-    ArgumentCaptor<ChangeInput> changeInputCaptor = ArgumentCaptor.forClass(ChangeInput.class);
-    Mockito.verify(gApiMock.changes(), Mockito.times(2)).create(changeInputCaptor.capture());
-    List<String> capturedBranches =
-        Lists.transform(
-            changeInputCaptor.getAllValues(),
-            new Function<ChangeInput, String>() {
-              @Override
-              public String apply(ChangeInput c) {
-                return c.branch;
-              }
-            });
-    assertThat(capturedBranches).containsExactly("testone", "testtwo");
-  }
-
-  @Test
-  public void testCreateDownstreamMerges_withPreviousRevisions() throws Exception {
-    Map<String, Boolean> downstreamBranchMap = new HashMap<>();
-    downstreamBranchMap.put("testone", true);
-    downstreamBranchMap.put("testtwo", true);
-
-    List<ChangeInfo> changeInfoList = mockChangeInfoList("testup");
-    Mockito.when(
-            gApiMock
-                .changes()
-                .query(Mockito.anyString())
-                .withOptions(ListChangesOption.ALL_REVISIONS, ListChangesOption.CURRENT_COMMIT)
-                .get())
-        .thenReturn(changeInfoList);
-
-    MultipleDownstreamMergeInput mdsMergeInput = new MultipleDownstreamMergeInput();
-    mdsMergeInput.dsBranchMap = downstreamBranchMap;
-    mdsMergeInput.sourceId = changeId;
-    mdsMergeInput.project = changeProject;
-    mdsMergeInput.topic = changeTopic;
-    mdsMergeInput.subject = changeSubject;
-    mdsMergeInput.obsoleteRevision = "testup";
-    mdsMergeInput.currentRevision = "testCurrent";
-
-    ds.createDownstreamMerges(mdsMergeInput);
-
-    // Check that previous revisions were updated
-    Mockito.verify(gApiMock.changes().id(Mockito.anyInt()), Mockito.times(2))
-        .createMergePatchSet(Mockito.any(MergePatchSetInput.class));
-  }
-
-  @Test
-  public void testGetExistingMergesOnBranch() throws Exception {
-    List<ChangeInfo> changeInfoList = mockChangeInfoList("testup");
-    Mockito.when(
-            gApiMock
-                .changes()
-                .query(Mockito.anyString())
-                .withOptions(ListChangesOption.ALL_REVISIONS, ListChangesOption.CURRENT_COMMIT)
-                .get())
-        .thenReturn(changeInfoList);
-
-    List<Integer> downstreamChangeNumbers =
-        ds.getExistingMergesOnBranch("testup", "testtopic", "testdown");
-    assertThat(downstreamChangeNumbers).containsExactly(1, 3).inOrder();
-  }
-}