You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LocalDiskRepositoryTestCase.java 20KB

Persist minimal racy threshold and allow manual configuration To enable persisting the minimal racy threshold per FileStore add a new config option to the user global git configuration: - Config section is "filesystem" - Config subsection is concatenation of - Java vendor (system property "java.vendor") - Java version (system property "java.version") - FileStore's name, on Windows we use the attribute volume:vsn instead since the name is not necessarily unique. - separated by '|' e.g. "AdoptOpenJDK|1.8.0_212-b03|/dev/disk1s1" The same prefix is used as for filesystem timestamp resolution, so both values are stored in the same config section - The config key for minmal racy threshold is "minRacyThreshold" as a time value, supported time units are those supported by DefaultTypedConfigGetter#getTimeUnit - measure for 3 seconds to limit runtime which depends on hardware, OS and Java version being used If the minimal racy threshold is configured for a given FileStore the configured value is used instead of measuring it. When the minimal racy threshold was measured it is persisted in the user global git configuration. Rename FileStoreAttributeCache to FileStoreAttributes since this class is now declared public in order to enable exposing all attributes in one object. Example: [filesystem "AdoptOpenJDK|11.0.3|/dev/disk1s1"] timestampResolution = 7000 nanoseconds minRacyThreshold = 3440 microseconds Change-Id: I22195e488453aae8d011b0a8e3276fe3d99deaea Signed-off-by: Matthias Sohn <matthias.sohn@sap.com> Also-By: Marc Strapetz <marc.strapetz@syntevo.com>
4 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. /*
  2. * Copyright (C) 2009-2010, Google Inc.
  3. * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  4. * Copyright (C) 2007, Shawn O. Pearce <spearce@spearce.org> and others
  5. *
  6. * This program and the accompanying materials are made available under the
  7. * terms of the Eclipse Distribution License v. 1.0 which is available at
  8. * https://www.eclipse.org/org/documents/edl-v10.php.
  9. *
  10. * SPDX-License-Identifier: BSD-3-Clause
  11. */
  12. package org.eclipse.jgit.junit;
  13. import static java.nio.charset.StandardCharsets.UTF_8;
  14. import static org.junit.Assert.assertFalse;
  15. import static org.junit.Assert.fail;
  16. import java.io.File;
  17. import java.io.IOException;
  18. import java.io.PrintStream;
  19. import java.time.Instant;
  20. import java.util.ArrayList;
  21. import java.util.Collections;
  22. import java.util.HashMap;
  23. import java.util.HashSet;
  24. import java.util.List;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import java.util.TreeSet;
  28. import org.eclipse.jgit.dircache.DirCache;
  29. import org.eclipse.jgit.dircache.DirCacheEntry;
  30. import org.eclipse.jgit.internal.storage.file.FileRepository;
  31. import org.eclipse.jgit.lib.ConfigConstants;
  32. import org.eclipse.jgit.lib.Constants;
  33. import org.eclipse.jgit.lib.ObjectId;
  34. import org.eclipse.jgit.lib.PersonIdent;
  35. import org.eclipse.jgit.lib.Repository;
  36. import org.eclipse.jgit.lib.RepositoryCache;
  37. import org.eclipse.jgit.storage.file.FileBasedConfig;
  38. import org.eclipse.jgit.storage.file.WindowCacheConfig;
  39. import org.eclipse.jgit.util.FS;
  40. import org.eclipse.jgit.util.FileUtils;
  41. import org.eclipse.jgit.util.SystemReader;
  42. import org.junit.After;
  43. import org.junit.Before;
  44. /**
  45. * JUnit TestCase with specialized support for temporary local repository.
  46. * <p>
  47. * A temporary directory is created for each test, allowing each test to use a
  48. * fresh environment. The temporary directory is cleaned up after the test ends.
  49. * <p>
  50. * Callers should not use {@link org.eclipse.jgit.lib.RepositoryCache} from
  51. * within these tests as it may wedge file descriptors open past the end of the
  52. * test.
  53. * <p>
  54. * A system property {@code jgit.junit.usemmap} defines whether memory mapping
  55. * is used. Memory mapping has an effect on the file system, in that memory
  56. * mapped files in Java cannot be deleted as long as the mapped arrays have not
  57. * been reclaimed by the garbage collector. The programmer cannot control this
  58. * with precision, so temporary files may hang around longer than desired during
  59. * a test, or tests may fail altogether if there is insufficient file
  60. * descriptors or address space for the test process.
  61. */
  62. public abstract class LocalDiskRepositoryTestCase {
  63. private static final boolean useMMAP = "true".equals(System
  64. .getProperty("jgit.junit.usemmap"));
  65. /** A fake (but stable) identity for author fields in the test. */
  66. protected PersonIdent author;
  67. /** A fake (but stable) identity for committer fields in the test. */
  68. protected PersonIdent committer;
  69. /**
  70. * A {@link SystemReader} used to coordinate time, envars, etc.
  71. * @since 4.2
  72. */
  73. protected MockSystemReader mockSystemReader;
  74. private final Set<Repository> toClose = new HashSet<>();
  75. private File tmp;
  76. /**
  77. * Setup test
  78. *
  79. * @throws Exception
  80. */
  81. @Before
  82. public void setUp() throws Exception {
  83. tmp = File.createTempFile("jgit_test_", "_tmp");
  84. CleanupThread.deleteOnShutdown(tmp);
  85. if (!tmp.delete() || !tmp.mkdir()) {
  86. throw new IOException("Cannot create " + tmp);
  87. }
  88. mockSystemReader = new MockSystemReader();
  89. SystemReader.setInstance(mockSystemReader);
  90. // Measure timer resolution before the test to avoid time critical tests
  91. // are affected by time needed for measurement.
  92. // The MockSystemReader must be configured first since we need to use
  93. // the same one here
  94. FS.getFileStoreAttributes(tmp.toPath().getParent());
  95. FileBasedConfig jgitConfig = new FileBasedConfig(
  96. new File(tmp, "jgitconfig"), FS.DETECTED);
  97. FileBasedConfig systemConfig = new FileBasedConfig(jgitConfig,
  98. new File(tmp, "systemgitconfig"), FS.DETECTED);
  99. FileBasedConfig userConfig = new FileBasedConfig(systemConfig,
  100. new File(tmp, "usergitconfig"), FS.DETECTED);
  101. // We have to set autoDetach to false for tests, because tests expect to be able
  102. // to clean up by recursively removing the repository, and background GC might be
  103. // in the middle of writing or deleting files, which would disrupt this.
  104. userConfig.setBoolean(ConfigConstants.CONFIG_GC_SECTION,
  105. null, ConfigConstants.CONFIG_KEY_AUTODETACH, false);
  106. userConfig.save();
  107. mockSystemReader.setJGitConfig(jgitConfig);
  108. mockSystemReader.setSystemGitConfig(systemConfig);
  109. mockSystemReader.setUserGitConfig(userConfig);
  110. ceilTestDirectories(getCeilings());
  111. author = new PersonIdent("J. Author", "jauthor@example.com");
  112. committer = new PersonIdent("J. Committer", "jcommitter@example.com");
  113. final WindowCacheConfig c = new WindowCacheConfig();
  114. c.setPackedGitLimit(128 * WindowCacheConfig.KB);
  115. c.setPackedGitWindowSize(8 * WindowCacheConfig.KB);
  116. c.setPackedGitMMAP(useMMAP);
  117. c.setDeltaBaseCacheLimit(8 * WindowCacheConfig.KB);
  118. c.install();
  119. }
  120. /**
  121. * Get temporary directory.
  122. *
  123. * @return the temporary directory
  124. */
  125. protected File getTemporaryDirectory() {
  126. return tmp.getAbsoluteFile();
  127. }
  128. /**
  129. * Get list of ceiling directories
  130. *
  131. * @return list of ceiling directories
  132. */
  133. protected List<File> getCeilings() {
  134. return Collections.singletonList(getTemporaryDirectory());
  135. }
  136. private void ceilTestDirectories(List<File> ceilings) {
  137. mockSystemReader.setProperty(Constants.GIT_CEILING_DIRECTORIES_KEY, makePath(ceilings));
  138. }
  139. private static String makePath(List<?> objects) {
  140. final StringBuilder stringBuilder = new StringBuilder();
  141. for (Object object : objects) {
  142. if (stringBuilder.length() > 0)
  143. stringBuilder.append(File.pathSeparatorChar);
  144. stringBuilder.append(object.toString());
  145. }
  146. return stringBuilder.toString();
  147. }
  148. /**
  149. * Tear down the test
  150. *
  151. * @throws Exception
  152. */
  153. @After
  154. public void tearDown() throws Exception {
  155. RepositoryCache.clear();
  156. for (Repository r : toClose)
  157. r.close();
  158. toClose.clear();
  159. // Since memory mapping is controlled by the GC we need to
  160. // tell it this is a good time to clean up and unlock
  161. // memory mapped files.
  162. //
  163. if (useMMAP)
  164. System.gc();
  165. if (tmp != null)
  166. recursiveDelete(tmp, false, true);
  167. if (tmp != null && !tmp.exists())
  168. CleanupThread.removed(tmp);
  169. SystemReader.setInstance(null);
  170. }
  171. /**
  172. * Increment the {@link #author} and {@link #committer} times.
  173. */
  174. protected void tick() {
  175. mockSystemReader.tick(5 * 60);
  176. final long now = mockSystemReader.getCurrentTime();
  177. final int tz = mockSystemReader.getTimezone(now);
  178. author = new PersonIdent(author, now, tz);
  179. committer = new PersonIdent(committer, now, tz);
  180. }
  181. /**
  182. * Recursively delete a directory, failing the test if the delete fails.
  183. *
  184. * @param dir
  185. * the recursively directory to delete, if present.
  186. */
  187. protected void recursiveDelete(File dir) {
  188. recursiveDelete(dir, false, true);
  189. }
  190. private static boolean recursiveDelete(final File dir,
  191. boolean silent, boolean failOnError) {
  192. assert !(silent && failOnError);
  193. int options = FileUtils.RECURSIVE | FileUtils.RETRY
  194. | FileUtils.SKIP_MISSING;
  195. if (silent) {
  196. options |= FileUtils.IGNORE_ERRORS;
  197. }
  198. try {
  199. FileUtils.delete(dir, options);
  200. } catch (IOException e) {
  201. reportDeleteFailure(failOnError, dir, e);
  202. return !failOnError;
  203. }
  204. return true;
  205. }
  206. private static void reportDeleteFailure(boolean failOnError, File f,
  207. Exception cause) {
  208. String severity = failOnError ? "ERROR" : "WARNING";
  209. String msg = severity + ": Failed to delete " + f;
  210. if (failOnError) {
  211. fail(msg);
  212. } else {
  213. System.err.println(msg);
  214. }
  215. cause.printStackTrace(new PrintStream(System.err));
  216. }
  217. /** Constant <code>MOD_TIME=1</code> */
  218. public static final int MOD_TIME = 1;
  219. /** Constant <code>SMUDGE=2</code> */
  220. public static final int SMUDGE = 2;
  221. /** Constant <code>LENGTH=4</code> */
  222. public static final int LENGTH = 4;
  223. /** Constant <code>CONTENT_ID=8</code> */
  224. public static final int CONTENT_ID = 8;
  225. /** Constant <code>CONTENT=16</code> */
  226. public static final int CONTENT = 16;
  227. /** Constant <code>ASSUME_UNCHANGED=32</code> */
  228. public static final int ASSUME_UNCHANGED = 32;
  229. /**
  230. * Represent the state of the index in one String. This representation is
  231. * useful when writing tests which do assertions on the state of the index.
  232. * By default information about path, mode, stage (if different from 0) is
  233. * included. A bitmask controls which additional info about
  234. * modificationTimes, smudge state and length is included.
  235. * <p>
  236. * The format of the returned string is described with this BNF:
  237. *
  238. * <pre>
  239. * result = ( "[" path mode stage? time? smudge? length? sha1? content? "]" )* .
  240. * mode = ", mode:" number .
  241. * stage = ", stage:" number .
  242. * time = ", time:t" timestamp-index .
  243. * smudge = "" | ", smudged" .
  244. * length = ", length:" number .
  245. * sha1 = ", sha1:" hex-sha1 .
  246. * content = ", content:" blob-data .
  247. * </pre>
  248. *
  249. * 'stage' is only presented when the stage is different from 0. All
  250. * reported time stamps are mapped to strings like "t0", "t1", ... "tn". The
  251. * smallest reported time-stamp will be called "t0". This allows to write
  252. * assertions against the string although the concrete value of the time
  253. * stamps is unknown.
  254. *
  255. * @param repo
  256. * the repository the index state should be determined for
  257. * @param includedOptions
  258. * a bitmask constructed out of the constants {@link #MOD_TIME},
  259. * {@link #SMUDGE}, {@link #LENGTH}, {@link #CONTENT_ID} and
  260. * {@link #CONTENT} controlling which info is present in the
  261. * resulting string.
  262. * @return a string encoding the index state
  263. * @throws IllegalStateException
  264. * @throws IOException
  265. */
  266. public static String indexState(Repository repo, int includedOptions)
  267. throws IllegalStateException, IOException {
  268. DirCache dc = repo.readDirCache();
  269. StringBuilder sb = new StringBuilder();
  270. TreeSet<Instant> timeStamps = new TreeSet<>();
  271. // iterate once over the dircache just to collect all time stamps
  272. if (0 != (includedOptions & MOD_TIME)) {
  273. for (int i = 0; i < dc.getEntryCount(); ++i) {
  274. timeStamps.add(dc.getEntry(i).getLastModifiedInstant());
  275. }
  276. }
  277. // iterate again, now produce the result string
  278. for (int i=0; i<dc.getEntryCount(); ++i) {
  279. DirCacheEntry entry = dc.getEntry(i);
  280. sb.append("["+entry.getPathString()+", mode:" + entry.getFileMode());
  281. int stage = entry.getStage();
  282. if (stage != 0)
  283. sb.append(", stage:" + stage);
  284. if (0 != (includedOptions & MOD_TIME)) {
  285. sb.append(", time:t"+
  286. timeStamps.headSet(entry.getLastModifiedInstant())
  287. .size());
  288. }
  289. if (0 != (includedOptions & SMUDGE))
  290. if (entry.isSmudged())
  291. sb.append(", smudged");
  292. if (0 != (includedOptions & LENGTH))
  293. sb.append(", length:"
  294. + Integer.toString(entry.getLength()));
  295. if (0 != (includedOptions & CONTENT_ID))
  296. sb.append(", sha1:" + ObjectId.toString(entry.getObjectId()));
  297. if (0 != (includedOptions & CONTENT)) {
  298. sb.append(", content:"
  299. + new String(repo.open(entry.getObjectId(),
  300. Constants.OBJ_BLOB).getCachedBytes(), UTF_8));
  301. }
  302. if (0 != (includedOptions & ASSUME_UNCHANGED))
  303. sb.append(", assume-unchanged:"
  304. + Boolean.toString(entry.isAssumeValid()));
  305. sb.append("]");
  306. }
  307. return sb.toString();
  308. }
  309. /**
  310. * Creates a new empty bare repository.
  311. *
  312. * @return the newly created bare repository, opened for access. The
  313. * repository will not be closed in {@link #tearDown()}; the caller
  314. * is responsible for closing it.
  315. * @throws IOException
  316. * the repository could not be created in the temporary area
  317. */
  318. protected FileRepository createBareRepository() throws IOException {
  319. return createRepository(true /* bare */);
  320. }
  321. /**
  322. * Creates a new empty repository within a new empty working directory.
  323. *
  324. * @return the newly created repository, opened for access. The repository
  325. * will not be closed in {@link #tearDown()}; the caller is
  326. * responsible for closing it.
  327. * @throws IOException
  328. * the repository could not be created in the temporary area
  329. */
  330. protected FileRepository createWorkRepository() throws IOException {
  331. return createRepository(false /* not bare */);
  332. }
  333. /**
  334. * Creates a new empty repository.
  335. *
  336. * @param bare
  337. * true to create a bare repository; false to make a repository
  338. * within its working directory
  339. * @return the newly created repository, opened for access. The repository
  340. * will not be closed in {@link #tearDown()}; the caller is
  341. * responsible for closing it.
  342. * @throws IOException
  343. * the repository could not be created in the temporary area
  344. * @since 5.3
  345. */
  346. protected FileRepository createRepository(boolean bare)
  347. throws IOException {
  348. return createRepository(bare, false /* auto close */);
  349. }
  350. /**
  351. * Creates a new empty repository.
  352. *
  353. * @param bare
  354. * true to create a bare repository; false to make a repository
  355. * within its working directory
  356. * @param autoClose
  357. * auto close the repository in {@link #tearDown()}
  358. * @return the newly created repository, opened for access
  359. * @throws IOException
  360. * the repository could not be created in the temporary area
  361. * @deprecated use {@link #createRepository(boolean)} instead
  362. */
  363. @Deprecated
  364. public FileRepository createRepository(boolean bare, boolean autoClose)
  365. throws IOException {
  366. File gitdir = createUniqueTestGitDir(bare);
  367. FileRepository db = new FileRepository(gitdir);
  368. assertFalse(gitdir.exists());
  369. db.create(bare);
  370. if (autoClose) {
  371. addRepoToClose(db);
  372. }
  373. return db;
  374. }
  375. /**
  376. * Adds a repository to the list of repositories which is closed at the end
  377. * of the tests
  378. *
  379. * @param r
  380. * the repository to be closed
  381. */
  382. public void addRepoToClose(Repository r) {
  383. toClose.add(r);
  384. }
  385. /**
  386. * Creates a unique directory for a test
  387. *
  388. * @param name
  389. * a subdirectory
  390. * @return a unique directory for a test
  391. * @throws IOException
  392. */
  393. protected File createTempDirectory(String name) throws IOException {
  394. File directory = new File(createTempFile(), name);
  395. FileUtils.mkdirs(directory);
  396. return directory.getCanonicalFile();
  397. }
  398. /**
  399. * Creates a new unique directory for a test repository
  400. *
  401. * @param bare
  402. * true for a bare repository; false for a repository with a
  403. * working directory
  404. * @return a unique directory for a test repository
  405. * @throws IOException
  406. */
  407. protected File createUniqueTestGitDir(boolean bare) throws IOException {
  408. String gitdirName = createTempFile().getPath();
  409. if (!bare)
  410. gitdirName += "/";
  411. return new File(gitdirName + Constants.DOT_GIT);
  412. }
  413. /**
  414. * Allocates a new unique file path that does not exist.
  415. * <p>
  416. * Unlike the standard {@code File.createTempFile} the returned path does
  417. * not exist, but may be created by another thread in a race with the
  418. * caller. Good luck.
  419. * <p>
  420. * This method is inherently unsafe due to a race condition between creating
  421. * the name and the first use that reserves it.
  422. *
  423. * @return a unique path that does not exist.
  424. * @throws IOException
  425. */
  426. protected File createTempFile() throws IOException {
  427. File p = File.createTempFile("tmp_", "", tmp);
  428. if (!p.delete()) {
  429. throw new IOException("Cannot obtain unique path " + tmp);
  430. }
  431. return p;
  432. }
  433. /**
  434. * Run a hook script in the repository, returning the exit status.
  435. *
  436. * @param db
  437. * repository the script should see in GIT_DIR environment
  438. * @param hook
  439. * path of the hook script to execute, must be executable file
  440. * type on this platform
  441. * @param args
  442. * arguments to pass to the hook script
  443. * @return exit status code of the invoked hook
  444. * @throws IOException
  445. * the hook could not be executed
  446. * @throws InterruptedException
  447. * the caller was interrupted before the hook completed
  448. */
  449. protected int runHook(final Repository db, final File hook,
  450. final String... args) throws IOException, InterruptedException {
  451. final String[] argv = new String[1 + args.length];
  452. argv[0] = hook.getAbsolutePath();
  453. System.arraycopy(args, 0, argv, 1, args.length);
  454. final Map<String, String> env = cloneEnv();
  455. env.put("GIT_DIR", db.getDirectory().getAbsolutePath());
  456. putPersonIdent(env, "AUTHOR", author);
  457. putPersonIdent(env, "COMMITTER", committer);
  458. final File cwd = db.getWorkTree();
  459. final Process p = Runtime.getRuntime().exec(argv, toEnvArray(env), cwd);
  460. p.getOutputStream().close();
  461. p.getErrorStream().close();
  462. p.getInputStream().close();
  463. return p.waitFor();
  464. }
  465. private static void putPersonIdent(final Map<String, String> env,
  466. final String type, final PersonIdent who) {
  467. final String ident = who.toExternalString();
  468. final String date = ident.substring(ident.indexOf("> ") + 2);
  469. env.put("GIT_" + type + "_NAME", who.getName());
  470. env.put("GIT_" + type + "_EMAIL", who.getEmailAddress());
  471. env.put("GIT_" + type + "_DATE", date);
  472. }
  473. /**
  474. * Create a string to a UTF-8 temporary file and return the path.
  475. *
  476. * @param body
  477. * complete content to write to the file. If the file should end
  478. * with a trailing LF, the string should end with an LF.
  479. * @return path of the temporary file created within the trash area.
  480. * @throws IOException
  481. * the file could not be written.
  482. */
  483. protected File write(String body) throws IOException {
  484. final File f = File.createTempFile("temp", "txt", tmp);
  485. try {
  486. write(f, body);
  487. return f;
  488. } catch (Error | RuntimeException | IOException e) {
  489. f.delete();
  490. throw e;
  491. }
  492. }
  493. /**
  494. * Write a string as a UTF-8 file.
  495. *
  496. * @param f
  497. * file to write the string to. Caller is responsible for making
  498. * sure it is in the trash directory or will otherwise be cleaned
  499. * up at the end of the test. If the parent directory does not
  500. * exist, the missing parent directories are automatically
  501. * created.
  502. * @param body
  503. * content to write to the file.
  504. * @throws IOException
  505. * the file could not be written.
  506. */
  507. protected void write(File f, String body) throws IOException {
  508. JGitTestUtil.write(f, body);
  509. }
  510. /**
  511. * Read a file's content
  512. *
  513. * @param f
  514. * the file
  515. * @return the content of the file
  516. * @throws IOException
  517. */
  518. protected String read(File f) throws IOException {
  519. return JGitTestUtil.read(f);
  520. }
  521. private static String[] toEnvArray(Map<String, String> env) {
  522. final String[] envp = new String[env.size()];
  523. int i = 0;
  524. for (Map.Entry<String, String> e : env.entrySet())
  525. envp[i++] = e.getKey() + "=" + e.getValue();
  526. return envp;
  527. }
  528. private static HashMap<String, String> cloneEnv() {
  529. return new HashMap<>(System.getenv());
  530. }
  531. private static final class CleanupThread extends Thread {
  532. private static final CleanupThread me;
  533. static {
  534. me = new CleanupThread();
  535. Runtime.getRuntime().addShutdownHook(me);
  536. }
  537. static void deleteOnShutdown(File tmp) {
  538. synchronized (me) {
  539. me.toDelete.add(tmp);
  540. }
  541. }
  542. static void removed(File tmp) {
  543. synchronized (me) {
  544. me.toDelete.remove(tmp);
  545. }
  546. }
  547. private final List<File> toDelete = new ArrayList<>();
  548. @Override
  549. public void run() {
  550. // On windows accidentally open files or memory
  551. // mapped regions may prevent files from being deleted.
  552. // Suggesting a GC increases the likelihood that our
  553. // test repositories actually get removed after the
  554. // tests, even in the case of failure.
  555. System.gc();
  556. synchronized (this) {
  557. boolean silent = false;
  558. boolean failOnError = false;
  559. for (File tmp : toDelete)
  560. recursiveDelete(tmp, silent, failOnError);
  561. }
  562. }
  563. }
  564. }