Use distinct times for each TestRepository in acceptance tests It is easy to create identical commits using e.g. PushOneCommit, which uses a fixed commit message and tree contents by default. It uses TestRepository's native commit timestamp ticking such that each new commit gets a different commit timestamp, but this still causes problems when multiple TestRepository instances are created in the same process: each instance starts its clock at the same epoch. The result is identical computed Change-Ids for completely unrelated changes. Many tests (reasonably) assume that a Change-Id is unique within the scope of that test, and this would violate that assumption if we want to reuse a running server for multiple tests (and we do). Work around this problem by giving each new TestRepository instance (at least when created via GitUtil) its own 1-day window of timestamps. There are some 8k 1-day windows until 2038 when JGit runs out of 32-bit signed int timestamps, which should be enough for a single test process. Likewise a 1-day window provides 86k ticks of the clock before colliding with the next window, which should be enough for a single test method. Change-Id: Id86db8eb853d54bf2b88900274f595243e290fc4
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java index d90e459..2cd2579 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/GitUtil.java
@@ -16,6 +16,7 @@ import com.google.common.base.Optional; import com.google.common.collect.Iterables; +import com.google.common.primitives.Ints; import com.google.gerrit.common.FooterConstants; import com.google.gerrit.reviewdb.client.Project; @@ -31,6 +32,7 @@ import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.transport.FetchResult; import org.eclipse.jgit.transport.JschConfigSessionFactory; @@ -43,8 +45,12 @@ import java.io.IOException; import java.util.List; import java.util.Properties; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; public class GitUtil { + private static final AtomicInteger testRepoCount = new AtomicInteger(); + private static final int TEST_REPO_WINDOW_DAYS = 2; public static void initSsh(final TestAccount a) { final Properties config = new Properties(); @@ -68,6 +74,31 @@ }); } + /** + * Create a new {@link TestRepository} with a distinct commit clock. + * <p> + * It is very easy for tests to create commits with identical subjects and + * trees; if such commits also have identical authors/committers, then the + * computed Change-Id is identical as well. Tests may generally assume that + * Change-Ids are unique, so to ensure this, we provision TestRepository + * instances with non-overlapping commit clock times. + * <p> + * Space test repos 1 day apart, which allows for about 86k ticks per repo + * before overlapping, and about 8k instances per process before hitting + * JGit's year 2038 limit. + * + * @param repo repository to wrap. + * @return wrapped test repository with distinct commit time space. + */ + public static <R extends Repository> TestRepository<R> newTestRepository( + R repo) throws IOException { + TestRepository<R> tr = new TestRepository<>(repo); + tr.tick(Ints.checkedCast(TimeUnit.SECONDS.convert( + testRepoCount.getAndIncrement() * TEST_REPO_WINDOW_DAYS, + TimeUnit.DAYS))); + return tr; + } + public static TestRepository<InMemoryRepository> cloneProject( Project.NameKey project, String uri) throws Exception { InMemoryRepository dest = new InMemoryRepository.Builder() @@ -81,13 +112,13 @@ cfg.setString("remote", "origin", "url", uri); cfg.setString("remote", "origin", "fetch", "+refs/heads/*:refs/remotes/origin/*"); - TestRepository<InMemoryRepository> testRepo = new TestRepository<>(dest); + TestRepository<InMemoryRepository> testRepo = newTestRepository(dest); FetchResult result = testRepo.git().fetch().setRemote("origin").call(); String originMaster = "refs/remotes/origin/master"; if (result.getTrackingRefUpdate(originMaster) != null) { testRepo.reset(originMaster); } - return new TestRepository<>(dest); + return testRepo; } public static TestRepository<InMemoryRepository> cloneProject(
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java index 8e627c4..038a208 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
@@ -19,6 +19,7 @@ import com.google.common.collect.Iterables; import com.google.gerrit.acceptance.AbstractDaemonTest; +import com.google.gerrit.acceptance.GitUtil; import com.google.gerrit.acceptance.PushOneCommit; import com.google.gerrit.acceptance.RestResponse; import com.google.gerrit.common.data.AccessSection; @@ -40,7 +41,7 @@ @Before public void setUp() throws Exception { - repo = new TestRepository<>(repoManager.openRepository(project)); + repo = GitUtil.newTestRepository(repoManager.openRepository(project)); ProjectConfig pc = projectCache.checkedGet(allProjects).getConfig(); for (AccessSection sec : pc.getAccessSections()) {