Skip to content

Commit df84ca1

Browse files
authored
Merge pull request #344 from cryptomator/feature/fix-use-token-tests
Rework UseToken test
2 parents 8efb6e2 + 853445a commit df84ca1

1 file changed

Lines changed: 81 additions & 55 deletions

File tree

src/test/java/org/cryptomator/cryptofs/inuse/RealUseTokenTest.java

Lines changed: 81 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.junit.jupiter.api.RepeatedTest;
1212
import org.junit.jupiter.api.Test;
1313
import org.junit.jupiter.api.io.TempDir;
14+
import org.opentest4j.AssertionFailedError;
1415

1516
import java.io.ByteArrayInputStream;
1617
import java.io.IOException;
@@ -25,8 +26,9 @@
2526
import java.util.Properties;
2627
import java.util.concurrent.ConcurrentHashMap;
2728
import java.util.concurrent.ConcurrentMap;
28-
import java.util.concurrent.Executor;
29+
import java.util.concurrent.ExecutorService;
2930
import java.util.concurrent.Executors;
31+
import java.util.concurrent.TimeUnit;
3032

3133
import static org.mockito.ArgumentMatchers.any;
3234
import static org.mockito.ArgumentMatchers.eq;
@@ -40,12 +42,12 @@ public class RealUseTokenTest {
4042
private ConcurrentMap<Path, RealUseToken> useTokens;
4143
private Cryptor cryptor;
4244
private RealUseToken.EncryptionDecorator encWrapper;
43-
private Executor tokenPersistor;
45+
private ExecutorService tokenPersistor;
4446
@TempDir
4547
Path tmpDir;
4648
private WatchService watchService;
4749
private static final int CREATION_DELAY_MILLIS = 1000;
48-
private static final Duration FILE_OPERATION_DELAY = Duration.ofMillis(CREATION_DELAY_MILLIS -100L); //allow some leeway
50+
private static final Duration FILE_OPERATION_DELAY = Duration.ofMillis(CREATION_DELAY_MILLIS - 100L); //allow some leeway
4951
private static final Duration FILE_OPERATION_MAX = FILE_OPERATION_DELAY.plusMillis(2000L);
5052

5153
@BeforeEach
@@ -66,54 +68,50 @@ public void afterEach() {
6668
} catch (IOException _) {
6769
//no-op
6870
}
69-
}
7071

71-
@Test
72-
@DisplayName("After X seconds of token creation, a new file is created")
73-
public void testFileCreation() {
74-
var filePath = tmpDir.resolve("inUse.file");
75-
try (var token = new RealUseToken(filePath, "test3000", cryptor, useTokens, tokenPersistor, CREATION_DELAY_MILLIS, StandardOpenOption.CREATE_NEW, encWrapper)) {
76-
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX).until(() -> Files.exists(filePath));
77-
Assertions.assertTrue(Files.exists(filePath));
72+
try {
73+
tokenPersistor.shutdown();
74+
if (!tokenPersistor.awaitTermination(3000, TimeUnit.MILLISECONDS)) {
75+
tokenPersistor.shutdownNow();
76+
}
77+
} catch (InterruptedException e) {
78+
Thread.currentThread().interrupt();
7879
}
79-
Assertions.assertTrue(Files.notExists(filePath));
8080
}
8181

82-
@RepeatedTest(20)
83-
@DisplayName("The properties file contains required keys with valid content")
84-
public void testFileContent() throws IOException {
82+
private static void assertInUseFile(String expectedOwner, Path filePath) throws AssertionFailedError {
83+
var props = new Properties();
84+
var rawProps = Assertions.assertDoesNotThrow(() -> Files.readAllBytes(filePath));
85+
Assertions.assertDoesNotThrow(() -> props.load(new ByteArrayInputStream(rawProps)));
86+
Assertions.assertEquals(expectedOwner, props.getProperty(UseToken.OWNER_KEY));
87+
Assertions.assertDoesNotThrow(() -> Instant.parse(props.getProperty(UseToken.LASTUPDATED_KEY)));
88+
}
89+
90+
@RepeatedTest(5)
91+
@DisplayName("Creating a token creates valid inUse file, deletes file on close and removes itself from token map")
92+
public void testValidFileContent() throws IOException {
8593
var filePath = tmpDir.resolve("inUse.file");
8694
try (var token = new RealUseToken(filePath, "test3000", cryptor, useTokens, tokenPersistor, CREATION_DELAY_MILLIS, StandardOpenOption.CREATE_NEW, encWrapper)) {
87-
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX).until(() -> Files.exists(filePath));
88-
Awaitility.await().pollDelay(Duration.ofMillis(10)).until(() -> true);
89-
90-
var props = new Properties();
91-
var rawProps = Files.readAllBytes(filePath);
92-
props.load(new ByteArrayInputStream(rawProps));
93-
Assertions.assertEquals("test3000", props.getProperty(UseToken.OWNER_KEY));
94-
Assertions.assertDoesNotThrow(() -> Instant.parse(props.getProperty(UseToken.LASTUPDATED_KEY)));
95+
useTokens.put(filePath, token);
96+
Awaitility.await().atLeast(FILE_OPERATION_DELAY) //
97+
.atMost(FILE_OPERATION_MAX) //
98+
.untilAsserted(() -> assertInUseFile("test3000", filePath));
9599
}
100+
Assertions.assertTrue(Files.notExists(filePath));
101+
Assertions.assertNull(useTokens.get(filePath));
96102
}
97103

98104
@Test
99-
@DisplayName("After X seconds of token creation, a file is updated")
105+
@DisplayName("After X seconds of token creation, a file is updated/stolen")
100106
public void testFileSteal() throws IOException {
101107
var filePath = tmpDir.resolve("inUse.file");
102108
Files.createFile(filePath);
103-
var watchKey = tmpDir.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);
104-
var fileTime = Files.getLastModifiedTime(filePath);
105109

106110
try (var token = new RealUseToken(filePath, "test3000", cryptor, useTokens, tokenPersistor, CREATION_DELAY_MILLIS, StandardOpenOption.TRUNCATE_EXISTING, encWrapper)) {
107-
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX).until(() -> fileTime.compareTo(Files.getLastModifiedTime(filePath)) < 0);
108-
var events = watchKey.pollEvents();
109-
var createEvent = events.stream().filter(e -> e.kind().equals(StandardWatchEventKinds.ENTRY_MODIFY)).findAny();
110-
Assertions.assertTrue(createEvent.isPresent());
111-
createEvent.ifPresent(e -> {
112-
Assertions.assertTrue(filePath.endsWith((Path) e.context()));
113-
});
111+
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX) //
112+
.untilAsserted(() -> assertInUseFile("test3000", filePath));
114113
}
115114
Assertions.assertTrue(Files.notExists(filePath));
116-
Assertions.assertNull(useTokens.get(filePath));
117115
}
118116

119117
@Test
@@ -123,10 +121,9 @@ public void testFileStealFails() throws IOException {
123121
var watchKey = tmpDir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
124122

125123
try (var token = new RealUseToken(filePath, "test3000", cryptor, useTokens, tokenPersistor, CREATION_DELAY_MILLIS, StandardOpenOption.TRUNCATE_EXISTING, encWrapper)) {
126-
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX).until(token::isClosed);
124+
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX) //
125+
.until(token::isClosed);
127126
Assertions.assertTrue(Files.notExists(filePath));
128-
Assertions.assertTrue(token.isClosed());
129-
Assertions.assertNull(useTokens.get(filePath));
130127
}
131128
MatcherAssert.assertThat(watchKey.pollEvents(), Matchers.empty());
132129
}
@@ -137,12 +134,14 @@ public void testTokenCloseBeforeFileOperation() throws IOException {
137134
var filePath = tmpDir.resolve("inUse.file");
138135
var watchKey = tmpDir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
139136

140-
try (var token = new RealUseToken(filePath, "test3000", cryptor, useTokens, tokenPersistor, CREATION_DELAY_MILLIS, StandardOpenOption.CREATE_NEW, encWrapper)) {
141-
Assertions.assertTrue(Files.notExists(filePath));
137+
RealUseToken token;
138+
try (var t = new RealUseToken(filePath, "test3000", cryptor, useTokens, tokenPersistor, CREATION_DELAY_MILLIS, StandardOpenOption.CREATE_NEW, encWrapper)) {
139+
token = t;
140+
Assertions.assertFalse(t.isClosed());
142141
}
143-
Awaitility.await().pollDelay(FILE_OPERATION_MAX).timeout(FILE_OPERATION_MAX.multipliedBy(2)).until(() -> true);
142+
Assertions.assertTrue(token.isClosed());
143+
Awaitility.await().pollDelay(FILE_OPERATION_MAX).until(() -> true);
144144
Assertions.assertTrue(Files.notExists(filePath));
145-
Assertions.assertNull(useTokens.get(filePath));
146145
MatcherAssert.assertThat(watchKey.pollEvents(), Matchers.empty());
147146
}
148147

@@ -154,15 +153,18 @@ public void testMoveToBefore() throws IOException {
154153
var watchKey = tmpDir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
155154

156155
try (var token = new RealUseToken(filePath, "test3000", cryptor, useTokens, tokenPersistor, CREATION_DELAY_MILLIS, StandardOpenOption.CREATE_NEW, encWrapper)) {
156+
useTokens.put(filePath, token);
157157
token.moveToInternal(targetPath);
158158

159+
Assertions.assertNull(useTokens.get(filePath));
160+
Assertions.assertNotNull(useTokens.get(targetPath));
159161
//no file operation after move
160162
MatcherAssert.assertThat(watchKey.pollEvents(), Matchers.empty());
161-
// target file will be created
162-
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX).until(() -> Files.exists(targetPath));
163-
//orginal filePath does not exist, target exists
163+
// target file exists
164+
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX) //
165+
.untilAsserted(() -> assertInUseFile("test3000", targetPath));
166+
//original filePath does not exist
164167
Assertions.assertTrue(Files.notExists(filePath));
165-
Assertions.assertTrue(Files.exists(targetPath));
166168

167169
//only targetPath was created
168170
var events = watchKey.pollEvents();
@@ -173,7 +175,6 @@ public void testMoveToBefore() throws IOException {
173175
Assertions.assertTrue(targetPath.endsWith((Path) e.context()));
174176
});
175177
}
176-
Assertions.assertNull(useTokens.get(filePath));
177178
Assertions.assertNull(useTokens.get(targetPath));
178179
}
179180

@@ -184,7 +185,9 @@ public void testMoveToAfter() {
184185
var targetPath = tmpDir.resolve("inUseMove2.file");
185186

186187
try (var token = new RealUseToken(filePath, "test3000", cryptor, useTokens, tokenPersistor, CREATION_DELAY_MILLIS, StandardOpenOption.CREATE_NEW, encWrapper)) {
187-
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX).until(() -> Files.exists(filePath));
188+
useTokens.put(filePath, token);
189+
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX) //
190+
.untilAsserted(() -> assertInUseFile("test3000", filePath));
188191

189192
token.moveToInternal(targetPath);
190193

@@ -195,26 +198,47 @@ public void testMoveToAfter() {
195198
Assertions.assertNull(useTokens.get(filePath));
196199
Assertions.assertNotNull(useTokens.get(targetPath));
197200
}
198-
Assertions.assertNull(useTokens.get(filePath));
199201
Assertions.assertNull(useTokens.get(targetPath));
200202
}
201203

202204
@Test
203-
@DisplayName("Moving does nothing on closed token")
204-
public void testMoveToClosed() throws IOException {
205+
@DisplayName("Moving does nothing on never-persisted, closed token")
206+
public void testMoveToNeverPersistedClosed() throws IOException {
205207
var filePath = tmpDir.resolve("inUse.file");
206208
var targetPath = tmpDir.resolve("inUse2.file");
207209
var watchKey = tmpDir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
208210

209211
try (var token = new RealUseToken(filePath, "test3000", cryptor, useTokens, tokenPersistor, CREATION_DELAY_MILLIS, StandardOpenOption.CREATE_NEW, encWrapper)) {
210212
token.close();
211-
Awaitility.await().pollDelay(FILE_OPERATION_MAX).timeout(FILE_OPERATION_MAX.multipliedBy(2)).until(() -> true);
212213

214+
token.moveToInternal(targetPath);
215+
216+
Awaitility.await().pollDelay(FILE_OPERATION_DELAY).until(() -> true); //give some time to ensure no event was triggered
217+
MatcherAssert.assertThat(watchKey.pollEvents(), Matchers.empty());
218+
Assertions.assertNull(useTokens.get(targetPath));
219+
}
220+
}
221+
222+
@Test
223+
@DisplayName("Moving does nothing on persisted-but-closed token")
224+
public void testMoveToClosed() throws IOException {
225+
var filePath = tmpDir.resolve("inUse.file");
226+
var targetPath = tmpDir.resolve("inUse2.file");
227+
var watchKey = tmpDir.register(watchService, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);
228+
229+
try (var token = new RealUseToken(filePath, "test3000", cryptor, useTokens, tokenPersistor, CREATION_DELAY_MILLIS, StandardOpenOption.CREATE_NEW, encWrapper)) {
230+
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX) //
231+
.untilAsserted(() -> assertInUseFile("test3000", filePath));
232+
watchKey.pollEvents(); //clear watchEvents
233+
token.close();
234+
Awaitility.await().atMost(FILE_OPERATION_MAX) //
235+
.untilAsserted(() -> Assertions.assertTrue(Files.notExists(filePath)));
236+
watchKey.pollEvents(); //drain events
213237

214238
token.moveToInternal(targetPath);
215239

240+
Awaitility.await().pollDelay(FILE_OPERATION_DELAY).until(() -> true); //give some time to ensure no event was triggered
216241
MatcherAssert.assertThat(watchKey.pollEvents(), Matchers.empty());
217-
Assertions.assertNull(useTokens.get(filePath));
218242
Assertions.assertNull(useTokens.get(targetPath));
219243
}
220244
}
@@ -225,13 +249,15 @@ public void testFileRefresh() throws IOException {
225249
var filePath = tmpDir.resolve("inUse.file");
226250

227251
try (var token = new RealUseToken(filePath, "test3000", cryptor, useTokens, tokenPersistor, CREATION_DELAY_MILLIS, StandardOpenOption.CREATE_NEW, encWrapper)) {
228-
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX).until(() -> Files.exists(filePath));
252+
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX) //
253+
.untilAsserted(() -> assertInUseFile("test3000", filePath));
229254

230255
var props = new Properties();
231256
var rawProps = Files.readAllBytes(filePath);
232257
props.load(new ByteArrayInputStream(rawProps));
233258
var oldLastUpdated = Instant.parse(props.getProperty(UseToken.LASTUPDATED_KEY));
234259

260+
Awaitility.await().pollDelay(FILE_OPERATION_DELAY).until(() -> true);
235261
token.refresh();
236262

237263
var props2 = new Properties();
@@ -250,8 +276,8 @@ public void testFileRefreshWrongLastModified() throws IOException {
250276
var filePath = tmpDir.resolve("inUse.file");
251277

252278
try (var token = new RealUseToken(filePath, "test3000", cryptor, useTokens, tokenPersistor, CREATION_DELAY_MILLIS, StandardOpenOption.CREATE_NEW, encWrapper)) {
253-
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX).until(() -> Files.exists(filePath));
254-
Awaitility.await().pollDelay(Duration.ofMillis(10)).until(() -> true);
279+
Awaitility.await().atLeast(FILE_OPERATION_DELAY).atMost(FILE_OPERATION_MAX) //
280+
.untilAsserted(() -> assertInUseFile("test3000", filePath));
255281

256282
Files.setLastModifiedTime(filePath, FileTime.from(Instant.ofEpochMilli(0)));
257283

0 commit comments

Comments
 (0)