/*
* Copyright (C) 2010 Toni Menzel
* Copyright (C) 2019 Devin Avery
*
* Licensed 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.
*/
package org.ops4j.pax.url.mvn.internal;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.expect;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.powermock.api.easymock.PowerMock.replay;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Properties;
import org.apache.maven.settings.Settings;
import org.easymock.Capture;
import org.easymock.IAnswer;
import org.eclipse.aether.repository.LocalRepository;
import org.eclipse.aether.repository.RemoteRepository;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.ops4j.pax.url.mvn.ServiceConstants;
import org.ops4j.pax.url.mvn.internal.config.MavenConfiguration;
import org.ops4j.pax.url.mvn.internal.config.MavenConfigurationImpl;
import org.ops4j.pax.url.mvn.internal.config.MavenRepositoryURL;
import org.ops4j.util.property.PropertiesPropertyResolver;
import org.powermock.api.easymock.PowerMock;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Simply playing with mvn api.
*
* NOTE: For some of our tests which deal with file order checking we need
* to leverage a more powerful mocking framework. This is required because
* we need to make the order in which files are returned from File.listFiles()
* predictable so we can ensure that our production code works as expected.
*/
//Because we are changing class loader logic we need to run with the PowerMock
//test runner instead of the default junit runner.
@RunWith(PowerMockRunner.class)
//This is the class in which we want to override creation of File objects.
//The intercept only happens in the tests where we tell it too.
@PrepareForTest( {MavenRepositoryURL.class})
//The following ignores are required to allow Logger classes to load up
//during unit testing without errors.
@PowerMockIgnore({"javax.xml.*",
"org.xml.sax.*",
"org.w3c.dom.*",
"org.springframework.context.*",
"org.apache.log4j.*",
"javax.xml.parsers.*",
"org.apache.http.conn.ssl.*"})
public class AetherMultiTest {
private static Logger LOG = LoggerFactory.getLogger( AetherMultiTest.class );
@Test
public void resolveArtifactUsingMulti()
throws Exception
{
AetherBasedResolver aetherBasedResolver =
new AetherBasedResolver(
getDummyConfigRemoveRepo("repomulti" ));
aetherBasedResolver.resolve( "ant", "ant", "", "jar", "1.5.1" );
aetherBasedResolver.close();
}
@Test
public void resolveArtifactUsingMultiIgnoreFiles()
throws Exception
{
AetherBasedResolver aetherBasedResolver =
new AetherBasedResolver(
getDummyConfigRemoveRepo("repomulti_with_file"));
aetherBasedResolver.resolve( "ant", "ant", "", "jar", "1.5.1" );
aetherBasedResolver.close();
}
/**
* Validate that the @multi forces the files to be returned in a predictable
* order (namely alphabetical)
* @throws Exception
*/
@Test
public void resolveArtifactUsingMultiAssertOrder()
throws Exception
{
//Tell PowerMock to override the "File.listFiles()" methods
forceFileToReturnListFilesInReverseOrder();
//The test: ensure this multi repo returns folders in expected order.
AetherBasedResolver aetherBasedResolver =
new AetherBasedResolver(
getDummyConfigRemoveRepo("repomulti_ordered"));
List<RemoteRepository> repositories =
aetherBasedResolver.getRepositories();
assertEquals("Expected only 2 repositories returned",
2, repositories.size());
assertTrue("Expected mykar1 first",
repositories.get(0).getUrl().contains("mykar1"));
assertFalse("Didn't expect mykar2 in first url",
repositories.get(0).getUrl().contains("mykar2"));
assertTrue("Expected mykar2 second",
repositories.get(1).getUrl().contains("mykar2"));
assertFalse("Didn't expect mykar1 in second url",
repositories.get(1).getUrl().contains("mykar1"));
aetherBasedResolver.close();
}
@Test
public void resolveArtifactSelectDefaultRepositories() throws Exception
{
//Tell PowerMock to override the "File.listFiles()" methods
forceFileToReturnListFilesInReverseOrder();
//The test: ensure this multi repo returns folders in expected order.
AetherBasedResolver aetherBasedResolver =
new AetherBasedResolver(
getDummyConfig("repomulti_ordered",
ServiceConstants.PROPERTY_DEFAULT_REPOSITORIES));
List<LocalRepository> repositories =
aetherBasedResolver.selectDefaultRepositories();
assertEquals("Expected only 2 repositories returned",
2, repositories.size());
assertTrue("Expected mykar1 first",
repositories.get(0)
.getBasedir()
.getAbsolutePath().contains("mykar1"));
assertFalse("Didn't expect mykar2 in first url",
repositories.get(0)
.getBasedir()
.getAbsolutePath().contains("mykar2"));
assertTrue("Expected mykar2 second",
repositories.get(1)
.getBasedir()
.getAbsolutePath().contains("mykar2"));
assertFalse("Didn't expect mykar1 in second url",
repositories.get(1)
.getBasedir()
.getAbsolutePath().contains("mykar1"));
aetherBasedResolver.close();
}
/**
* Using PowerMock, we override the creation of File objects to return
* our Mocked File object. Our mocked File is the same as a file Object,
* except it returns the names of files to the "listFiles()" method in a
* predictable (reverse alphabetica) order.
* @throws Exception
*/
private void forceFileToReturnListFilesInReverseOrder() throws Exception {
/**
* By creating here we get default class loaded which is the normal
* java.io.File
*/
final UnmodifiedFileFactory fileFactory = new UnmodifiedFileFactory(){
@Override
public File newFile(String p) {
return new File(p);
}
};
/**
* We are using PowerMock to basically intercept all "new" calls
* to java.io.File. We do this because we want to interject a partial
* mock that provides a predicatableBAD order to the file names returned
* from listFiles, so we can thus make sure that our sorting logic works.
*/
final Capture<String> createCapture = new Capture<String>();
PowerMock.expectNew(File.class, new Class<?>[]{String.class},
capture(createCapture) ).andStubAnswer(
new IAnswer<File>() {
@Override
public File answer() throws Throwable {
return createMockFile(fileFactory, createCapture.getValue());
}
}
);
/** Because we are intercept "new" calls, need to replay the class **/
replay(File.class);
}
/**
* PowerMock intercepts all calls to "new File" so that we
* can change the default behavior.
*
* The problem is we only want to change the behavior slightly in our
* tests but still call the real method. In order to create an unmodified
* File object in our mock, we need to create an inner class that has an
* unmodified class loader.
* @author Devin Avery
*
*/
private static interface UnmodifiedFileFactory{
public File newFile(String p);
}
/**
* Sorts the files in reverse alphabetical order by their name().
* @param source File array to reverse sort.
*/
private static void reverseSortFiles( File[] source ){
Arrays.sort(source, new Comparator<File>(){
@Override
public int compare(File arg0, File arg1) {
return arg0.getName().compareTo(arg1.getName()) * -1;
}
});
}
/**
* Creates a mock java.io.File object which is the same as the original
* java.io.File, EXCEPT it returns calls to listFiles() in a predictable,
* reverse alphabetical order.
*
* This is necessary since java.io.listFiles() doesn't guarantee the order
* which files are returned and thus we can't truly know if the order
* we got back is because we have working code, or just got lucky. By
* mocking out the behavior to always return files opposite of what we
* want we can now guarantee the code fix.
* @param factory The file factory which is capable of creating unmodified
* files (i.e. instantiated BEFORE powermock modifies classloader)
* @param fileName The argument for the File object.
* @return A java.io.File object which is the same as the origin java.io.File
* object except that listFiles is guaranteed to return files in reverse
* alphabetical order.
*/
private File createMockFile(UnmodifiedFileFactory factory, final String fileName) {
final File realFile = factory.newFile(fileName);
File mockFile = PowerMock.createPartialMock(File.class,
new String[]{"listFiles"},
fileName );
final Capture<FilenameFilter> filenameFilter = new Capture<FilenameFilter>();
expect(mockFile.listFiles(capture(filenameFilter))).andStubAnswer(
new IAnswer<File[]>(){
@Override
public File[] answer() throws Throwable {
File[] listFiles = realFile.listFiles(filenameFilter.getValue());
reverseSortFiles(listFiles);
return listFiles;
}
}
);
final Capture<FileFilter> filter = new Capture<FileFilter>();
expect(mockFile.listFiles(capture(filter))).andStubAnswer(
new IAnswer<File[]>(){
@Override
public File[] answer() throws Throwable {
File[] listFiles = realFile.listFiles(filter.getValue());
reverseSortFiles(listFiles);
return listFiles;
}
}
);
expect(mockFile.listFiles()).andStubAnswer(new IAnswer<File[]>() {
@Override
public File[] answer() throws Throwable {
File[] listFiles = realFile.listFiles();
reverseSortFiles(listFiles);
return listFiles;
}
});
replay(mockFile);
return mockFile;
}
private MavenConfiguration getDummyConfigRemoveRepo( String folder_name )
throws IOException
{
return getDummyConfig(folder_name,
ServiceConstants.PROPERTY_REPOSITORIES);
}
private MavenConfiguration getDummyConfig( String folder_name,
String repoType )
throws IOException
{
Properties p = new Properties();
String localRepo = getCache().toURI().toASCIIString();
p.setProperty( ServiceConstants.PID + "." +
ServiceConstants.PROPERTY_LOCAL_REPOSITORY, localRepo );
File target = new File("target/test-classes/" + folder_name);
assertTrue("Can not find test repo " + target.toURI().toString(),
target.isDirectory());
String multiRepo = target.toURI().toString() + "@id=multitest@multi";
p.setProperty( ServiceConstants.PID + "." + repoType, multiRepo);
MavenConfigurationImpl config = new MavenConfigurationImpl(
new PropertiesPropertyResolver( p ), ServiceConstants.PID );
Settings settings = new Settings();
settings.setLocalRepository( localRepo );
config.setSettings( settings );
return config;
}
private File getCache()
throws IOException
{
File base = new File( "target" );
base.mkdir();
File f = File.createTempFile( "aethertest", ".dir", base );
f.delete();
f.mkdirs();
LOG.info( "Caching" + " to " + f.getAbsolutePath() );
return f;
}
}