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