| package org.eclipse.aether.connector.basic; |
| |
| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| import static org.junit.Assert.*; |
| import static org.junit.Assume.*; |
| |
| import java.io.Closeable; |
| import java.io.File; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.io.RandomAccessFile; |
| import java.nio.channels.FileLock; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.concurrent.CountDownLatch; |
| |
| import org.eclipse.aether.internal.test.util.TestFileUtils; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| public class PartialFileTest |
| { |
| |
| private static class StubRemoteAccessChecker |
| implements PartialFile.RemoteAccessChecker |
| { |
| |
| Exception exception; |
| |
| int invocations; |
| |
| public void checkRemoteAccess() |
| throws Exception |
| { |
| invocations++; |
| if ( exception != null ) |
| { |
| throw exception; |
| } |
| } |
| |
| } |
| |
| private static class ConcurrentWriter |
| extends Thread |
| { |
| |
| private final File dstFile; |
| |
| private final File partFile; |
| |
| private final File lockFile; |
| |
| private final CountDownLatch locked; |
| |
| private final int sleep; |
| |
| volatile int length; |
| |
| Exception error; |
| |
| public ConcurrentWriter( File dstFile, int sleep, int length ) |
| throws InterruptedException |
| { |
| super( "ConcurrentWriter-" + dstFile.getAbsolutePath() ); |
| this.dstFile = dstFile; |
| partFile = new File( dstFile.getPath() + PartialFile.EXT_PART ); |
| lockFile = new File( partFile.getPath() + PartialFile.EXT_LOCK ); |
| this.sleep = sleep; |
| this.length = length; |
| locked = new CountDownLatch( 1 ); |
| start(); |
| locked.await(); |
| } |
| |
| @Override |
| public void run() |
| { |
| RandomAccessFile raf = null; |
| FileLock lock = null; |
| OutputStream out = null; |
| try |
| { |
| raf = new RandomAccessFile( lockFile, "rw" ); |
| lock = raf.getChannel().lock( 0, 1, false ); |
| locked.countDown(); |
| out = new FileOutputStream( partFile ); |
| for ( int i = 0, n = Math.abs( length ); i < n; i++ ) |
| { |
| for ( long start = System.currentTimeMillis(); System.currentTimeMillis() - start < sleep; ) |
| { |
| Thread.sleep( 10 ); |
| } |
| out.write( 65 ); |
| out.flush(); |
| System.out.println( " " + System.currentTimeMillis() + " Wrote byte " + ( i + 1 ) + "/" |
| + n ); |
| } |
| if ( length >= 0 && !dstFile.setLastModified( System.currentTimeMillis() ) ) |
| { |
| throw new IOException( "Could not update destination file" ); |
| } |
| |
| out.close(); |
| out = null; |
| lock.release(); |
| lock = null; |
| raf.close(); |
| raf = null; |
| } |
| catch ( Exception e ) |
| { |
| error = e; |
| } |
| finally |
| { |
| try |
| { |
| if ( out != null ) |
| { |
| out.close(); |
| } |
| } |
| catch ( final IOException e ) |
| { |
| // Suppressed due to an exception already thrown in the try block. |
| } |
| finally |
| { |
| try |
| { |
| if ( lock != null ) |
| { |
| lock.release(); |
| } |
| } |
| catch ( final IOException e ) |
| { |
| // Suppressed due to an exception already thrown in the try block. |
| } |
| finally |
| { |
| try |
| { |
| if ( raf != null ) |
| { |
| raf.close(); |
| } |
| } |
| catch ( final IOException e ) |
| { |
| // Suppressed due to an exception already thrown in the try block. |
| } |
| finally |
| { |
| if ( !lockFile.delete() ) |
| { |
| lockFile.deleteOnExit(); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| } |
| |
| private static final boolean PROPER_LOCK_SUPPORT; |
| |
| static |
| { |
| String javaVersion = System.getProperty( "java.version" ).trim(); |
| boolean notJava5 = !javaVersion.startsWith( "1.5." ); |
| String osName = System.getProperty( "os.name" ).toLowerCase( Locale.ENGLISH ); |
| boolean windows = osName.contains( "windows" ); |
| PROPER_LOCK_SUPPORT = notJava5 || windows; |
| } |
| |
| private StubRemoteAccessChecker remoteAccessChecker; |
| |
| private File dstFile; |
| |
| private File partFile; |
| |
| private File lockFile; |
| |
| private List<Closeable> closeables; |
| |
| private PartialFile newPartialFile( long resumeThreshold, int requestTimeout ) |
| throws Exception |
| { |
| PartialFile.Factory factory = |
| new PartialFile.Factory( resumeThreshold >= 0L, resumeThreshold, requestTimeout ); |
| PartialFile partFile = factory.newInstance( dstFile, remoteAccessChecker ); |
| if ( partFile != null ) |
| { |
| closeables.add( partFile ); |
| } |
| return partFile; |
| } |
| |
| @Before |
| public void init() |
| throws Exception |
| { |
| closeables = new ArrayList<>(); |
| remoteAccessChecker = new StubRemoteAccessChecker(); |
| dstFile = TestFileUtils.createTempFile( "Hello World!" ); |
| partFile = new File( dstFile.getPath() + PartialFile.EXT_PART ); |
| lockFile = new File( partFile.getPath() + PartialFile.EXT_LOCK ); |
| } |
| |
| @After |
| public void exit() |
| { |
| for ( Closeable closeable : closeables ) |
| { |
| try |
| { |
| closeable.close(); |
| } |
| catch ( Exception e ) |
| { |
| e.printStackTrace(); |
| } |
| } |
| } |
| |
| @Test |
| public void testCloseNonResumableFile() |
| throws Exception |
| { |
| PartialFile partialFile = newPartialFile( -1, 100 ); |
| assertNotNull( partialFile ); |
| assertNotNull( partialFile.getFile() ); |
| assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() ); |
| partialFile.close(); |
| assertFalse( partialFile.getFile().getAbsolutePath(), partialFile.getFile().exists() ); |
| } |
| |
| @Test |
| public void testCloseResumableFile() |
| throws Exception |
| { |
| PartialFile partialFile = newPartialFile( 0, 100 ); |
| assertNotNull( partialFile ); |
| assertNotNull( partialFile.getFile() ); |
| assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() ); |
| assertEquals( partFile, partialFile.getFile() ); |
| assertTrue( lockFile.getAbsolutePath(), lockFile.isFile() ); |
| partialFile.close(); |
| assertTrue( partialFile.getFile().getAbsolutePath(), partialFile.getFile().isFile() ); |
| assertFalse( lockFile.getAbsolutePath(), lockFile.exists() ); |
| } |
| |
| @Test |
| public void testResumableFileCreationError() |
| throws Exception |
| { |
| assertTrue( partFile.getAbsolutePath(), partFile.mkdirs() ); |
| PartialFile partialFile = newPartialFile( 0, 100 ); |
| assertNotNull( partialFile ); |
| assertFalse( partialFile.isResume() ); |
| assertFalse( lockFile.getAbsolutePath(), lockFile.exists() ); |
| } |
| |
| @Test |
| public void testResumeThreshold() |
| throws Exception |
| { |
| PartialFile partialFile = newPartialFile( 0, 100 ); |
| assertNotNull( partialFile ); |
| assertTrue( partialFile.isResume() ); |
| partialFile.close(); |
| partialFile = newPartialFile( 1, 100 ); |
| assertNotNull( partialFile ); |
| assertFalse( partialFile.isResume() ); |
| partialFile.close(); |
| } |
| |
| @Test( timeout = 10000L ) |
| public void testResumeConcurrently_RequestTimeout() |
| throws Exception |
| { |
| assumeTrue( PROPER_LOCK_SUPPORT ); |
| ConcurrentWriter writer = new ConcurrentWriter( dstFile, 5 * 1000, 1 ); |
| try |
| { |
| newPartialFile( 0, 1000 ); |
| fail( "expected exception" ); |
| } |
| catch ( Exception e ) |
| { |
| assertTrue( e.getMessage().contains( "Timeout" ) ); |
| } |
| writer.interrupt(); |
| writer.join(); |
| } |
| |
| @Test( timeout = 10000L ) |
| public void testResumeConcurrently_AwaitCompletion_ConcurrentWriterSucceeds() |
| throws Exception |
| { |
| assumeTrue( PROPER_LOCK_SUPPORT ); |
| assertTrue( dstFile.setLastModified( System.currentTimeMillis() - 60L * 1000L ) ); |
| ConcurrentWriter writer = new ConcurrentWriter( dstFile, 100, 10 ); |
| assertNull( newPartialFile( 0, 500 ) ); |
| writer.join(); |
| assertNull( writer.error ); |
| assertEquals( 1, remoteAccessChecker.invocations ); |
| } |
| |
| @Test( timeout = 10000L ) |
| public void testResumeConcurrently_AwaitCompletion_ConcurrentWriterFails() |
| throws Exception |
| { |
| assumeTrue( PROPER_LOCK_SUPPORT ); |
| assertTrue( dstFile.setLastModified( System.currentTimeMillis() - 60L * 1000L ) ); |
| ConcurrentWriter writer = new ConcurrentWriter( dstFile, 100, -10 ); |
| PartialFile partialFile = newPartialFile( 0, 500 ); |
| assertNotNull( partialFile ); |
| assertTrue( partialFile.isResume() ); |
| writer.join(); |
| assertNull( writer.error ); |
| assertEquals( 1, remoteAccessChecker.invocations ); |
| } |
| |
| @Test( timeout = 10000L ) |
| public void testResumeConcurrently_CheckRemoteAccess() |
| throws Exception |
| { |
| assumeTrue( PROPER_LOCK_SUPPORT ); |
| remoteAccessChecker.exception = new IOException( "missing" ); |
| ConcurrentWriter writer = new ConcurrentWriter( dstFile, 1000, 1 ); |
| try |
| { |
| newPartialFile( 0, 1000 ); |
| fail( "expected exception" ); |
| } |
| catch ( Exception e ) |
| { |
| assertSame( remoteAccessChecker.exception, e ); |
| } |
| writer.interrupt(); |
| writer.join(); |
| } |
| |
| } |