diff options
Diffstat (limited to 'org.eclipse.jgit.junit/src/org')
5 files changed, 304 insertions, 68 deletions
diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/FakeIndexFactory.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/FakeIndexFactory.java new file mode 100644 index 0000000000..eb23bec584 --- /dev/null +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/FakeIndexFactory.java @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2025, Google Inc. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.junit; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toUnmodifiableList; + +import java.io.IOException; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.internal.storage.file.PackIndex; +import org.eclipse.jgit.internal.storage.file.PackIndex.EntriesIterator; +import org.eclipse.jgit.internal.storage.file.PackReverseIndex; +import org.eclipse.jgit.lib.AbbreviatedObjectId; +import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; + +/** + * Create indexes with predefined data + * + * @since 7.2 + */ +public class FakeIndexFactory { + + /** + * An object for the fake index + * + * @param name + * a sha1 + * @param offset + * the (fake) position of the object in the pack + */ + public record IndexObject(String name, long offset) { + /** + * Name (sha1) as an objectId + * + * @return name (a sha1) as an objectId. + */ + public ObjectId getObjectId() { + return ObjectId.fromString(name); + } + } + + /** + * Return an index populated with these objects + * + * @param objs + * objects to be indexed + * @return a PackIndex implementation + */ + public static PackIndex indexOf(List<IndexObject> objs) { + return new FakePackIndex(objs); + } + + /** + * Return a reverse pack index with these objects + * + * @param objs + * objects to be indexed + * @return a PackReverseIndex implementation + */ + public static PackReverseIndex reverseIndexOf(List<IndexObject> objs) { + return new FakeReverseIndex(objs); + } + + private FakeIndexFactory() { + } + + private static class FakePackIndex implements PackIndex { + private static final Comparator<IndexObject> SHA1_COMPARATOR = (o1, + o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.name(), + o2.name()); + + private final Map<String, IndexObject> idx; + + private final List<IndexObject> sha1Ordered; + + private final long offset64count; + + FakePackIndex(List<IndexObject> objs) { + sha1Ordered = objs.stream().sorted(SHA1_COMPARATOR) + .collect(toUnmodifiableList()); + idx = objs.stream().collect(toMap(IndexObject::name, identity())); + offset64count = objs.stream() + .filter(o -> o.offset > Integer.MAX_VALUE).count(); + } + + @Override + public Iterator<MutableEntry> iterator() { + return new FakeEntriesIterator(sha1Ordered); + } + + @Override + public long getObjectCount() { + return sha1Ordered.size(); + } + + @Override + public long getOffset64Count() { + return offset64count; + } + + @Override + public ObjectId getObjectId(long nthPosition) { + return ObjectId + .fromString(sha1Ordered.get((int) nthPosition).name()); + } + + @Override + public long getOffset(long nthPosition) { + return sha1Ordered.get((int) nthPosition).offset(); + } + + @Override + public long findOffset(AnyObjectId objId) { + IndexObject o = idx.get(objId.name()); + if (o == null) { + return -1; + } + return o.offset(); + } + + @Override + public int findPosition(AnyObjectId objId) { + IndexObject o = idx.get(objId.name()); + if (o == null) { + return -1; + } + return sha1Ordered.indexOf(o); + } + + @Override + public long findCRC32(AnyObjectId objId) throws MissingObjectException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean hasCRC32Support() { + return false; + } + + @Override + public void resolve(Set<ObjectId> matches, AbbreviatedObjectId id, + int matchLimit) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public byte[] getChecksum() { + return new byte[0]; + } + } + + private static class FakeReverseIndex implements PackReverseIndex { + private static final Comparator<IndexObject> OFFSET_COMPARATOR = Comparator + .comparingLong(IndexObject::offset); + + private final List<IndexObject> byOffset; + + private final Map<Long, IndexObject> ridx; + + FakeReverseIndex(List<IndexObject> objs) { + byOffset = objs.stream().sorted(OFFSET_COMPARATOR) + .collect(toUnmodifiableList()); + ridx = byOffset.stream() + .collect(toMap(IndexObject::offset, identity())); + } + + @Override + public void verifyPackChecksum(String packFilePath) { + // Do nothing + } + + @Override + public ObjectId findObject(long offset) { + IndexObject indexObject = ridx.get(offset); + if (indexObject == null) { + return null; + } + return ObjectId.fromString(indexObject.name()); + } + + @Override + public long findNextOffset(long offset, long maxOffset) + throws CorruptObjectException { + IndexObject o = ridx.get(offset); + if (o == null) { + throw new CorruptObjectException("Invalid offset"); //$NON-NLS-1$ + } + int pos = byOffset.indexOf(o); + if (pos == byOffset.size() - 1) { + return maxOffset; + } + return byOffset.get(pos + 1).offset(); + } + + @Override + public int findPosition(long offset) { + IndexObject indexObject = ridx.get(offset); + return byOffset.indexOf(indexObject); + } + + @Override + public ObjectId findObjectByPosition(int nthPosition) { + return byOffset.get(nthPosition).getObjectId(); + } + } + + private static class FakeEntriesIterator extends EntriesIterator { + + private static final byte[] buffer = new byte[Constants.OBJECT_ID_LENGTH]; + + private final Iterator<IndexObject> it; + + FakeEntriesIterator(List<IndexObject> objs) { + super(objs.size()); + it = objs.iterator(); + } + + @Override + protected void readNext() { + IndexObject next = it.next(); + next.getObjectId().copyRawTo(buffer, 0); + setIdBuffer(buffer, 0); + setOffset(next.offset()); + } + } +} diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java index 407290a399..0d20f6488a 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java @@ -20,19 +20,19 @@ import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.time.Instant; +import java.time.ZoneId; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Random; import java.util.Set; import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.internal.storage.file.FileRepository; -import org.eclipse.jgit.internal.util.ShutdownHook; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -47,6 +47,7 @@ import org.eclipse.jgit.util.SystemReader; import org.junit.After; import org.junit.Before; import org.junit.Rule; +import org.junit.rules.TemporaryFolder; import org.junit.rules.TestName; /** @@ -84,6 +85,16 @@ public abstract class LocalDiskRepositoryTestCase { protected MockSystemReader mockSystemReader; private final Set<Repository> toClose = new HashSet<>(); + + /** + * Temporary test root directory for files created by tests. + * @since 7.2 + */ + @Rule + public TemporaryFolder testRoot = new TemporaryFolder(); + + Random rand = new Random(); + private File tmp; private File homeDir; @@ -114,11 +125,8 @@ public abstract class LocalDiskRepositoryTestCase { */ @Before public void setUp() throws Exception { - tmp = File.createTempFile("jgit_" + getTestName() + '_', "_tmp"); - Cleanup.deleteOnShutdown(tmp); - if (!tmp.delete() || !tmp.mkdir()) { - throw new IOException("Cannot create " + tmp); - } + tmp = testRoot.newFolder(getTestName() + rand.nextInt()); + mockSystemReader = new MockSystemReader(); SystemReader.setInstance(mockSystemReader); @@ -219,12 +227,6 @@ public abstract class LocalDiskRepositoryTestCase { System.gc(); } FS.DETECTED.setUserHome(homeDir); - if (tmp != null) { - recursiveDelete(tmp, false, true); - } - if (tmp != null && !tmp.exists()) { - Cleanup.removed(tmp); - } SystemReader.setInstance(null); } @@ -233,8 +235,8 @@ public abstract class LocalDiskRepositoryTestCase { */ protected void tick() { mockSystemReader.tick(5 * 60); - final long now = mockSystemReader.getCurrentTime(); - final int tz = mockSystemReader.getTimezone(now); + Instant now = mockSystemReader.now(); + ZoneId tz = mockSystemReader.getTimeZoneId(); author = new PersonIdent(author, now, tz); committer = new PersonIdent(committer, now, tz); @@ -623,41 +625,4 @@ public abstract class LocalDiskRepositoryTestCase { private static HashMap<String, String> cloneEnv() { return new HashMap<>(System.getenv()); } - - private static final class Cleanup { - private static final Cleanup INSTANCE = new Cleanup(); - - static { - ShutdownHook.INSTANCE.register(() -> INSTANCE.onShutdown()); - } - - private final Set<File> toDelete = ConcurrentHashMap.newKeySet(); - - private Cleanup() { - // empty - } - - static void deleteOnShutdown(File tmp) { - INSTANCE.toDelete.add(tmp); - } - - static void removed(File tmp) { - INSTANCE.toDelete.remove(tmp); - } - - private void onShutdown() { - // On windows accidentally open files or memory - // mapped regions may prevent files from being deleted. - // Suggesting a GC increases the likelihood that our - // test repositories actually get removed after the - // tests, even in the case of failure. - System.gc(); - synchronized (this) { - boolean silent = false; - boolean failOnError = false; - for (File tmp : toDelete) - recursiveDelete(tmp, silent, failOnError); - } - } - } } diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java index 419fdb1966..38f0d0b2cb 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java @@ -18,6 +18,8 @@ import java.lang.reflect.Field; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.time.Duration; +import java.time.ZoneId; +import java.time.ZoneOffset; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -242,6 +244,11 @@ public class MockSystemReader extends SystemReader { } @Override + public ZoneId getTimeZoneId() { + return ZoneOffset.ofHoursMinutes(-3, -30); + } + + @Override public Locale getLocale() { return Locale.US; } diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SeparateClassloaderTestRunner.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SeparateClassloaderTestRunner.java index c8c56b21b8..2a482df04a 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SeparateClassloaderTestRunner.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/SeparateClassloaderTestRunner.java @@ -44,7 +44,7 @@ public class SeparateClassloaderTestRunner extends BlockJUnit4ClassRunner { try { String pathSeparator = System.getProperty("path.separator"); String[] classPathEntries = System.getProperty("java.class.path") - .split(pathSeparator); + .split(pathSeparator, -1); URL[] urls = new URL[classPathEntries.length]; for (int i = 0; i < classPathEntries.length; i++) { urls[i] = Paths.get(classPathEntries[i]).toUri().toURL(); diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java index a2e0a571eb..2d00a850e5 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -21,6 +21,7 @@ import java.io.IOException; import java.io.OutputStream; import java.security.MessageDigest; import java.time.Instant; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -30,6 +31,7 @@ import java.util.List; import java.util.Set; import java.util.TimeZone; +import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; @@ -157,8 +159,8 @@ public class TestRepository<R extends Repository> implements AutoCloseable { this.pool = rw; this.inserter = db.newObjectInserter(); this.mockSystemReader = reader; - long now = mockSystemReader.getCurrentTime(); - int tz = mockSystemReader.getTimezone(now); + Instant now = mockSystemReader.now(); + ZoneId tz = mockSystemReader.getTimeZoneAt(now); defaultAuthor = new PersonIdent(AUTHOR, AUTHOR_EMAIL, now, tz); defaultCommitter = new PersonIdent(COMMITTER, COMMITTER_EMAIL, now, tz); } @@ -197,7 +199,9 @@ public class TestRepository<R extends Repository> implements AutoCloseable { * * @return current date. * @since 4.2 + * @deprecated Use {@link #getInstant()} instead. */ + @Deprecated(since = "7.2") public Date getDate() { return new Date(mockSystemReader.getCurrentTime()); } @@ -209,18 +213,31 @@ public class TestRepository<R extends Repository> implements AutoCloseable { * @since 6.8 */ public Instant getInstant() { - return Instant.ofEpochMilli(mockSystemReader.getCurrentTime()); + return mockSystemReader.now(); } /** * Get timezone * * @return timezone used for default identities. + * @deprecated Use {@link #getTimeZoneId()} instead. */ + @Deprecated(since = "7.2") public TimeZone getTimeZone() { return mockSystemReader.getTimeZone(); } + + /** + * Get timezone + * + * @return timezone used for default identities. + * @since 7.2 + */ + public ZoneId getTimeZoneId() { + return mockSystemReader.getTimeZoneId(); + } + /** * Adjust the current time that will used by the next commit. * @@ -232,14 +249,14 @@ public class TestRepository<R extends Repository> implements AutoCloseable { } /** - * Set the author and committer using {@link #getDate()}. + * Set the author and committer using {@link #getInstant()}. * * @param c * the commit builder to store. */ public void setAuthorAndCommitter(org.eclipse.jgit.lib.CommitBuilder c) { - c.setAuthor(new PersonIdent(defaultAuthor, getDate())); - c.setCommitter(new PersonIdent(defaultCommitter, getDate())); + c.setAuthor(new PersonIdent(defaultAuthor, getInstant())); + c.setCommitter(new PersonIdent(defaultCommitter, getInstant())); } /** @@ -487,8 +504,8 @@ public class TestRepository<R extends Repository> implements AutoCloseable { c = new org.eclipse.jgit.lib.CommitBuilder(); c.setTreeId(tree); c.setParentIds(parents); - c.setAuthor(new PersonIdent(defaultAuthor, getDate())); - c.setCommitter(new PersonIdent(defaultCommitter, getDate())); + c.setAuthor(new PersonIdent(defaultAuthor, getInstant())); + c.setCommitter(new PersonIdent(defaultCommitter, getInstant())); c.setMessage(""); ObjectId id; try (ObjectInserter ins = inserter) { @@ -528,7 +545,7 @@ public class TestRepository<R extends Repository> implements AutoCloseable { final TagBuilder t = new TagBuilder(); t.setObjectId(dst); t.setTag(name); - t.setTagger(new PersonIdent(defaultCommitter, getDate())); + t.setTagger(new PersonIdent(defaultCommitter, getInstant())); t.setMessage(""); ObjectId id; try (ObjectInserter ins = inserter) { @@ -797,7 +814,7 @@ public class TestRepository<R extends Repository> implements AutoCloseable { b.setParentId(head); b.setTreeId(merger.getResultTreeId()); b.setAuthor(commit.getAuthorIdent()); - b.setCommitter(new PersonIdent(defaultCommitter, getDate())); + b.setCommitter(new PersonIdent(defaultCommitter, getInstant())); b.setMessage(commit.getFullMessage()); ObjectId result; try (ObjectInserter ins = inserter) { @@ -1019,7 +1036,8 @@ public class TestRepository<R extends Repository> implements AutoCloseable { private static void prunePacked(ObjectDirectory odb) throws IOException { for (Pack p : odb.getPacks()) { for (MutableEntry e : p) - FileUtils.delete(odb.fileFor(e.toObjectId())); + FileUtils.delete(odb.fileFor(e.toObjectId()), + FileUtils.SKIP_MISSING); } } @@ -1144,15 +1162,18 @@ public class TestRepository<R extends Repository> implements AutoCloseable { } /** - * set parent commit + * Set parent commit * * @param p - * parent commit + * parent commit, can be {@code null} * @return this commit builder * @throws Exception * if an error occurred */ - public CommitBuilder parent(RevCommit p) throws Exception { + public CommitBuilder parent(@Nullable RevCommit p) throws Exception { + if (p == null) { + return this; + } if (parents.isEmpty()) { DirCacheBuilder b = tree.builder(); parseBody(p); @@ -1403,7 +1424,7 @@ public class TestRepository<R extends Repository> implements AutoCloseable { c.setAuthor(author); if (committer != null) { if (updateCommitterTime) - committer = new PersonIdent(committer, getDate()); + committer = new PersonIdent(committer, getInstant()); c.setCommitter(committer); } |