1
2
3
4
5
6
7
8
9
10
11
12
13 package org.eclipse.jgit.junit;
14
15 import static java.nio.charset.StandardCharsets.UTF_8;
16 import static org.junit.Assert.assertFalse;
17 import static org.junit.Assert.fail;
18
19 import java.io.File;
20 import java.io.IOException;
21 import java.io.PrintStream;
22 import java.time.Instant;
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.HashMap;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.TreeSet;
31
32 import org.eclipse.jgit.dircache.DirCache;
33 import org.eclipse.jgit.dircache.DirCacheEntry;
34 import org.eclipse.jgit.internal.storage.file.FileRepository;
35 import org.eclipse.jgit.lib.ConfigConstants;
36 import org.eclipse.jgit.lib.Constants;
37 import org.eclipse.jgit.lib.ObjectId;
38 import org.eclipse.jgit.lib.PersonIdent;
39 import org.eclipse.jgit.lib.Repository;
40 import org.eclipse.jgit.lib.RepositoryCache;
41 import org.eclipse.jgit.storage.file.FileBasedConfig;
42 import org.eclipse.jgit.storage.file.WindowCacheConfig;
43 import org.eclipse.jgit.util.FS;
44 import org.eclipse.jgit.util.FileUtils;
45 import org.eclipse.jgit.util.SystemReader;
46 import org.junit.After;
47 import org.junit.Before;
48 import org.junit.Rule;
49 import org.junit.rules.TestName;
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69 public abstract class LocalDiskRepositoryTestCase {
70 private static final boolean useMMAP = "true".equals(System
71 .getProperty("jgit.junit.usemmap"));
72
73
74 protected PersonIdent author;
75
76
77 protected PersonIdent committer;
78
79
80
81
82
83 protected MockSystemReader mockSystemReader;
84
85 private final Set<Repository> toClose = new HashSet<>();
86 private File tmp;
87
88
89
90
91 @Rule
92 public TestName currentTest = new TestName();
93
94 private String getTestName() {
95 String name = currentTest.getMethodName();
96 name = name.replaceAll("[^a-zA-Z0-9]", "_");
97 name = name.replaceAll("__+", "_");
98 if (name.startsWith("_")) {
99 name = name.substring(1);
100 }
101 return name;
102 }
103
104
105
106
107
108
109 @Before
110 public void setUp() throws Exception {
111 tmp = File.createTempFile("jgit_" + getTestName() + '_', "_tmp");
112 CleanupThread.deleteOnShutdown(tmp);
113 if (!tmp.delete() || !tmp.mkdir()) {
114 throw new IOException("Cannot create " + tmp);
115 }
116 mockSystemReader = new MockSystemReader();
117 SystemReader.setInstance(mockSystemReader);
118
119
120
121
122
123 FS.getFileStoreAttributes(tmp.toPath().getParent());
124
125 FileBasedConfig jgitConfig = new FileBasedConfig(
126 new File(tmp, "jgitconfig"), FS.DETECTED);
127 FileBasedConfig systemConfig = new FileBasedConfig(jgitConfig,
128 new File(tmp, "systemgitconfig"), FS.DETECTED);
129 FileBasedConfig userConfig = new FileBasedConfig(systemConfig,
130 new File(tmp, "usergitconfig"), FS.DETECTED);
131
132
133
134 userConfig.setBoolean(ConfigConstants.CONFIG_GC_SECTION,
135 null, ConfigConstants.CONFIG_KEY_AUTODETACH, false);
136 userConfig.save();
137 mockSystemReader.setJGitConfig(jgitConfig);
138 mockSystemReader.setSystemGitConfig(systemConfig);
139 mockSystemReader.setUserGitConfig(userConfig);
140
141 ceilTestDirectories(getCeilings());
142
143 author = new PersonIdent("J. Author", "jauthor@example.com");
144 committer = new PersonIdent("J. Committer", "jcommitter@example.com");
145
146 final WindowCacheConfig c = new WindowCacheConfig();
147 c.setPackedGitLimit(128 * WindowCacheConfig.KB);
148 c.setPackedGitWindowSize(8 * WindowCacheConfig.KB);
149 c.setPackedGitMMAP(useMMAP);
150 c.setDeltaBaseCacheLimit(8 * WindowCacheConfig.KB);
151 c.install();
152 }
153
154
155
156
157
158
159 protected File getTemporaryDirectory() {
160 return tmp.getAbsoluteFile();
161 }
162
163
164
165
166
167
168 protected List<File> getCeilings() {
169 return Collections.singletonList(getTemporaryDirectory());
170 }
171
172 private void ceilTestDirectories(List<File> ceilings) {
173 mockSystemReader.setProperty(Constants.GIT_CEILING_DIRECTORIES_KEY, makePath(ceilings));
174 }
175
176 private static String makePath(List<?> objects) {
177 final StringBuilder stringBuilder = new StringBuilder();
178 for (Object object : objects) {
179 if (stringBuilder.length() > 0)
180 stringBuilder.append(File.pathSeparatorChar);
181 stringBuilder.append(object.toString());
182 }
183 return stringBuilder.toString();
184 }
185
186
187
188
189
190
191 @After
192 public void tearDown() throws Exception {
193 RepositoryCache.clear();
194 for (Repository r : toClose)
195 r.close();
196 toClose.clear();
197
198
199
200
201
202 if (useMMAP)
203 System.gc();
204 if (tmp != null)
205 recursiveDelete(tmp, false, true);
206 if (tmp != null && !tmp.exists())
207 CleanupThread.removed(tmp);
208
209 SystemReader.setInstance(null);
210 }
211
212
213
214
215 protected void tick() {
216 mockSystemReader.tick(5 * 60);
217 final long now = mockSystemReader.getCurrentTime();
218 final int tz = mockSystemReader.getTimezone(now);
219
220 author = new PersonIdent(author, now, tz);
221 committer = new PersonIdent(committer, now, tz);
222 }
223
224
225
226
227
228
229
230 protected void recursiveDelete(File dir) {
231 recursiveDelete(dir, false, true);
232 }
233
234 private static boolean recursiveDelete(final File dir,
235 boolean silent, boolean failOnError) {
236 assert !(silent && failOnError);
237 int options = FileUtils.RECURSIVE | FileUtils.RETRY
238 | FileUtils.SKIP_MISSING;
239 if (silent) {
240 options |= FileUtils.IGNORE_ERRORS;
241 }
242 try {
243 FileUtils.delete(dir, options);
244 } catch (IOException e) {
245 reportDeleteFailure(failOnError, dir, e);
246 return !failOnError;
247 }
248 return true;
249 }
250
251 private static void reportDeleteFailure(boolean failOnError, File f,
252 Exception cause) {
253 String severity = failOnError ? "ERROR" : "WARNING";
254 String msg = severity + ": Failed to delete " + f;
255 if (failOnError) {
256 fail(msg);
257 } else {
258 System.err.println(msg);
259 }
260 cause.printStackTrace(new PrintStream(System.err));
261 }
262
263
264 public static final int MOD_TIME = 1;
265
266
267 public static final int SMUDGE = 2;
268
269
270 public static final int LENGTH = 4;
271
272
273 public static final int CONTENT_ID = 8;
274
275
276 public static final int CONTENT = 16;
277
278
279 public static final int ASSUME_UNCHANGED = 32;
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318 public static String indexState(Repository repo, int includedOptions)
319 throws IllegalStateException, IOException {
320 DirCache dc = repo.readDirCache();
321 StringBuilder sb = new StringBuilder();
322 TreeSet<Instant> timeStamps = new TreeSet<>();
323
324
325 if (0 != (includedOptions & MOD_TIME)) {
326 for (int i = 0; i < dc.getEntryCount(); ++i) {
327 timeStamps.add(dc.getEntry(i).getLastModifiedInstant());
328 }
329 }
330
331
332 for (int i=0; i<dc.getEntryCount(); ++i) {
333 DirCacheEntry entry = dc.getEntry(i);
334 sb.append("["+entry.getPathString()+", mode:" + entry.getFileMode());
335 int stage = entry.getStage();
336 if (stage != 0)
337 sb.append(", stage:" + stage);
338 if (0 != (includedOptions & MOD_TIME)) {
339 sb.append(", time:t"+
340 timeStamps.headSet(entry.getLastModifiedInstant())
341 .size());
342 }
343 if (0 != (includedOptions & SMUDGE))
344 if (entry.isSmudged())
345 sb.append(", smudged");
346 if (0 != (includedOptions & LENGTH))
347 sb.append(", length:"
348 + Integer.toString(entry.getLength()));
349 if (0 != (includedOptions & CONTENT_ID))
350 sb.append(", sha1:" + ObjectId.toString(entry.getObjectId()));
351 if (0 != (includedOptions & CONTENT)) {
352 sb.append(", content:"
353 + new String(repo.open(entry.getObjectId(),
354 Constants.OBJ_BLOB).getCachedBytes(), UTF_8));
355 }
356 if (0 != (includedOptions & ASSUME_UNCHANGED))
357 sb.append(", assume-unchanged:"
358 + Boolean.toString(entry.isAssumeValid()));
359 sb.append("]");
360 }
361 return sb.toString();
362 }
363
364
365
366
367
368
369
370
371
372
373
374 protected FileRepository createBareRepository() throws IOException {
375 return createRepository(true );
376 }
377
378
379
380
381
382
383
384
385
386
387 protected FileRepository createWorkRepository() throws IOException {
388 return createRepository(false );
389 }
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404 protected FileRepository createRepository(boolean bare)
405 throws IOException {
406 return createRepository(bare, false );
407 }
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422 @Deprecated
423 public FileRepository createRepository(boolean bare, boolean autoClose)
424 throws IOException {
425 File gitdir = createUniqueTestGitDir(bare);
426 FileRepository db = new FileRepository(gitdir);
427 assertFalse(gitdir.exists());
428 db.create(bare);
429 if (autoClose) {
430 addRepoToClose(db);
431 }
432 return db;
433 }
434
435
436
437
438
439
440
441
442 public void addRepoToClose(Repository r) {
443 toClose.add(r);
444 }
445
446
447
448
449
450
451
452
453
454 protected File createTempDirectory(String name) throws IOException {
455 File directory = new File(createTempFile(), name);
456 FileUtils.mkdirs(directory);
457 return directory.getCanonicalFile();
458 }
459
460
461
462
463
464
465
466
467
468
469 protected File createUniqueTestGitDir(boolean bare) throws IOException {
470 String gitdirName = createTempFile().getPath();
471 if (!bare)
472 gitdirName += "/";
473 return new File(gitdirName + Constants.DOT_GIT);
474 }
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489 protected File createTempFile() throws IOException {
490 File p = File.createTempFile("tmp_", "", tmp);
491 if (!p.delete()) {
492 throw new IOException("Cannot obtain unique path " + tmp);
493 }
494 return p;
495 }
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513 protected int runHook(final Repository db, final File hook,
514 final String... args) throws IOException, InterruptedException {
515 final String[] argv = new String[1 + args.length];
516 argv[0] = hook.getAbsolutePath();
517 System.arraycopy(args, 0, argv, 1, args.length);
518
519 final Map<String, String> env = cloneEnv();
520 env.put("GIT_DIR", db.getDirectory().getAbsolutePath());
521 putPersonIdent(env, "AUTHOR", author);
522 putPersonIdent(env, "COMMITTER", committer);
523
524 final File cwd = db.getWorkTree();
525 final Process p = Runtime.getRuntime().exec(argv, toEnvArray(env), cwd);
526 p.getOutputStream().close();
527 p.getErrorStream().close();
528 p.getInputStream().close();
529 return p.waitFor();
530 }
531
532 private static void putPersonIdent(final Map<String, String> env,
533 final String type, final PersonIdent who) {
534 final String ident = who.toExternalString();
535 final String date = ident.substring(ident.indexOf("> ") + 2);
536 env.put("GIT_" + type + "_NAME", who.getName());
537 env.put("GIT_" + type + "_EMAIL", who.getEmailAddress());
538 env.put("GIT_" + type + "_DATE", date);
539 }
540
541
542
543
544
545
546
547
548
549
550
551 protected File write(String body) throws IOException {
552 final File f = File.createTempFile("temp", "txt", tmp);
553 try {
554 write(f, body);
555 return f;
556 } catch (Error | RuntimeException | IOException e) {
557 f.delete();
558 throw e;
559 }
560 }
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576 protected void write(File f, String body) throws IOException {
577 JGitTestUtil.write(f, body);
578 }
579
580
581
582
583
584
585
586
587
588 protected String read(File f) throws IOException {
589 return JGitTestUtil.read(f);
590 }
591
592 private static String[] toEnvArray(Map<String, String> env) {
593 final String[] envp = new String[env.size()];
594 int i = 0;
595 for (Map.Entry<String, String> e : env.entrySet())
596 envp[i++] = e.getKey() + "=" + e.getValue();
597 return envp;
598 }
599
600 private static HashMap<String, String> cloneEnv() {
601 return new HashMap<>(System.getenv());
602 }
603
604 private static final class CleanupThread extends Thread {
605 private static final CleanupThread me;
606 static {
607 me = new CleanupThread();
608 Runtime.getRuntime().addShutdownHook(me);
609 }
610
611 static void deleteOnShutdown(File tmp) {
612 synchronized (me) {
613 me.toDelete.add(tmp);
614 }
615 }
616
617 static void removed(File tmp) {
618 synchronized (me) {
619 me.toDelete.remove(tmp);
620 }
621 }
622
623 private final List<File> toDelete = new ArrayList<>();
624
625 @Override
626 public void run() {
627
628
629
630
631
632 System.gc();
633 synchronized (this) {
634 boolean silent = false;
635 boolean failOnError = false;
636 for (File tmp : toDelete)
637 recursiveDelete(tmp, silent, failOnError);
638 }
639 }
640 }
641 }