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.junit.After; 039import org.junit.Before; 040import org.junit.Test; 041 042public class PartialFileTest 043{ 044 045 private static class StubRemoteAccessChecker 046 implements PartialFile.RemoteAccessChecker 047 { 048 049 Exception exception; 050 051 int invocations; 052 053 public void checkRemoteAccess() 054 throws Exception 055 { 056 invocations++; 057 if ( exception != null ) 058 { 059 throw exception; 060 } 061 } 062 063 } 064 065 private static class ConcurrentWriter 066 extends Thread 067 { 068 069 private final File dstFile; 070 071 private final File partFile; 072 073 private final File lockFile; 074 075 private final CountDownLatch locked; 076 077 private final int sleep; 078 079 volatile int length; 080 081 Exception error; 082 083 public ConcurrentWriter( File dstFile, int sleep, int length ) 084 throws InterruptedException 085 { 086 super( "ConcurrentWriter-" + dstFile.getAbsolutePath() ); 087 this.dstFile = dstFile; 088 partFile = new File( dstFile.getPath() + PartialFile.EXT_PART ); 089 lockFile = new File( partFile.getPath() + PartialFile.EXT_LOCK ); 090 this.sleep = sleep; 091 this.length = length; 092 locked = new CountDownLatch( 1 ); 093 start(); 094 locked.await(); 095 } 096 097 @Override 098 public void run() 099 { 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}