blob: bb6530e6d097471a39a0994eee27c9371a5ae64f [file] [log] [blame]
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();
}
}