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()) {