1111import org .junit .jupiter .api .RepeatedTest ;
1212import org .junit .jupiter .api .Test ;
1313import org .junit .jupiter .api .io .TempDir ;
14+ import org .opentest4j .AssertionFailedError ;
1415
1516import java .io .ByteArrayInputStream ;
1617import java .io .IOException ;
2526import java .util .Properties ;
2627import java .util .concurrent .ConcurrentHashMap ;
2728import java .util .concurrent .ConcurrentMap ;
28- import java .util .concurrent .Executor ;
29+ import java .util .concurrent .ExecutorService ;
2930import java .util .concurrent .Executors ;
31+ import java .util .concurrent .TimeUnit ;
3032
3133import static org .mockito .ArgumentMatchers .any ;
3234import 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