View Javadoc
1   package org.eclipse.aether.connector.basic;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   * 
12   *  http://www.apache.org/licenses/LICENSE-2.0
13   * 
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import static org.junit.Assert.*;
23  import static org.junit.Assume.*;
24  
25  import java.io.Closeable;
26  import java.io.File;
27  import java.io.FileOutputStream;
28  import java.io.IOException;
29  import java.io.OutputStream;
30  import java.io.RandomAccessFile;
31  import java.nio.channels.FileLock;
32  import java.util.ArrayList;
33  import java.util.List;
34  import java.util.Locale;
35  import java.util.concurrent.CountDownLatch;
36  
37  import org.eclipse.aether.internal.test.util.TestFileUtils;
38  import org.junit.After;
39  import org.junit.Before;
40  import org.junit.Test;
41  
42  public class PartialFileTest
43  {
44  
45      private static class StubRemoteAccessChecker
46          implements PartialFile.RemoteAccessChecker
47      {
48  
49          Exception exception;
50  
51          int invocations;
52  
53          public void checkRemoteAccess()
54              throws Exception
55          {
56              invocations++;
57              if ( exception != null )
58              {
59                  throw exception;
60              }
61          }
62  
63      }
64  
65      private static class ConcurrentWriter
66          extends Thread
67      {
68  
69          private final File dstFile;
70  
71          private final File partFile;
72  
73          private final File lockFile;
74  
75          private final CountDownLatch locked;
76  
77          private final int sleep;
78  
79          volatile int length;
80  
81          Exception error;
82  
83          public ConcurrentWriter( File dstFile, int sleep, int length )
84              throws InterruptedException
85          {
86              super( "ConcurrentWriter-" + dstFile.getAbsolutePath() );
87              this.dstFile = dstFile;
88              partFile = new File( dstFile.getPath() + PartialFile.EXT_PART );
89              lockFile = new File( partFile.getPath() + PartialFile.EXT_LOCK );
90              this.sleep = sleep;
91              this.length = length;
92              locked = new CountDownLatch( 1 );
93              start();
94              locked.await();
95          }
96  
97          @Override
98          public void run()
99          {
100             RandomAccessFile raf = null;
101             FileLock lock = null;
102             OutputStream out = null;
103             try
104             {
105                 raf = new RandomAccessFile( lockFile, "rw" );
106                 lock = raf.getChannel().lock( 0, 1, false );
107                 locked.countDown();
108                 out = new FileOutputStream( partFile );
109                 for ( int i = 0, n = Math.abs( length ); i < n; i++ )
110                 {
111                     for ( long start = System.currentTimeMillis(); System.currentTimeMillis() - start < sleep; )
112                     {
113                         Thread.sleep( 10 );
114                     }
115                     out.write( 65 );
116                     out.flush();
117                     System.out.println( "  " + System.currentTimeMillis() + " Wrote byte " + ( i + 1 ) + "/"
118                                             + n );
119                 }
120                 if ( length >= 0 && !dstFile.setLastModified( System.currentTimeMillis() ) )
121                 {
122                     throw new IOException( "Could not update destination file" );
123                 }
124 
125                 out.close();
126                 out = null;
127                 lock.release();
128                 lock = null;
129                 raf.close();
130                 raf = null;
131             }
132             catch ( Exception e )
133             {
134                 error = e;
135             }
136             finally
137             {
138                 try
139                 {
140                     if ( out != null )
141                     {
142                         out.close();
143                     }
144                 }
145                 catch ( final IOException e )
146                 {
147                     // Suppressed due to an exception already thrown in the try block.
148                 }
149                 finally
150                 {
151                     try
152                     {
153                         if ( lock != null )
154                         {
155                             lock.release();
156                         }
157                     }
158                     catch ( final IOException e )
159                     {
160                         // Suppressed due to an exception already thrown in the try block.
161                     }
162                     finally
163                     {
164                         try
165                         {
166                             if ( raf != null )
167                             {
168                                 raf.close();
169                             }
170                         }
171                         catch ( final IOException e )
172                         {
173                             // Suppressed due to an exception already thrown in the try block.
174                         }
175                         finally
176                         {
177                             if ( !lockFile.delete() )
178                             {
179                                 lockFile.deleteOnExit();
180                             }
181                         }
182                     }
183                 }
184             }
185         }
186 
187     }
188 
189     private static final boolean PROPER_LOCK_SUPPORT;
190 
191     static
192     {
193         String javaVersion = System.getProperty( "java.version" ).trim();
194         boolean notJava5 = !javaVersion.startsWith( "1.5." );
195         String osName = System.getProperty( "os.name" ).toLowerCase( Locale.ENGLISH );
196         boolean windows = osName.contains( "windows" );
197         PROPER_LOCK_SUPPORT = notJava5 || windows;
198     }
199 
200     private StubRemoteAccessChecker remoteAccessChecker;
201 
202     private File dstFile;
203 
204     private File partFile;
205 
206     private File lockFile;
207 
208     private List<Closeable> closeables;
209 
210     private PartialFile newPartialFile( long resumeThreshold, int requestTimeout )
211         throws Exception
212     {
213         PartialFile.Factory factory =
214             new PartialFile.Factory( resumeThreshold >= 0L, resumeThreshold, requestTimeout );
215         PartialFile partFile = factory.newInstance( dstFile, remoteAccessChecker );
216         if ( partFile != null )
217         {
218             closeables.add( partFile );
219         }
220         return partFile;
221     }
222 
223     @Before
224     public void init()
225         throws Exception
226     {
227         closeables = new ArrayList<>();
228         remoteAccessChecker = new StubRemoteAccessChecker();
229         dstFile = TestFileUtils.createTempFile( "Hello World!" );
230         partFile = new File( dstFile.getPath() + PartialFile.EXT_PART );
231         lockFile = new File( partFile.getPath() + PartialFile.EXT_LOCK );
232     }
233 
234     @After
235     public void exit()
236     {
237         for ( Closeable closeable : closeables )
238         {
239             try
240             {
241                 closeable.close();
242             }
243             catch ( Exception e )
244             {
245                 e.printStackTrace();
246             }
247         }
248     }
249 
250     @Test
251     public void testCloseNonResumableFile()
252         throws Exception
253     {
254         PartialFile partialFile = newPartialFile( -1, 100 );
255         assertNotNull( partialFile );
256         assertNotNull( partialFile.getFile() );
257         assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() );
258         partialFile.close();
259         assertFalse( partialFile.getFile().getAbsolutePath(), partialFile.getFile().exists() );
260     }
261 
262     @Test
263     public void testCloseResumableFile()
264         throws Exception
265     {
266         PartialFile partialFile = newPartialFile( 0, 100 );
267         assertNotNull( partialFile );
268         assertNotNull( partialFile.getFile() );
269         assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() );
270         assertEquals( partFile, partialFile.getFile() );
271         assertTrue( lockFile.getAbsolutePath(), lockFile.isFile() );
272         partialFile.close();
273         assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() );
274         assertFalse( lockFile.getAbsolutePath(), lockFile.exists() );
275     }
276 
277     @Test
278     public void testResumableFileCreationError()
279         throws Exception
280     {
281         assertTrue( partFile.getAbsolutePath(), partFile.mkdirs() );
282         PartialFile partialFile = newPartialFile( 0, 100 );
283         assertNotNull( partialFile );
284         assertFalse( partialFile.isResume() );
285         assertFalse( lockFile.getAbsolutePath(), lockFile.exists() );
286     }
287 
288     @Test
289     public void testResumeThreshold()
290         throws Exception
291     {
292         PartialFile partialFile = newPartialFile( 0, 100 );
293         assertNotNull( partialFile );
294         assertTrue( partialFile.isResume() );
295         partialFile.close();
296         partialFile = newPartialFile( 1, 100 );
297         assertNotNull( partialFile );
298         assertFalse( partialFile.isResume() );
299         partialFile.close();
300     }
301 
302     @Test( timeout = 10000L )
303     public void testResumeConcurrently_RequestTimeout()
304         throws Exception
305     {
306         assumeTrue( PROPER_LOCK_SUPPORT );
307         ConcurrentWriter writer = new ConcurrentWriter( dstFile, 5 * 1000, 1 );
308         try
309         {
310             newPartialFile( 0, 1000 );
311             fail( "expected exception" );
312         }
313         catch ( Exception e )
314         {
315             assertTrue( e.getMessage().contains( "Timeout" ) );
316         }
317         writer.interrupt();
318         writer.join();
319     }
320 
321     @Test( timeout = 10000L )
322     public void testResumeConcurrently_AwaitCompletion_ConcurrentWriterSucceeds()
323         throws Exception
324     {
325         assumeTrue( PROPER_LOCK_SUPPORT );
326         assertTrue( dstFile.setLastModified( System.currentTimeMillis() - 60L * 1000L ) );
327         ConcurrentWriter writer = new ConcurrentWriter( dstFile, 100, 10 );
328         assertNull( newPartialFile( 0, 500 ) );
329         writer.join();
330         assertNull( writer.error );
331         assertEquals( 1, remoteAccessChecker.invocations );
332     }
333 
334     @Test( timeout = 10000L )
335     public void testResumeConcurrently_AwaitCompletion_ConcurrentWriterFails()
336         throws Exception
337     {
338         assumeTrue( PROPER_LOCK_SUPPORT );
339         assertTrue( dstFile.setLastModified( System.currentTimeMillis() - 60L * 1000L ) );
340         ConcurrentWriter writer = new ConcurrentWriter( dstFile, 100, -10 );
341         PartialFile partialFile = newPartialFile( 0, 500 );
342         assertNotNull( partialFile );
343         assertTrue( partialFile.isResume() );
344         writer.join();
345         assertNull( writer.error );
346         assertEquals( 1, remoteAccessChecker.invocations );
347     }
348 
349     @Test( timeout = 10000L )
350     public void testResumeConcurrently_CheckRemoteAccess()
351         throws Exception
352     {
353         assumeTrue( PROPER_LOCK_SUPPORT );
354         remoteAccessChecker.exception = new IOException( "missing" );
355         ConcurrentWriter writer = new ConcurrentWriter( dstFile, 1000, 1 );
356         try
357         {
358             newPartialFile( 0, 1000 );
359             fail( "expected exception" );
360         }
361         catch ( Exception e )
362         {
363             assertSame( remoteAccessChecker.exception, e );
364         }
365         writer.interrupt();
366         writer.join();
367     }
368 
369 }