149aac325SShawn O. Pearce /* 2f5eb0d93SShawn O. Pearce * Copyright (C) 2009-2010, Google Inc. 349aac325SShawn O. Pearce * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> 4*5c5f7c6bSMatthias Sohn * Copyright (C) 2007, Shawn O. Pearce <spearce@spearce.org> and others 549aac325SShawn O. Pearce * 6*5c5f7c6bSMatthias Sohn * This program and the accompanying materials are made available under the 7*5c5f7c6bSMatthias Sohn * terms of the Eclipse Distribution License v. 1.0 which is available at 8*5c5f7c6bSMatthias Sohn * https://www.eclipse.org/org/documents/edl-v10.php. 949aac325SShawn O. Pearce * 10*5c5f7c6bSMatthias Sohn * SPDX-License-Identifier: BSD-3-Clause 1149aac325SShawn O. Pearce */ 1249aac325SShawn O. Pearce 1349aac325SShawn O. Pearce package org.eclipse.jgit.junit; 1449aac325SShawn O. Pearce 1530c6c754SDavid Pursehouse import static java.nio.charset.StandardCharsets.UTF_8; 16d9e07a57SRobin Rosenberg import static org.junit.Assert.assertFalse; 17d9e07a57SRobin Rosenberg import static org.junit.Assert.fail; 18d9e07a57SRobin Rosenberg 1949aac325SShawn O. Pearce import java.io.File; 2049aac325SShawn O. Pearce import java.io.IOException; 21902935c3SMatthias Sohn import java.io.PrintStream; 2295e8264cSMatthias Sohn import java.time.Instant; 23a45cfee7SDavid Pursehouse import java.util.ArrayList; 24a45cfee7SDavid Pursehouse import java.util.Collections; 25a45cfee7SDavid Pursehouse import java.util.HashMap; 26d69a829bSDavid Pursehouse import java.util.HashSet; 27a45cfee7SDavid Pursehouse import java.util.List; 28a45cfee7SDavid Pursehouse import java.util.Map; 29d69a829bSDavid Pursehouse import java.util.Set; 30a45cfee7SDavid Pursehouse import java.util.TreeSet; 3149aac325SShawn O. Pearce 3237a1e4beSChris Price import org.eclipse.jgit.dircache.DirCache; 3337a1e4beSChris Price import org.eclipse.jgit.dircache.DirCacheEntry; 34f32b8612SShawn Pearce import org.eclipse.jgit.internal.storage.file.FileRepository; 356b1e3c58SDavid Turner import org.eclipse.jgit.lib.ConfigConstants; 36a45cfee7SDavid Pursehouse import org.eclipse.jgit.lib.Constants; 37a45cfee7SDavid Pursehouse import org.eclipse.jgit.lib.ObjectId; 38a45cfee7SDavid Pursehouse import org.eclipse.jgit.lib.PersonIdent; 39a45cfee7SDavid Pursehouse import org.eclipse.jgit.lib.Repository; 40a45cfee7SDavid Pursehouse import org.eclipse.jgit.lib.RepositoryCache; 41ad5238dcSShawn O. Pearce import org.eclipse.jgit.storage.file.FileBasedConfig; 42ad5238dcSShawn O. Pearce import org.eclipse.jgit.storage.file.WindowCacheConfig; 43db82b8d7SJens Baumgart import org.eclipse.jgit.util.FS; 4438eec8f4SMatthias Sohn import org.eclipse.jgit.util.FileUtils; 4549aac325SShawn O. Pearce import org.eclipse.jgit.util.SystemReader; 46d9e07a57SRobin Rosenberg import org.junit.After; 47d9e07a57SRobin Rosenberg import org.junit.Before; 4849aac325SShawn O. Pearce 4949aac325SShawn O. Pearce /** 5049aac325SShawn O. Pearce * JUnit TestCase with specialized support for temporary local repository. 5149aac325SShawn O. Pearce * <p> 5249aac325SShawn O. Pearce * A temporary directory is created for each test, allowing each test to use a 5349aac325SShawn O. Pearce * fresh environment. The temporary directory is cleaned up after the test ends. 5449aac325SShawn O. Pearce * <p> 55a90b75b4SMatthias Sohn * Callers should not use {@link org.eclipse.jgit.lib.RepositoryCache} from 56a90b75b4SMatthias Sohn * within these tests as it may wedge file descriptors open past the end of the 57a90b75b4SMatthias Sohn * test. 5849aac325SShawn O. Pearce * <p> 5949aac325SShawn O. Pearce * A system property {@code jgit.junit.usemmap} defines whether memory mapping 6049aac325SShawn O. Pearce * is used. Memory mapping has an effect on the file system, in that memory 6149aac325SShawn O. Pearce * mapped files in Java cannot be deleted as long as the mapped arrays have not 6249aac325SShawn O. Pearce * been reclaimed by the garbage collector. The programmer cannot control this 6349aac325SShawn O. Pearce * with precision, so temporary files may hang around longer than desired during 6449aac325SShawn O. Pearce * a test, or tests may fail altogether if there is insufficient file 6549aac325SShawn O. Pearce * descriptors or address space for the test process. 6649aac325SShawn O. Pearce */ 67d9e07a57SRobin Rosenberg public abstract class LocalDiskRepositoryTestCase { 6849aac325SShawn O. Pearce private static final boolean useMMAP = "true".equals(System 6949aac325SShawn O. Pearce .getProperty("jgit.junit.usemmap")); 7049aac325SShawn O. Pearce 7149aac325SShawn O. Pearce /** A fake (but stable) identity for author fields in the test. */ 7249aac325SShawn O. Pearce protected PersonIdent author; 7349aac325SShawn O. Pearce 7449aac325SShawn O. Pearce /** A fake (but stable) identity for committer fields in the test. */ 7549aac325SShawn O. Pearce protected PersonIdent committer; 7649aac325SShawn O. Pearce 772cdc130dSMatthias Sohn /** 782cdc130dSMatthias Sohn * A {@link SystemReader} used to coordinate time, envars, etc. 792cdc130dSMatthias Sohn * @since 4.2 802cdc130dSMatthias Sohn */ 81069040ddSTerry Parker protected MockSystemReader mockSystemReader; 82069040ddSTerry Parker 83d69a829bSDavid Pursehouse private final Set<Repository> toClose = new HashSet<>(); 8436144e12SShawn Pearce private File tmp; 8549aac325SShawn O. Pearce 86a90b75b4SMatthias Sohn /** 87a90b75b4SMatthias Sohn * Setup test 88a90b75b4SMatthias Sohn * 89a90b75b4SMatthias Sohn * @throws Exception 90a90b75b4SMatthias Sohn */ 91d9e07a57SRobin Rosenberg @Before setUp()92d9e07a57SRobin Rosenberg public void setUp() throws Exception { 9336144e12SShawn Pearce tmp = File.createTempFile("jgit_test_", "_tmp"); 9436144e12SShawn Pearce CleanupThread.deleteOnShutdown(tmp); 95838b5a84SMatthias Sohn if (!tmp.delete() || !tmp.mkdir()) { 9636144e12SShawn Pearce throw new IOException("Cannot create " + tmp); 97838b5a84SMatthias Sohn } 9849aac325SShawn O. Pearce 9949aac325SShawn O. Pearce mockSystemReader = new MockSystemReader(); 100c28167e1SMatthias Sohn SystemReader.setInstance(mockSystemReader); 1017f92a70fSMatthias Sohn 1027f92a70fSMatthias Sohn // Measure timer resolution before the test to avoid time critical tests 1037f92a70fSMatthias Sohn // are affected by time needed for measurement. 1047f92a70fSMatthias Sohn // The MockSystemReader must be configured first since we need to use 1057f92a70fSMatthias Sohn // the same one here 106db82b8d7SJens Baumgart FS.getFileStoreAttributes(tmp.toPath().getParent()); 1076b1e3c58SDavid Turner 108838b5a84SMatthias Sohn FileBasedConfig jgitConfig = new FileBasedConfig( 109838b5a84SMatthias Sohn new File(tmp, "jgitconfig"), FS.DETECTED); 110838b5a84SMatthias Sohn FileBasedConfig systemConfig = new FileBasedConfig(jgitConfig, 111838b5a84SMatthias Sohn new File(tmp, "systemgitconfig"), FS.DETECTED); 112838b5a84SMatthias Sohn FileBasedConfig userConfig = new FileBasedConfig(systemConfig, 113f383206aSMatthias Sohn new File(tmp, "usergitconfig"), FS.DETECTED); 1146b1e3c58SDavid Turner // We have to set autoDetach to false for tests, because tests expect to be able 1156b1e3c58SDavid Turner // to clean up by recursively removing the repository, and background GC might be 1166b1e3c58SDavid Turner // in the middle of writing or deleting files, which would disrupt this. 117f383206aSMatthias Sohn userConfig.setBoolean(ConfigConstants.CONFIG_GC_SECTION, 1186b1e3c58SDavid Turner null, ConfigConstants.CONFIG_KEY_AUTODETACH, false); 119f383206aSMatthias Sohn userConfig.save(); 120838b5a84SMatthias Sohn mockSystemReader.setJGitConfig(jgitConfig); 121838b5a84SMatthias Sohn mockSystemReader.setSystemGitConfig(systemConfig); 122f383206aSMatthias Sohn mockSystemReader.setUserGitConfig(userConfig); 123838b5a84SMatthias Sohn 124eb63bfc1SRobin Rosenberg ceilTestDirectories(getCeilings()); 12549aac325SShawn O. Pearce 12649aac325SShawn O. Pearce author = new PersonIdent("J. Author", "jauthor@example.com"); 12749aac325SShawn O. Pearce committer = new PersonIdent("J. Committer", "jcommitter@example.com"); 12849aac325SShawn O. Pearce 12949aac325SShawn O. Pearce final WindowCacheConfig c = new WindowCacheConfig(); 13049aac325SShawn O. Pearce c.setPackedGitLimit(128 * WindowCacheConfig.KB); 13149aac325SShawn O. Pearce c.setPackedGitWindowSize(8 * WindowCacheConfig.KB); 13249aac325SShawn O. Pearce c.setPackedGitMMAP(useMMAP); 13349aac325SShawn O. Pearce c.setDeltaBaseCacheLimit(8 * WindowCacheConfig.KB); 134f32b8612SShawn Pearce c.install(); 13549aac325SShawn O. Pearce } 13649aac325SShawn O. Pearce 137a90b75b4SMatthias Sohn /** 138a90b75b4SMatthias Sohn * Get temporary directory. 139a90b75b4SMatthias Sohn * 140a90b75b4SMatthias Sohn * @return the temporary directory 141a90b75b4SMatthias Sohn */ getTemporaryDirectory()14236144e12SShawn Pearce protected File getTemporaryDirectory() { 14336144e12SShawn Pearce return tmp.getAbsoluteFile(); 14436144e12SShawn Pearce } 145eb63bfc1SRobin Rosenberg 146a90b75b4SMatthias Sohn /** 147a90b75b4SMatthias Sohn * Get list of ceiling directories 148a90b75b4SMatthias Sohn * 149a90b75b4SMatthias Sohn * @return list of ceiling directories 150a90b75b4SMatthias Sohn */ getCeilings()151eb63bfc1SRobin Rosenberg protected List<File> getCeilings() { 15236144e12SShawn Pearce return Collections.singletonList(getTemporaryDirectory()); 153eb63bfc1SRobin Rosenberg } 154eb63bfc1SRobin Rosenberg ceilTestDirectories(List<File> ceilings)155eb63bfc1SRobin Rosenberg private void ceilTestDirectories(List<File> ceilings) { 156eb63bfc1SRobin Rosenberg mockSystemReader.setProperty(Constants.GIT_CEILING_DIRECTORIES_KEY, makePath(ceilings)); 157eb63bfc1SRobin Rosenberg } 158eb63bfc1SRobin Rosenberg makePath(List<?> objects)159a57dd1c1SRobin Rosenberg private static String makePath(List<?> objects) { 160eb63bfc1SRobin Rosenberg final StringBuilder stringBuilder = new StringBuilder(); 161eb63bfc1SRobin Rosenberg for (Object object : objects) { 162eb63bfc1SRobin Rosenberg if (stringBuilder.length() > 0) 163eb63bfc1SRobin Rosenberg stringBuilder.append(File.pathSeparatorChar); 164eb63bfc1SRobin Rosenberg stringBuilder.append(object.toString()); 165eb63bfc1SRobin Rosenberg } 166eb63bfc1SRobin Rosenberg return stringBuilder.toString(); 167eb63bfc1SRobin Rosenberg } 168eb63bfc1SRobin Rosenberg 169a90b75b4SMatthias Sohn /** 170a90b75b4SMatthias Sohn * Tear down the test 171a90b75b4SMatthias Sohn * 172a90b75b4SMatthias Sohn * @throws Exception 173a90b75b4SMatthias Sohn */ 174d9e07a57SRobin Rosenberg @After tearDown()175d9e07a57SRobin Rosenberg public void tearDown() throws Exception { 17649aac325SShawn O. Pearce RepositoryCache.clear(); 17749aac325SShawn O. Pearce for (Repository r : toClose) 17849aac325SShawn O. Pearce r.close(); 17949aac325SShawn O. Pearce toClose.clear(); 18049aac325SShawn O. Pearce 18149aac325SShawn O. Pearce // Since memory mapping is controlled by the GC we need to 18249aac325SShawn O. Pearce // tell it this is a good time to clean up and unlock 18349aac325SShawn O. Pearce // memory mapped files. 18449aac325SShawn O. Pearce // 18549aac325SShawn O. Pearce if (useMMAP) 18649aac325SShawn O. Pearce System.gc(); 18736144e12SShawn Pearce if (tmp != null) 18836144e12SShawn Pearce recursiveDelete(tmp, false, true); 18936144e12SShawn Pearce if (tmp != null && !tmp.exists()) 19036144e12SShawn Pearce CleanupThread.removed(tmp); 19109711a4bSRobin Stocker 19209711a4bSRobin Stocker SystemReader.setInstance(null); 19349aac325SShawn O. Pearce } 19449aac325SShawn O. Pearce 195a90b75b4SMatthias Sohn /** 196a90b75b4SMatthias Sohn * Increment the {@link #author} and {@link #committer} times. 197a90b75b4SMatthias Sohn */ tick()19849aac325SShawn O. Pearce protected void tick() { 199069040ddSTerry Parker mockSystemReader.tick(5 * 60); 200069040ddSTerry Parker final long now = mockSystemReader.getCurrentTime(); 20149aac325SShawn O. Pearce final int tz = mockSystemReader.getTimezone(now); 20249aac325SShawn O. Pearce 20349aac325SShawn O. Pearce author = new PersonIdent(author, now, tz); 20449aac325SShawn O. Pearce committer = new PersonIdent(committer, now, tz); 20549aac325SShawn O. Pearce } 20649aac325SShawn O. Pearce 20749aac325SShawn O. Pearce /** 20849aac325SShawn O. Pearce * Recursively delete a directory, failing the test if the delete fails. 20949aac325SShawn O. Pearce * 21049aac325SShawn O. Pearce * @param dir 21149aac325SShawn O. Pearce * the recursively directory to delete, if present. 21249aac325SShawn O. Pearce */ recursiveDelete(File dir)2136d370d83SHan-Wen Nienhuys protected void recursiveDelete(File dir) { 21436144e12SShawn Pearce recursiveDelete(dir, false, true); 21549aac325SShawn O. Pearce } 21649aac325SShawn O. Pearce recursiveDelete(final File dir, boolean silent, boolean failOnError)21736144e12SShawn Pearce private static boolean recursiveDelete(final File dir, 21836144e12SShawn Pearce boolean silent, boolean failOnError) { 21949aac325SShawn O. Pearce assert !(silent && failOnError); 220902935c3SMatthias Sohn int options = FileUtils.RECURSIVE | FileUtils.RETRY 221902935c3SMatthias Sohn | FileUtils.SKIP_MISSING; 222902935c3SMatthias Sohn if (silent) { 223902935c3SMatthias Sohn options |= FileUtils.IGNORE_ERRORS; 2246e03645aSCarsten Hammer } 225902935c3SMatthias Sohn try { 226902935c3SMatthias Sohn FileUtils.delete(dir, options); 227902935c3SMatthias Sohn } catch (IOException e) { 228902935c3SMatthias Sohn reportDeleteFailure(failOnError, dir, e); 229902935c3SMatthias Sohn return !failOnError; 23049aac325SShawn O. Pearce } 231902935c3SMatthias Sohn return true; 23249aac325SShawn O. Pearce } 23349aac325SShawn O. Pearce reportDeleteFailure(boolean failOnError, File f, Exception cause)234902935c3SMatthias Sohn private static void reportDeleteFailure(boolean failOnError, File f, 235902935c3SMatthias Sohn Exception cause) { 23636144e12SShawn Pearce String severity = failOnError ? "ERROR" : "WARNING"; 237902935c3SMatthias Sohn String msg = severity + ": Failed to delete " + f; 238902935c3SMatthias Sohn if (failOnError) { 23949aac325SShawn O. Pearce fail(msg); 240902935c3SMatthias Sohn } else { 24149aac325SShawn O. Pearce System.err.println(msg); 24249aac325SShawn O. Pearce } 243902935c3SMatthias Sohn cause.printStackTrace(new PrintStream(System.err)); 244902935c3SMatthias Sohn } 24549aac325SShawn O. Pearce 246a90b75b4SMatthias Sohn /** Constant <code>MOD_TIME=1</code> */ 24737a1e4beSChris Price public static final int MOD_TIME = 1; 24837a1e4beSChris Price 249a90b75b4SMatthias Sohn /** Constant <code>SMUDGE=2</code> */ 25037a1e4beSChris Price public static final int SMUDGE = 2; 25137a1e4beSChris Price 252a90b75b4SMatthias Sohn /** Constant <code>LENGTH=4</code> */ 25337a1e4beSChris Price public static final int LENGTH = 4; 25437a1e4beSChris Price 255a90b75b4SMatthias Sohn /** Constant <code>CONTENT_ID=8</code> */ 25637a1e4beSChris Price public static final int CONTENT_ID = 8; 25737a1e4beSChris Price 258a90b75b4SMatthias Sohn /** Constant <code>CONTENT=16</code> */ 25937a1e4beSChris Price public static final int CONTENT = 16; 26037a1e4beSChris Price 261a90b75b4SMatthias Sohn /** Constant <code>ASSUME_UNCHANGED=32</code> */ 26237a1e4beSChris Price public static final int ASSUME_UNCHANGED = 32; 26337a1e4beSChris Price 26437a1e4beSChris Price /** 26537a1e4beSChris Price * Represent the state of the index in one String. This representation is 26637a1e4beSChris Price * useful when writing tests which do assertions on the state of the index. 26737a1e4beSChris Price * By default information about path, mode, stage (if different from 0) is 26837a1e4beSChris Price * included. A bitmask controls which additional info about 26937a1e4beSChris Price * modificationTimes, smudge state and length is included. 27037a1e4beSChris Price * <p> 27137a1e4beSChris Price * The format of the returned string is described with this BNF: 27237a1e4beSChris Price * 27337a1e4beSChris Price * <pre> 27437a1e4beSChris Price * result = ( "[" path mode stage? time? smudge? length? sha1? content? "]" )* . 27537a1e4beSChris Price * mode = ", mode:" number . 27637a1e4beSChris Price * stage = ", stage:" number . 27737a1e4beSChris Price * time = ", time:t" timestamp-index . 27837a1e4beSChris Price * smudge = "" | ", smudged" . 27937a1e4beSChris Price * length = ", length:" number . 28037a1e4beSChris Price * sha1 = ", sha1:" hex-sha1 . 28137a1e4beSChris Price * content = ", content:" blob-data . 28237a1e4beSChris Price * </pre> 28337a1e4beSChris Price * 28437a1e4beSChris Price * 'stage' is only presented when the stage is different from 0. All 28537a1e4beSChris Price * reported time stamps are mapped to strings like "t0", "t1", ... "tn". The 28637a1e4beSChris Price * smallest reported time-stamp will be called "t0". This allows to write 28737a1e4beSChris Price * assertions against the string although the concrete value of the time 28837a1e4beSChris Price * stamps is unknown. 28937a1e4beSChris Price * 29037a1e4beSChris Price * @param repo 29137a1e4beSChris Price * the repository the index state should be determined for 29237a1e4beSChris Price * @param includedOptions 29337a1e4beSChris Price * a bitmask constructed out of the constants {@link #MOD_TIME}, 29437a1e4beSChris Price * {@link #SMUDGE}, {@link #LENGTH}, {@link #CONTENT_ID} and 29537a1e4beSChris Price * {@link #CONTENT} controlling which info is present in the 29637a1e4beSChris Price * resulting string. 29737a1e4beSChris Price * @return a string encoding the index state 29837a1e4beSChris Price * @throws IllegalStateException 29937a1e4beSChris Price * @throws IOException 30037a1e4beSChris Price */ indexState(Repository repo, int includedOptions)30137a1e4beSChris Price public static String indexState(Repository repo, int includedOptions) 30237a1e4beSChris Price throws IllegalStateException, IOException { 30337a1e4beSChris Price DirCache dc = repo.readDirCache(); 30437a1e4beSChris Price StringBuilder sb = new StringBuilder(); 30595e8264cSMatthias Sohn TreeSet<Instant> timeStamps = new TreeSet<>(); 30637a1e4beSChris Price 30737a1e4beSChris Price // iterate once over the dircache just to collect all time stamps 30837a1e4beSChris Price if (0 != (includedOptions & MOD_TIME)) { 30995e8264cSMatthias Sohn for (int i = 0; i < dc.getEntryCount(); ++i) { 31095e8264cSMatthias Sohn timeStamps.add(dc.getEntry(i).getLastModifiedInstant()); 31195e8264cSMatthias Sohn } 31237a1e4beSChris Price } 31337a1e4beSChris Price 31437a1e4beSChris Price // iterate again, now produce the result string 31537a1e4beSChris Price for (int i=0; i<dc.getEntryCount(); ++i) { 31637a1e4beSChris Price DirCacheEntry entry = dc.getEntry(i); 31737a1e4beSChris Price sb.append("["+entry.getPathString()+", mode:" + entry.getFileMode()); 31837a1e4beSChris Price int stage = entry.getStage(); 31937a1e4beSChris Price if (stage != 0) 32037a1e4beSChris Price sb.append(", stage:" + stage); 32137a1e4beSChris Price if (0 != (includedOptions & MOD_TIME)) { 32237a1e4beSChris Price sb.append(", time:t"+ 32395e8264cSMatthias Sohn timeStamps.headSet(entry.getLastModifiedInstant()) 32495e8264cSMatthias Sohn .size()); 32537a1e4beSChris Price } 32637a1e4beSChris Price if (0 != (includedOptions & SMUDGE)) 32737a1e4beSChris Price if (entry.isSmudged()) 32837a1e4beSChris Price sb.append(", smudged"); 32937a1e4beSChris Price if (0 != (includedOptions & LENGTH)) 33037a1e4beSChris Price sb.append(", length:" 33137a1e4beSChris Price + Integer.toString(entry.getLength())); 33237a1e4beSChris Price if (0 != (includedOptions & CONTENT_ID)) 33337a1e4beSChris Price sb.append(", sha1:" + ObjectId.toString(entry.getObjectId())); 33437a1e4beSChris Price if (0 != (includedOptions & CONTENT)) { 33537a1e4beSChris Price sb.append(", content:" 33637a1e4beSChris Price + new String(repo.open(entry.getObjectId(), 33730c6c754SDavid Pursehouse Constants.OBJ_BLOB).getCachedBytes(), UTF_8)); 33837a1e4beSChris Price } 33937a1e4beSChris Price if (0 != (includedOptions & ASSUME_UNCHANGED)) 34037a1e4beSChris Price sb.append(", assume-unchanged:" 34137a1e4beSChris Price + Boolean.toString(entry.isAssumeValid())); 34237a1e4beSChris Price sb.append("]"); 34337a1e4beSChris Price } 34437a1e4beSChris Price return sb.toString(); 34537a1e4beSChris Price } 34637a1e4beSChris Price 34737a1e4beSChris Price 34849aac325SShawn O. Pearce /** 34949aac325SShawn O. Pearce * Creates a new empty bare repository. 35049aac325SShawn O. Pearce * 351ee32ca22SDavid Pursehouse * @return the newly created bare repository, opened for access. The 352ee32ca22SDavid Pursehouse * repository will not be closed in {@link #tearDown()}; the caller 353ee32ca22SDavid Pursehouse * is responsible for closing it. 35449aac325SShawn O. Pearce * @throws IOException 35549aac325SShawn O. Pearce * the repository could not be created in the temporary area 35649aac325SShawn O. Pearce */ createBareRepository()35789d4a737SShawn O. Pearce protected FileRepository createBareRepository() throws IOException { 35849aac325SShawn O. Pearce return createRepository(true /* bare */); 35949aac325SShawn O. Pearce } 36049aac325SShawn O. Pearce 36149aac325SShawn O. Pearce /** 36249aac325SShawn O. Pearce * Creates a new empty repository within a new empty working directory. 36349aac325SShawn O. Pearce * 364ee32ca22SDavid Pursehouse * @return the newly created repository, opened for access. The repository 365ee32ca22SDavid Pursehouse * will not be closed in {@link #tearDown()}; the caller is 366ee32ca22SDavid Pursehouse * responsible for closing it. 36749aac325SShawn O. Pearce * @throws IOException 36849aac325SShawn O. Pearce * the repository could not be created in the temporary area 36949aac325SShawn O. Pearce */ createWorkRepository()37089d4a737SShawn O. Pearce protected FileRepository createWorkRepository() throws IOException { 37149aac325SShawn O. Pearce return createRepository(false /* not bare */); 37249aac325SShawn O. Pearce } 37349aac325SShawn O. Pearce 37449aac325SShawn O. Pearce /** 37549aac325SShawn O. Pearce * Creates a new empty repository. 37649aac325SShawn O. Pearce * 37749aac325SShawn O. Pearce * @param bare 37849aac325SShawn O. Pearce * true to create a bare repository; false to make a repository 37949aac325SShawn O. Pearce * within its working directory 380ee32ca22SDavid Pursehouse * @return the newly created repository, opened for access. The repository 381ee32ca22SDavid Pursehouse * will not be closed in {@link #tearDown()}; the caller is 382ee32ca22SDavid Pursehouse * responsible for closing it. 38349aac325SShawn O. Pearce * @throws IOException 38449aac325SShawn O. Pearce * the repository could not be created in the temporary area 38552923e9bSDavid Pursehouse * @since 5.3 38649aac325SShawn O. Pearce */ createRepository(boolean bare)38752923e9bSDavid Pursehouse protected FileRepository createRepository(boolean bare) 38853ad4373SMatthias Sohn throws IOException { 38952923e9bSDavid Pursehouse return createRepository(bare, false /* auto close */); 39053ad4373SMatthias Sohn } 39153ad4373SMatthias Sohn 39253ad4373SMatthias Sohn /** 39353ad4373SMatthias Sohn * Creates a new empty repository. 39453ad4373SMatthias Sohn * 39553ad4373SMatthias Sohn * @param bare 39653ad4373SMatthias Sohn * true to create a bare repository; false to make a repository 39753ad4373SMatthias Sohn * within its working directory 39853ad4373SMatthias Sohn * @param autoClose 39952923e9bSDavid Pursehouse * auto close the repository in {@link #tearDown()} 40053ad4373SMatthias Sohn * @return the newly created repository, opened for access 40153ad4373SMatthias Sohn * @throws IOException 40253ad4373SMatthias Sohn * the repository could not be created in the temporary area 40352923e9bSDavid Pursehouse * @deprecated use {@link #createRepository(boolean)} instead 40453ad4373SMatthias Sohn */ 40552923e9bSDavid Pursehouse @Deprecated createRepository(boolean bare, boolean autoClose)40653ad4373SMatthias Sohn public FileRepository createRepository(boolean bare, boolean autoClose) 40753ad4373SMatthias Sohn throws IOException { 408ea0f2f9eSMatthias Sohn File gitdir = createUniqueTestGitDir(bare); 40989d4a737SShawn O. Pearce FileRepository db = new FileRepository(gitdir); 41049aac325SShawn O. Pearce assertFalse(gitdir.exists()); 41135c00a7aSChristian Halstrick db.create(bare); 41253ad4373SMatthias Sohn if (autoClose) { 4135d0ca36dSDavid Pursehouse addRepoToClose(db); 41453ad4373SMatthias Sohn } 41549aac325SShawn O. Pearce return db; 41649aac325SShawn O. Pearce } 41749aac325SShawn O. Pearce 41849aac325SShawn O. Pearce /** 419c1525e2aSChristian Halstrick * Adds a repository to the list of repositories which is closed at the end 420c1525e2aSChristian Halstrick * of the tests 421c1525e2aSChristian Halstrick * 422c1525e2aSChristian Halstrick * @param r 423c1525e2aSChristian Halstrick * the repository to be closed 424c1525e2aSChristian Halstrick */ addRepoToClose(Repository r)425c1525e2aSChristian Halstrick public void addRepoToClose(Repository r) { 426c1525e2aSChristian Halstrick toClose.add(r); 427c1525e2aSChristian Halstrick } 428c1525e2aSChristian Halstrick 429ad74bbf9SAdrian Goerler /** 430ad74bbf9SAdrian Goerler * Creates a unique directory for a test 431ad74bbf9SAdrian Goerler * 432ad74bbf9SAdrian Goerler * @param name 433ad74bbf9SAdrian Goerler * a subdirectory 434ad74bbf9SAdrian Goerler * @return a unique directory for a test 435ad74bbf9SAdrian Goerler * @throws IOException 436ad74bbf9SAdrian Goerler */ createTempDirectory(String name)437ad74bbf9SAdrian Goerler protected File createTempDirectory(String name) throws IOException { 43836144e12SShawn Pearce File directory = new File(createTempFile(), name); 439ad74bbf9SAdrian Goerler FileUtils.mkdirs(directory); 440ad74bbf9SAdrian Goerler return directory.getCanonicalFile(); 441ad74bbf9SAdrian Goerler } 442ad74bbf9SAdrian Goerler 443c1525e2aSChristian Halstrick /** 444ea0f2f9eSMatthias Sohn * Creates a new unique directory for a test repository 445ea0f2f9eSMatthias Sohn * 446ea0f2f9eSMatthias Sohn * @param bare 447ea0f2f9eSMatthias Sohn * true for a bare repository; false for a repository with a 448ea0f2f9eSMatthias Sohn * working directory 449ea0f2f9eSMatthias Sohn * @return a unique directory for a test repository 450ea0f2f9eSMatthias Sohn * @throws IOException 451ea0f2f9eSMatthias Sohn */ createUniqueTestGitDir(boolean bare)452ea0f2f9eSMatthias Sohn protected File createUniqueTestGitDir(boolean bare) throws IOException { 45336144e12SShawn Pearce String gitdirName = createTempFile().getPath(); 454ad74bbf9SAdrian Goerler if (!bare) 455ad74bbf9SAdrian Goerler gitdirName += "/"; 45636144e12SShawn Pearce return new File(gitdirName + Constants.DOT_GIT); 457ea0f2f9eSMatthias Sohn } 458ea0f2f9eSMatthias Sohn 45936144e12SShawn Pearce /** 46036144e12SShawn Pearce * Allocates a new unique file path that does not exist. 46136144e12SShawn Pearce * <p> 46236144e12SShawn Pearce * Unlike the standard {@code File.createTempFile} the returned path does 46336144e12SShawn Pearce * not exist, but may be created by another thread in a race with the 46436144e12SShawn Pearce * caller. Good luck. 46536144e12SShawn Pearce * <p> 46636144e12SShawn Pearce * This method is inherently unsafe due to a race condition between creating 46736144e12SShawn Pearce * the name and the first use that reserves it. 46836144e12SShawn Pearce * 46936144e12SShawn Pearce * @return a unique path that does not exist. 47036144e12SShawn Pearce * @throws IOException 47136144e12SShawn Pearce */ createTempFile()472d60001c8SKetan Padegaonkar protected File createTempFile() throws IOException { 47336144e12SShawn Pearce File p = File.createTempFile("tmp_", "", tmp); 47436144e12SShawn Pearce if (!p.delete()) { 47536144e12SShawn Pearce throw new IOException("Cannot obtain unique path " + tmp); 47636144e12SShawn Pearce } 47736144e12SShawn Pearce return p; 478d60001c8SKetan Padegaonkar } 479d60001c8SKetan Padegaonkar 480ea0f2f9eSMatthias Sohn /** 48149aac325SShawn O. Pearce * Run a hook script in the repository, returning the exit status. 48249aac325SShawn O. Pearce * 48349aac325SShawn O. Pearce * @param db 48449aac325SShawn O. Pearce * repository the script should see in GIT_DIR environment 48549aac325SShawn O. Pearce * @param hook 48649aac325SShawn O. Pearce * path of the hook script to execute, must be executable file 48749aac325SShawn O. Pearce * type on this platform 48849aac325SShawn O. Pearce * @param args 48949aac325SShawn O. Pearce * arguments to pass to the hook script 49049aac325SShawn O. Pearce * @return exit status code of the invoked hook 49149aac325SShawn O. Pearce * @throws IOException 49249aac325SShawn O. Pearce * the hook could not be executed 49349aac325SShawn O. Pearce * @throws InterruptedException 49449aac325SShawn O. Pearce * the caller was interrupted before the hook completed 49549aac325SShawn O. Pearce */ runHook(final Repository db, final File hook, final String... args)49649aac325SShawn O. Pearce protected int runHook(final Repository db, final File hook, 49749aac325SShawn O. Pearce final String... args) throws IOException, InterruptedException { 49849aac325SShawn O. Pearce final String[] argv = new String[1 + args.length]; 49949aac325SShawn O. Pearce argv[0] = hook.getAbsolutePath(); 50049aac325SShawn O. Pearce System.arraycopy(args, 0, argv, 1, args.length); 50149aac325SShawn O. Pearce 50249aac325SShawn O. Pearce final Map<String, String> env = cloneEnv(); 50349aac325SShawn O. Pearce env.put("GIT_DIR", db.getDirectory().getAbsolutePath()); 50449aac325SShawn O. Pearce putPersonIdent(env, "AUTHOR", author); 50549aac325SShawn O. Pearce putPersonIdent(env, "COMMITTER", committer); 50649aac325SShawn O. Pearce 507203bd662SShawn O. Pearce final File cwd = db.getWorkTree(); 50849aac325SShawn O. Pearce final Process p = Runtime.getRuntime().exec(argv, toEnvArray(env), cwd); 50949aac325SShawn O. Pearce p.getOutputStream().close(); 51049aac325SShawn O. Pearce p.getErrorStream().close(); 51149aac325SShawn O. Pearce p.getInputStream().close(); 51249aac325SShawn O. Pearce return p.waitFor(); 51349aac325SShawn O. Pearce } 51449aac325SShawn O. Pearce putPersonIdent(final Map<String, String> env, final String type, final PersonIdent who)51549aac325SShawn O. Pearce private static void putPersonIdent(final Map<String, String> env, 51649aac325SShawn O. Pearce final String type, final PersonIdent who) { 51749aac325SShawn O. Pearce final String ident = who.toExternalString(); 51849aac325SShawn O. Pearce final String date = ident.substring(ident.indexOf("> ") + 2); 51949aac325SShawn O. Pearce env.put("GIT_" + type + "_NAME", who.getName()); 52049aac325SShawn O. Pearce env.put("GIT_" + type + "_EMAIL", who.getEmailAddress()); 52149aac325SShawn O. Pearce env.put("GIT_" + type + "_DATE", date); 52249aac325SShawn O. Pearce } 52349aac325SShawn O. Pearce 52449aac325SShawn O. Pearce /** 52549aac325SShawn O. Pearce * Create a string to a UTF-8 temporary file and return the path. 52649aac325SShawn O. Pearce * 52749aac325SShawn O. Pearce * @param body 52849aac325SShawn O. Pearce * complete content to write to the file. If the file should end 52949aac325SShawn O. Pearce * with a trailing LF, the string should end with an LF. 53049aac325SShawn O. Pearce * @return path of the temporary file created within the trash area. 53149aac325SShawn O. Pearce * @throws IOException 53249aac325SShawn O. Pearce * the file could not be written. 53349aac325SShawn O. Pearce */ write(String body)5346d370d83SHan-Wen Nienhuys protected File write(String body) throws IOException { 53536144e12SShawn Pearce final File f = File.createTempFile("temp", "txt", tmp); 53649aac325SShawn O. Pearce try { 53749aac325SShawn O. Pearce write(f, body); 53849aac325SShawn O. Pearce return f; 539c0268f89SCarsten Hammer } catch (Error | RuntimeException | IOException e) { 54049aac325SShawn O. Pearce f.delete(); 54149aac325SShawn O. Pearce throw e; 54249aac325SShawn O. Pearce } 54349aac325SShawn O. Pearce } 54449aac325SShawn O. Pearce 54549aac325SShawn O. Pearce /** 54649aac325SShawn O. Pearce * Write a string as a UTF-8 file. 54749aac325SShawn O. Pearce * 54849aac325SShawn O. Pearce * @param f 54949aac325SShawn O. Pearce * file to write the string to. Caller is responsible for making 55049aac325SShawn O. Pearce * sure it is in the trash directory or will otherwise be cleaned 55149aac325SShawn O. Pearce * up at the end of the test. If the parent directory does not 55249aac325SShawn O. Pearce * exist, the missing parent directories are automatically 55349aac325SShawn O. Pearce * created. 55449aac325SShawn O. Pearce * @param body 55549aac325SShawn O. Pearce * content to write to the file. 55649aac325SShawn O. Pearce * @throws IOException 55749aac325SShawn O. Pearce * the file could not be written. 55849aac325SShawn O. Pearce */ write(File f, String body)5596d370d83SHan-Wen Nienhuys protected void write(File f, String body) throws IOException { 560b649eaa9SDariusz Luksza JGitTestUtil.write(f, body); 56149aac325SShawn O. Pearce } 56249aac325SShawn O. Pearce 563a90b75b4SMatthias Sohn /** 564a90b75b4SMatthias Sohn * Read a file's content 565a90b75b4SMatthias Sohn * 566a90b75b4SMatthias Sohn * @param f 567a90b75b4SMatthias Sohn * the file 568a90b75b4SMatthias Sohn * @return the content of the file 569a90b75b4SMatthias Sohn * @throws IOException 570a90b75b4SMatthias Sohn */ read(File f)5716d370d83SHan-Wen Nienhuys protected String read(File f) throws IOException { 5729ea38173SRobin Stocker return JGitTestUtil.read(f); 57349aac325SShawn O. Pearce } 57449aac325SShawn O. Pearce toEnvArray(Map<String, String> env)5756d370d83SHan-Wen Nienhuys private static String[] toEnvArray(Map<String, String> env) { 57649aac325SShawn O. Pearce final String[] envp = new String[env.size()]; 57749aac325SShawn O. Pearce int i = 0; 5782062d623SAdrian Goerler for (Map.Entry<String, String> e : env.entrySet()) 57949aac325SShawn O. Pearce envp[i++] = e.getKey() + "=" + e.getValue(); 58049aac325SShawn O. Pearce return envp; 58149aac325SShawn O. Pearce } 58249aac325SShawn O. Pearce cloneEnv()58349aac325SShawn O. Pearce private static HashMap<String, String> cloneEnv() { 5843b444863SDavid Pursehouse return new HashMap<>(System.getenv()); 58549aac325SShawn O. Pearce } 58649aac325SShawn O. Pearce 58736144e12SShawn Pearce private static final class CleanupThread extends Thread { 58836144e12SShawn Pearce private static final CleanupThread me; 58936144e12SShawn Pearce static { 59036144e12SShawn Pearce me = new CleanupThread(); 59136144e12SShawn Pearce Runtime.getRuntime().addShutdownHook(me); 59249aac325SShawn O. Pearce } 593ad74bbf9SAdrian Goerler deleteOnShutdown(File tmp)59436144e12SShawn Pearce static void deleteOnShutdown(File tmp) { 59536144e12SShawn Pearce synchronized (me) { 59636144e12SShawn Pearce me.toDelete.add(tmp); 59736144e12SShawn Pearce } 59836144e12SShawn Pearce } 59936144e12SShawn Pearce removed(File tmp)60036144e12SShawn Pearce static void removed(File tmp) { 60136144e12SShawn Pearce synchronized (me) { 60236144e12SShawn Pearce me.toDelete.remove(tmp); 60336144e12SShawn Pearce } 60436144e12SShawn Pearce } 60536144e12SShawn Pearce 6063b444863SDavid Pursehouse private final List<File> toDelete = new ArrayList<>(); 60736144e12SShawn Pearce 60836144e12SShawn Pearce @Override run()60936144e12SShawn Pearce public void run() { 61036144e12SShawn Pearce // On windows accidentally open files or memory 61136144e12SShawn Pearce // mapped regions may prevent files from being deleted. 61236144e12SShawn Pearce // Suggesting a GC increases the likelihood that our 61336144e12SShawn Pearce // test repositories actually get removed after the 61436144e12SShawn Pearce // tests, even in the case of failure. 61536144e12SShawn Pearce System.gc(); 61636144e12SShawn Pearce synchronized (this) { 61736144e12SShawn Pearce boolean silent = false; 61836144e12SShawn Pearce boolean failOnError = false; 61936144e12SShawn Pearce for (File tmp : toDelete) 62036144e12SShawn Pearce recursiveDelete(tmp, silent, failOnError); 62136144e12SShawn Pearce } 62236144e12SShawn Pearce } 62336144e12SShawn Pearce } 62449aac325SShawn O. Pearce } 625