001package org.eclipse.aether.connector.basic; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import static org.junit.Assert.*; 023import static org.junit.Assume.*; 024 025import java.io.Closeable; 026import java.io.File; 027import java.io.FileOutputStream; 028import java.io.IOException; 029import java.io.OutputStream; 030import java.io.RandomAccessFile; 031import java.nio.channels.FileLock; 032import java.util.ArrayList; 033import java.util.List; 034import java.util.Locale; 035import java.util.concurrent.CountDownLatch; 036 037import org.eclipse.aether.internal.test.util.TestFileUtils; 038import org.eclipse.aether.internal.test.util.TestLoggerFactory; 039import org.junit.After; 040import org.junit.Before; 041import org.junit.Test; 042 043public class PartialFileTest 044{ 045 046 private static class StubRemoteAccessChecker 047 implements PartialFile.RemoteAccessChecker 048 { 049 050 Exception exception; 051 052 int invocations; 053 054 public void checkRemoteAccess() 055 throws Exception 056 { 057 invocations++; 058 if ( exception != null ) 059 { 060 throw exception; 061 } 062 } 063 064 } 065 066 private static class ConcurrentWriter 067 extends Thread 068 { 069 070 private final File dstFile; 071 072 private final File partFile; 073 074 private final File lockFile; 075 076 private final CountDownLatch locked; 077 078 private final int sleep; 079 080 volatile int length; 081 082 Exception error; 083 084 public ConcurrentWriter( File dstFile, int sleep, int length ) 085 throws InterruptedException 086 { 087 super( "ConcurrentWriter-" + dstFile.getAbsolutePath() ); 088 this.dstFile = dstFile; 089 partFile = new File( dstFile.getPath() + PartialFile.EXT_PART ); 090 lockFile = new File( partFile.getPath() + PartialFile.EXT_LOCK ); 091 this.sleep = sleep; 092 this.length = length; 093 locked = new CountDownLatch( 1 ); 094 start(); 095 locked.await(); 096 } 097 098 @Override 099 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}