add more control to indexState() return-value
[jgit.git] / org.eclipse.jgit.test / tst / org / eclipse / jgit / lib / RacyGitTests.java
blobc29f1a0d72d687e1841ddb3fdcf8fb9d43268c64
1 /*
2 * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
3 * and other copyright owners as documented in the project's IP log.
5 * This program and the accompanying materials are made available
6 * under the terms of the Eclipse Distribution License v1.0 which
7 * accompanies this distribution, is reproduced below, and is
8 * available at http://www.eclipse.org/org/documents/edl-v10.php
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
14 * conditions are met:
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
19 * - Redistributions in binary form must reproduce the above
20 * copyright notice, this list of conditions and the following
21 * disclaimer in the documentation and/or other materials provided
22 * with the distribution.
24 * - Neither the name of the Eclipse Foundation, Inc. nor the
25 * names of its contributors may be used to endorse or promote
26 * products derived from this software without specific prior
27 * written permission.
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43 package org.eclipse.jgit.lib;
45 import java.io.File;
46 import java.io.FileNotFoundException;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.util.TreeSet;
51 import org.eclipse.jgit.dircache.DirCacheBuilder;
52 import org.eclipse.jgit.dircache.DirCacheEntry;
53 import org.eclipse.jgit.treewalk.FileTreeIterator;
54 import org.eclipse.jgit.treewalk.FileTreeIteratorWithTimeControl;
55 import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
57 public class RacyGitTests extends RepositoryTestCase {
58 public void testIterator() throws IllegalStateException, IOException,
59 InterruptedException {
60 TreeSet<Long> modTimes = new TreeSet<Long>();
61 File lastFile = null;
62 for (int i = 0; i < 10; i++) {
63 lastFile = new File(db.getWorkTree(), "0." + i);
64 lastFile.createNewFile();
65 if (i == 5)
66 fsTick(lastFile);
68 modTimes.add(fsTick(lastFile));
69 for (int i = 0; i < 10; i++) {
70 lastFile = new File(db.getWorkTree(), "1." + i);
71 lastFile.createNewFile();
73 modTimes.add(fsTick(lastFile));
74 for (int i = 0; i < 10; i++) {
75 lastFile = new File(db.getWorkTree(), "2." + i);
76 lastFile.createNewFile();
77 if (i % 4 == 0)
78 fsTick(lastFile);
80 FileTreeIteratorWithTimeControl fileIt = new FileTreeIteratorWithTimeControl(
81 db, modTimes);
82 NameConflictTreeWalk tw = new NameConflictTreeWalk(db);
83 tw.reset();
84 tw.addTree(fileIt);
85 tw.setRecursive(true);
86 FileTreeIterator t;
87 long t0 = 0;
88 for (int i = 0; i < 10; i++) {
89 assertTrue(tw.next());
90 t = tw.getTree(0, FileTreeIterator.class);
91 if (i == 0)
92 t0 = t.getEntryLastModified();
93 else
94 assertEquals(t0, t.getEntryLastModified());
96 long t1 = 0;
97 for (int i = 0; i < 10; i++) {
98 assertTrue(tw.next());
99 t = tw.getTree(0, FileTreeIterator.class);
100 if (i == 0) {
101 t1 = t.getEntryLastModified();
102 assertTrue(t1 > t0);
103 } else
104 assertEquals(t1, t.getEntryLastModified());
106 long t2 = 0;
107 for (int i = 0; i < 10; i++) {
108 assertTrue(tw.next());
109 t = tw.getTree(0, FileTreeIterator.class);
110 if (i == 0) {
111 t2 = t.getEntryLastModified();
112 assertTrue(t2 > t1);
113 } else
114 assertEquals(t2, t.getEntryLastModified());
118 public void testRacyGitDetection() throws IOException,
119 IllegalStateException, InterruptedException {
120 TreeSet<Long> modTimes = new TreeSet<Long>();
121 File lastFile;
123 // wait to ensure that modtimes of the file doesn't match last index
124 // file modtime
125 modTimes.add(fsTick(db.getIndexFile()));
127 // create two files
128 addToWorkDir("a", "a");
129 lastFile = addToWorkDir("b", "b");
131 // wait to ensure that file-modTimes and therefore index entry modTime
132 // doesn't match the modtime of index-file after next persistance
133 modTimes.add(fsTick(lastFile));
135 // now add both files to the index. No racy git expected
136 addToIndex(modTimes);
138 assertEquals(
139 "[a, mode:100644, time:t0, length:1, sha1:2e65efe2a145dda7ee51d1741299f848e5bf752e]" +
140 "[b, mode:100644, time:t0, length:1, sha1:63d8dbd40c23542e740659a7168a0ce3138ea748]",
141 indexState(SMUDGE | MOD_TIME | LENGTH | CONTENT_ID));
143 // Remember the last modTime of index file. All modifications times of
144 // further modification are translated to this value so it looks that
145 // files have been modified in the same time slot as the index file
146 modTimes.add(Long.valueOf(db.getIndexFile().lastModified()));
148 // modify one file
149 addToWorkDir("a", "a2");
150 // now update the index the index. 'a' has to be racily clean -- because
151 // it's modification time is exactly the same as the previous index file
152 // mod time.
153 addToIndex(modTimes);
155 db.readDirCache();
156 // although racily clean a should not be reported as being dirty
157 assertEquals(
158 "[a, mode:100644, time:t1, smudged, length:0]" +
159 "[b, mode:100644, time:t0, length:1]",
160 indexState(SMUDGE|MOD_TIME|LENGTH));
164 * Waits until it is guaranteed that the filesystem timer (used e.g. for
165 * lastModified) has a value greater than the lastmodified time of the given
166 * file. This is done by touch a file, reading the lastmodified and sleeping
167 * attribute sleeping
169 * @param lastFile
170 * @return return the last measured value of the filesystem timer which is
171 * greater than then the lastmodification time of lastfile.
172 * @throws InterruptedException
173 * @throws IOException
175 public static long fsTick(File lastFile) throws InterruptedException,
176 IOException {
177 long sleepTime = 1;
178 File tmp = File.createTempFile("FileTreeIteratorWithTimeControl", null);
179 try {
180 long startTime = (lastFile == null) ? tmp.lastModified() : lastFile
181 .lastModified();
182 long actTime = tmp.lastModified();
183 while (actTime <= startTime) {
184 Thread.sleep(sleepTime);
185 sleepTime *= 5;
186 tmp.setLastModified(System.currentTimeMillis());
187 actTime = tmp.lastModified();
189 return actTime;
190 } finally {
191 tmp.delete();
195 private void addToIndex(TreeSet<Long> modTimes)
196 throws FileNotFoundException, IOException {
197 DirCacheBuilder builder = db.lockDirCache().builder();
198 FileTreeIterator fIt = new FileTreeIteratorWithTimeControl(
199 db, modTimes);
200 DirCacheEntry dce;
201 while (!fIt.eof()) {
202 dce = new DirCacheEntry(fIt.getEntryPathString());
203 dce.setFileMode(fIt.getEntryFileMode());
204 dce.setLastModified(fIt.getEntryLastModified());
205 dce.setLength((int) fIt.getEntryLength());
206 dce.setObjectId(fIt.getEntryObjectId());
207 builder.add(dce);
208 fIt.next(1);
210 builder.commit();
213 private File addToWorkDir(String path, String content) throws IOException {
214 File f = new File(db.getWorkTree(), path);
215 FileOutputStream fos = new FileOutputStream(f);
216 try {
217 fos.write(content.getBytes(Constants.CHARACTER_ENCODING));
218 return f;
219 } finally {
220 fos.close();