diff options
author | Matthias Sohn <matthias.sohn@sap.com> | 2019-08-08 12:30:47 +0200 |
---|---|---|
committer | Matthias Sohn <matthias.sohn@sap.com> | 2019-08-08 13:04:38 +0200 |
commit | 718555518ded4fbcafe0c1203f0035d3251884e6 (patch) | |
tree | 3e49529cd0ebc21fcea581efe0fa15fdcdc8b81c /org.eclipse.jgit.junit/src | |
parent | fcfe1299c37ae8308e9d930201823b3ac340dd2b (diff) | |
parent | 0046b2a8fefcbfecc10a7b198a075eb2775d3e7a (diff) | |
download | jgit-718555518ded4fbcafe0c1203f0035d3251884e6.tar.gz jgit-718555518ded4fbcafe0c1203f0035d3251884e6.zip |
Merge branch 'stable-5.2' into stable-5.3
* stable-5.2:
Fix OpenSshConfigTest#config
FileSnapshot: fix bug with timestamp thresholding
In LockFile#waitForStatChange wait in units of file time resolution
Cache FileStoreAttributeCache per directory
Fix FileSnapshot#save(long) and FileSnapshot#save(Instant)
Persist minimal racy threshold and allow manual configuration
Measure minimum racy interval to auto-configure FileSnapshot
Reuse FileUtils to recursively delete files created by tests
Fix FileAttributeCache.toString()
Add test for racy git detection in FileSnapshot
Repeat RefDirectoryTest.testGetRef_DiscoversModifiedLoose 100 times
Fix org.eclipse.jdt.core.prefs of org.eclipse.jgit.junit
Add missing javadoc in org.eclipse.jgit.junit
Enhance RepeatRule to report number of failures at the end
Fix FileSnapshotTests for filesystem with high timestamp resolution
Retry deleting test files in FileBasedConfigTest
Measure filesystem timestamp resolution already in test setup
Refactor FileSnapshotTest to use NIO APIs
Measure stored timestamp resolution instead of time to touch file
Handle CancellationException in FileStoreAttributeCache
Fix FileSnapshot#saveNoConfig
Use Instant for smudge time in DirCache and DirCacheEntry
Use Instant instead of milliseconds for filesystem timestamp handling
Workaround SecurityException in FS#getFsTimestampResolution
Fix NPE in FS$FileStoreAttributeCache.getFsTimestampResolution
FS: ignore AccessDeniedException when measuring timestamp resolution
Add debug trace for FileSnapshot
Use FileChannel.open to touch file and set mtime to now
Persist filesystem timestamp resolution and allow manual configuration
Increase bazel timeout for long running tests
Bazel: Fix lint warning flagged by buildifier
Update bazlets to latest version
Bazel: Add missing dependencies for ArchiveCommandTest
Bazel: Remove FileTreeIteratorWithTimeControl from BUILD file
Add support for nanoseconds and microseconds for Config#getTimeUnit
Optionally measure filesystem timestamp resolution asynchronously
Delete unused FileTreeIteratorWithTimeControl
FileSnapshot#equals: consider UNKNOWN_SIZE
Timeout measuring file timestamp resolution after 2 seconds
Fix RacyGitTests#testRacyGitDetection
Change RacyGitTests to create a racy git situation in a stable way
Deprecate Constants.CHARACTER_ENCODING in favor of
StandardCharsets.UTF_8
Fix non-deterministic hash of archives created by ArchiveCommand
Update Maven plugins ecj, plexus, error-prone
Update Maven plugins and cleanup Maven warnings
Make inner classes static where possible
Fix API problem filters
Change-Id: I238adfd3080a5fed9d64c3c757297da6ea893918
Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
Diffstat (limited to 'org.eclipse.jgit.junit/src')
6 files changed, 332 insertions, 38 deletions
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 4b2eadf418..838537f3ae 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 @@ -51,6 +51,8 @@ import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; +import java.io.PrintStream; +import java.time.Instant; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -126,6 +128,10 @@ public abstract class LocalDiskRepositoryTestCase { if (!tmp.delete() || !tmp.mkdir()) throw new IOException("Cannot create " + tmp); + // measure timer resolution before the test to avoid time critical tests + // are affected by time needed for measurement + FS.getFileStoreAttributes(tmp.toPath().getParent()); + mockSystemReader = new MockSystemReader(); mockSystemReader.userGitConfig = new FileBasedConfig(new File(tmp, "usergitconfig"), FS.DETECTED); @@ -232,35 +238,30 @@ public abstract class LocalDiskRepositoryTestCase { private static boolean recursiveDelete(final File dir, boolean silent, boolean failOnError) { assert !(silent && failOnError); - if (!dir.exists()) - return silent; - final File[] ls = dir.listFiles(); - if (ls != null) - for (int k = 0; k < ls.length; k++) { - final File e = ls[k]; - if (e.isDirectory()) - silent = recursiveDelete(e, silent, failOnError); - else if (!e.delete()) { - if (!silent) - reportDeleteFailure(failOnError, e); - silent = !failOnError; - } - } - if (!dir.delete()) { - if (!silent) - reportDeleteFailure(failOnError, dir); - silent = !failOnError; + int options = FileUtils.RECURSIVE | FileUtils.RETRY + | FileUtils.SKIP_MISSING; + if (silent) { + options |= FileUtils.IGNORE_ERRORS; } - return silent; + try { + FileUtils.delete(dir, options); + } catch (IOException e) { + reportDeleteFailure(failOnError, dir, e); + return !failOnError; + } + return true; } - private static void reportDeleteFailure(boolean failOnError, File e) { + private static void reportDeleteFailure(boolean failOnError, File f, + Exception cause) { String severity = failOnError ? "ERROR" : "WARNING"; - String msg = severity + ": Failed to delete " + e; - if (failOnError) + String msg = severity + ": Failed to delete " + f; + if (failOnError) { fail(msg); - else + } else { System.err.println(msg); + } + cause.printStackTrace(new PrintStream(System.err)); } /** Constant <code>MOD_TIME=1</code> */ @@ -322,12 +323,13 @@ public abstract class LocalDiskRepositoryTestCase { throws IllegalStateException, IOException { DirCache dc = repo.readDirCache(); StringBuilder sb = new StringBuilder(); - TreeSet<Long> timeStamps = new TreeSet<>(); + TreeSet<Instant> timeStamps = new TreeSet<>(); // iterate once over the dircache just to collect all time stamps if (0 != (includedOptions & MOD_TIME)) { - for (int i=0; i<dc.getEntryCount(); ++i) - timeStamps.add(Long.valueOf(dc.getEntry(i).getLastModified())); + for (int i = 0; i < dc.getEntryCount(); ++i) { + timeStamps.add(dc.getEntry(i).getLastModifiedInstant()); + } } // iterate again, now produce the result string @@ -339,7 +341,8 @@ public abstract class LocalDiskRepositoryTestCase { sb.append(", stage:" + stage); if (0 != (includedOptions & MOD_TIME)) { sb.append(", time:t"+ - timeStamps.headSet(Long.valueOf(entry.getLastModified())).size()); + timeStamps.headSet(entry.getLastModifiedInstant()) + .size()); } if (0 != (includedOptions & SMUDGE)) if (entry.isSmudged()) diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java index 08220ce245..94df554aec 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/Repeat.java @@ -56,4 +56,13 @@ public @interface Repeat { * Number of repetitions */ public abstract int n(); + + /** + * Whether to abort execution on first test failure + * + * @return {@code true} if execution should be aborted on the first failure, + * otherwise count failures and continue execution + * @since 5.1.9 + */ + public boolean abortOnFailure() default true; } diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java index 8165738ed8..8636f2a0aa 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepeatRule.java @@ -81,9 +81,31 @@ public class RepeatRule implements TestRule { private static Logger LOG = Logger .getLogger(RepeatRule.class.getName()); + /** + * Exception thrown if repeated execution of a test annotated with + * {@code @Repeat} failed. + */ public static class RepeatedTestException extends RuntimeException { private static final long serialVersionUID = 1L; + /** + * Constructor + * + * @param message + * the error message + */ + public RepeatedTestException(String message) { + super(message); + } + + /** + * Constructor + * + * @param message + * the error message + * @param cause + * exception causing this exception + */ public RepeatedTestException(String message, Throwable cause) { super(message, cause); } @@ -93,28 +115,45 @@ public class RepeatRule implements TestRule { private final int repetitions; + private boolean abortOnFailure; + private final Statement statement; - private RepeatStatement(int repetitions, Statement statement) { + private RepeatStatement(int repetitions, boolean abortOnFailure, + Statement statement) { this.repetitions = repetitions; + this.abortOnFailure = abortOnFailure; this.statement = statement; } @Override public void evaluate() throws Throwable { + int failures = 0; for (int i = 0; i < repetitions; i++) { try { statement.evaluate(); } catch (Throwable e) { + failures += 1; RepeatedTestException ex = new RepeatedTestException( MessageFormat.format( "Repeated test failed when run for the {0}. time", Integer.valueOf(i + 1)), e); LOG.log(Level.SEVERE, ex.getMessage(), ex); - throw ex; + if (abortOnFailure) { + throw ex; + } } } + if (failures > 0) { + RepeatedTestException e = new RepeatedTestException( + MessageFormat.format( + "Test failed {0} times out of {1} repeated executions", + Integer.valueOf(failures), + Integer.valueOf(repetitions))); + LOG.log(Level.SEVERE, e.getMessage(), e); + throw e; + } } } @@ -125,7 +164,8 @@ public class RepeatRule implements TestRule { Repeat repeat = description.getAnnotation(Repeat.class); if (repeat != null) { int n = repeat.n(); - result = new RepeatStatement(n, statement); + boolean abortOnFailure = repeat.abortOnFailure(); + result = new RepeatStatement(n, abortOnFailure, statement); } return result; } diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java index 987f923b0e..5aacbbadec 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java @@ -57,7 +57,9 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.nio.file.Path; +import java.time.Instant; import java.util.Map; +import java.util.concurrent.TimeUnit; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; @@ -284,7 +286,7 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { dce = new DirCacheEntry(treeItr.getEntryPathString()); dce.setFileMode(treeItr.getEntryFileMode()); - dce.setLastModified(treeItr.getEntryLastModified()); + dce.setLastModified(treeItr.getEntryLastModifiedInstant()); dce.setLength((int) len); try (FileInputStream in = new FileInputStream( treeItr.getEntryFile())) { @@ -361,7 +363,8 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { * @throws InterruptedException * @throws IOException */ - public static long fsTick(File lastFile) throws InterruptedException, + public static Instant fsTick(File lastFile) + throws InterruptedException, IOException { File tmp; FS fs = FS.DETECTED; @@ -375,15 +378,16 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { tmp = File.createTempFile("fsTickTmpFile", null, lastFile.getParentFile()); } - long res = FS.getFsTimerResolution(tmp.toPath()).toMillis(); + long res = FS.getFileStoreAttributes(tmp.toPath()) + .getFsTimestampResolution().toNanos(); long sleepTime = res / 10; try { - long startTime = fs.lastModified(lastFile); - long actTime = fs.lastModified(tmp); - while (actTime <= startTime) { - Thread.sleep(sleepTime); + Instant startTime = fs.lastModifiedInstant(lastFile); + Instant actTime = fs.lastModifiedInstant(tmp); + while (actTime.compareTo(startTime) <= 0) { + TimeUnit.NANOSECONDS.sleep(sleepTime); FileUtils.touch(tmp.toPath()); - actTime = fs.lastModified(tmp); + actTime = fs.lastModifiedInstant(tmp); } return actTime; } finally { 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 55a7766f60..02ffe4f409 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 @@ -1076,6 +1076,14 @@ public class TestRepository<R extends Repository> implements AutoCloseable { parents.add(prior.create()); } + /** + * set parent commit + * + * @param p + * parent commit + * @return this commit builder + * @throws Exception + */ public CommitBuilder parent(RevCommit p) throws Exception { if (parents.isEmpty()) { DirCacheBuilder b = tree.builder(); @@ -1088,29 +1096,71 @@ public class TestRepository<R extends Repository> implements AutoCloseable { return this; } + /** + * Get parent commits + * + * @return parent commits + */ public List<RevCommit> parents() { return Collections.unmodifiableList(parents); } + /** + * Remove parent commits + * + * @return this commit builder + */ public CommitBuilder noParents() { parents.clear(); return this; } + /** + * Remove files + * + * @return this commit builder + */ public CommitBuilder noFiles() { tree.clear(); return this; } + /** + * Set top level tree + * + * @param treeId + * the top level tree + * @return this commit builder + */ public CommitBuilder setTopLevelTree(ObjectId treeId) { topLevelTree = treeId; return this; } + /** + * Add file with given content + * + * @param path + * path of the file + * @param content + * the file content + * @return this commit builder + * @throws Exception + */ public CommitBuilder add(String path, String content) throws Exception { return add(path, blob(content)); } + /** + * Add file with given path and blob + * + * @param path + * path of the file + * @param id + * blob for this file + * @return this commit builder + * @throws Exception + */ public CommitBuilder add(String path, RevBlob id) throws Exception { return edit(new PathEdit(path) { @@ -1122,6 +1172,13 @@ public class TestRepository<R extends Repository> implements AutoCloseable { }); } + /** + * Edit the index + * + * @param edit + * the index record update + * @return this commit builder + */ public CommitBuilder edit(PathEdit edit) { DirCacheEditor e = tree.editor(); e.add(edit); @@ -1129,6 +1186,13 @@ public class TestRepository<R extends Repository> implements AutoCloseable { return this; } + /** + * Remove a file + * + * @param path + * path of the file + * @return this commit builder + */ public CommitBuilder rm(String path) { DirCacheEditor e = tree.editor(); e.add(new DeletePath(path)); @@ -1137,49 +1201,111 @@ public class TestRepository<R extends Repository> implements AutoCloseable { return this; } + /** + * Set commit message + * + * @param m + * the message + * @return this commit builder + */ public CommitBuilder message(String m) { message = m; return this; } + /** + * Get the commit message + * + * @return the commit message + */ public String message() { return message; } + /** + * Tick the clock + * + * @param secs + * number of seconds + * @return this commit builder + */ public CommitBuilder tick(int secs) { tick = secs; return this; } + /** + * Set author and committer identity + * + * @param ident + * identity to set + * @return this commit builder + */ public CommitBuilder ident(PersonIdent ident) { author = ident; committer = ident; return this; } + /** + * Set the author identity + * + * @param a + * the author's identity + * @return this commit builder + */ public CommitBuilder author(PersonIdent a) { author = a; return this; } + /** + * Get the author identity + * + * @return the author identity + */ public PersonIdent author() { return author; } + /** + * Set the committer identity + * + * @param c + * the committer identity + * @return this commit builder + */ public CommitBuilder committer(PersonIdent c) { committer = c; return this; } + /** + * Get the committer identity + * + * @return the committer identity + */ public PersonIdent committer() { return committer; } + /** + * Insert changeId + * + * @return this commit builder + */ public CommitBuilder insertChangeId() { changeId = ""; return this; } + /** + * Insert given changeId + * + * @param c + * changeId + * @return this commit builder + */ public CommitBuilder insertChangeId(String c) { // Validate, but store as a string so we can use "" as a sentinel. ObjectId.fromString(c); @@ -1187,6 +1313,13 @@ public class TestRepository<R extends Repository> implements AutoCloseable { return this; } + /** + * Create the commit + * + * @return the new commit + * @throws Exception + * if creation failed + */ public RevCommit create() throws Exception { if (self == null) { TestRepository.this.tick(tick); @@ -1247,6 +1380,12 @@ public class TestRepository<R extends Repository> implements AutoCloseable { + cid.getName() + "\n"); //$NON-NLS-1$ } + /** + * Create child commit builder + * + * @return child commit builder + * @throws Exception + */ public CommitBuilder child() throws Exception { return new CommitBuilder(this); } diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java new file mode 100644 index 0000000000..1f8070d919 --- /dev/null +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/time/TimeUtil.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2019, Matthias Sohn <matthias.sohn@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.junit.time; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.time.Instant; + +import org.eclipse.jgit.util.FS; + +/** + * Utility methods for handling timestamps + */ +public class TimeUtil { + /** + * Set the lastModified time of a given file by adding a given offset to the + * current lastModified time + * + * @param path + * path of a file to set last modified + * @param offsetMillis + * offset in milliseconds, if negative the new lastModified time + * is offset before the original lastModified time, otherwise + * after the original time + * @return the new lastModified time + */ + public static Instant setLastModifiedWithOffset(Path path, + long offsetMillis) { + Instant mTime = FS.DETECTED.lastModifiedInstant(path) + .plusMillis(offsetMillis); + try { + Files.setLastModifiedTime(path, FileTime.from(mTime)); + return mTime; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Set the lastModified time of file a to the one from file b + * + * @param a + * file to set lastModified time + * @param b + * file to read lastModified time from + */ + public static void setLastModifiedOf(Path a, Path b) { + Instant mTime = FS.DETECTED.lastModifiedInstant(b); + try { + Files.setLastModifiedTime(a, FileTime.from(mTime)); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + +} |