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.RandomAccessFile;
30  import java.nio.channels.FileLock;
31  import java.util.ArrayList;
32  import java.util.List;
33  import java.util.Locale;
34  import java.util.concurrent.CountDownLatch;
35  
36  import org.eclipse.aether.internal.test.util.TestFileUtils;
37  import org.eclipse.aether.internal.test.util.TestLoggerFactory;
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             try
101             {
102                 RandomAccessFile raf = new RandomAccessFile( lockFile, "rw" );
103                 try
104                 {
105                     FileLock lock = raf.getChannel().lock( 0, 1, false );
106                     locked.countDown();
107                     FileOutputStream fos = new FileOutputStream( partFile );
108                     try
109                     {
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                             fos.write( 65 );
117                             fos.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                     finally
127                     {
128                         fos.close();
129                     }
130                     lock.release();
131                 }
132                 finally
133                 {
134                     raf.close();
135                     lockFile.delete();
136                 }
137             }
138             catch ( Exception e )
139             {
140                 error = e;
141             }
142         }
143 
144     }
145 
146     private static final boolean PROPER_LOCK_SUPPORT;
147 
148     static
149     {
150         String javaVersion = System.getProperty( "java.version" ).trim();
151         boolean notJava5 = !javaVersion.startsWith( "1.5." );
152         String osName = System.getProperty( "os.name" ).toLowerCase( Locale.ENGLISH );
153         boolean windows = osName.contains( "windows" );
154         PROPER_LOCK_SUPPORT = notJava5 || windows;
155     }
156 
157     private StubRemoteAccessChecker remoteAccessChecker;
158 
159     private File dstFile;
160 
161     private File partFile;
162 
163     private File lockFile;
164 
165     private List<Closeable> closeables;
166 
167     private PartialFile newPartialFile( long resumeThreshold, int requestTimeout )
168         throws Exception
169     {
170         PartialFile.Factory factory =
171             new PartialFile.Factory( resumeThreshold >= 0, resumeThreshold, requestTimeout,
172                                      new TestLoggerFactory().getLogger( "" ) );
173         PartialFile partFile = factory.newInstance( dstFile, remoteAccessChecker );
174         if ( partFile != null )
175         {
176             closeables.add( partFile );
177         }
178         return partFile;
179     }
180 
181     @Before
182     public void init()
183         throws Exception
184     {
185         closeables = new ArrayList<Closeable>();
186         remoteAccessChecker = new StubRemoteAccessChecker();
187         dstFile = TestFileUtils.createTempFile( "Hello World!" );
188         partFile = new File( dstFile.getPath() + PartialFile.EXT_PART );
189         lockFile = new File( partFile.getPath() + PartialFile.EXT_LOCK );
190     }
191 
192     @After
193     public void exit()
194     {
195         for ( Closeable closeable : closeables )
196         {
197             try
198             {
199                 closeable.close();
200             }
201             catch ( Exception e )
202             {
203                 e.printStackTrace();
204             }
205         }
206     }
207 
208     @Test
209     public void testCloseNonResumableFile()
210         throws Exception
211     {
212         PartialFile partialFile = newPartialFile( -1, 100 );
213         assertNotNull( partialFile );
214         assertNotNull( partialFile.getFile() );
215         assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() );
216         partialFile.close();
217         assertFalse( partialFile.getFile().getAbsolutePath(), partialFile.getFile().exists() );
218     }
219 
220     @Test
221     public void testCloseResumableFile()
222         throws Exception
223     {
224         PartialFile partialFile = newPartialFile( 0, 100 );
225         assertNotNull( partialFile );
226         assertNotNull( partialFile.getFile() );
227         assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() );
228         assertEquals( partFile, partialFile.getFile() );
229         assertTrue( lockFile.getAbsolutePath(), lockFile.isFile() );
230         partialFile.close();
231         assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() );
232         assertFalse( lockFile.getAbsolutePath(), lockFile.exists() );
233     }
234 
235     @Test
236     public void testResumableFileCreationError()
237         throws Exception
238     {
239         assertTrue( partFile.getAbsolutePath(), partFile.mkdirs() );
240         PartialFile partialFile = newPartialFile( 0, 100 );
241         assertNotNull( partialFile );
242         assertFalse( partialFile.isResume() );
243         assertFalse( lockFile.getAbsolutePath(), lockFile.exists() );
244     }
245 
246     @Test
247     public void testResumeThreshold()
248         throws Exception
249     {
250         PartialFile partialFile = newPartialFile( 0, 100 );
251         assertNotNull( partialFile );
252         assertTrue( partialFile.isResume() );
253         partialFile.close();
254         partialFile = newPartialFile( 1, 100 );
255         assertNotNull( partialFile );
256         assertFalse( partialFile.isResume() );
257         partialFile.close();
258     }
259 
260     @Test( timeout = 10000 )
261     public void testResumeConcurrently_RequestTimeout()
262         throws Exception
263     {
264         assumeTrue( PROPER_LOCK_SUPPORT );
265         ConcurrentWriter writer = new ConcurrentWriter( dstFile, 5 * 1000, 1 );
266         try
267         {
268             newPartialFile( 0, 1000 );
269             fail( "expected exception" );
270         }
271         catch ( Exception e )
272         {
273             assertTrue( e.getMessage().contains( "Timeout" ) );
274         }
275         writer.interrupt();
276         writer.join();
277     }
278 
279     @Test( timeout = 10000 )
280     public void testResumeConcurrently_AwaitCompletion_ConcurrentWriterSucceeds()
281         throws Exception
282     {
283         assumeTrue( PROPER_LOCK_SUPPORT );
284         assertTrue( dstFile.setLastModified( System.currentTimeMillis() - 60 * 1000 ) );
285         ConcurrentWriter writer = new ConcurrentWriter( dstFile, 100, 10 );
286         assertNull( newPartialFile( 0, 500 ) );
287         writer.join();
288         assertNull( writer.error );
289         assertEquals( 1, remoteAccessChecker.invocations );
290     }
291 
292     @Test( timeout = 10000 )
293     public void testResumeConcurrently_AwaitCompletion_ConcurrentWriterFails()
294         throws Exception
295     {
296         assumeTrue( PROPER_LOCK_SUPPORT );
297         assertTrue( dstFile.setLastModified( System.currentTimeMillis() - 60 * 1000 ) );
298         ConcurrentWriter writer = new ConcurrentWriter( dstFile, 100, -10 );
299         PartialFile partialFile = newPartialFile( 0, 500 );
300         assertNotNull( partialFile );
301         assertTrue( partialFile.isResume() );
302         writer.join();
303         assertNull( writer.error );
304         assertEquals( 1, remoteAccessChecker.invocations );
305     }
306 
307     @Test( timeout = 10000 )
308     public void testResumeConcurrently_CheckRemoteAccess()
309         throws Exception
310     {
311         assumeTrue( PROPER_LOCK_SUPPORT );
312         remoteAccessChecker.exception = new IOException( "missing" );
313         ConcurrentWriter writer = new ConcurrentWriter( dstFile, 1000, 1 );
314         try
315         {
316             newPartialFile( 0, 1000 );
317             fail( "expected exception" );
318         }
319         catch ( Exception e )
320         {
321             assertSame( remoteAccessChecker.exception, e );
322         }
323         writer.interrupt();
324         writer.join();
325     }
326 
327 }