/* * Copyright 2015-present Facebook, Inc. * * 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 com.facebook.buck.file; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import com.facebook.buck.cli.BuckConfig; import com.facebook.buck.cli.FakeBuckConfig; import com.facebook.buck.event.BuckEventBus; import com.facebook.buck.event.BuckEventBusFactory; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.environment.Platform; import com.google.common.collect.ImmutableList; import com.google.common.jimfs.Configuration; import com.google.common.jimfs.Jimfs; import java.io.IOException; import java.lang.reflect.Field; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.FileSystem; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.Optional; import org.easymock.EasyMock; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; import org.hamcrest.Matcher; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; public class StackedDownloaderTest { @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void shouldCreateADownloaderEvenWithAnEmptyStack() { Downloader downloader = StackedDownloader.createFromConfig(FakeBuckConfig.builder().build(), Optional.empty()); assertNotNull(downloader); List<Downloader> downloaders = unpackDownloaders(downloader); assertFalse(downloaders.isEmpty()); } @Test public void shouldAddOnDiskAndroidReposIfPresentInSdk() throws IOException { Downloader downloader = StackedDownloader.createFromConfig(FakeBuckConfig.builder().build(), Optional.empty()); List<Downloader> downloaders = unpackDownloaders(downloader); for (Downloader seen : downloaders) { assertFalse(seen instanceof OnDiskMavenDownloader); } FileSystem vfs = Jimfs.newFileSystem(Configuration.unix()); Path sdkRoot = vfs.getPath("/opt/android_sdk"); Path androidM2 = sdkRoot.resolve("extras/android/m2repository"); Path googleM2 = sdkRoot.resolve("extras/google/m2repository"); Files.createDirectories(androidM2); Files.createDirectories(googleM2); downloader = StackedDownloader.createFromConfig(FakeBuckConfig.builder().build(), Optional.of(sdkRoot)); downloaders = unpackDownloaders(downloader); int count = 0; for (Downloader seen : downloaders) { if (seen instanceof OnDiskMavenDownloader) { count++; } } assertEquals(2, count); } @Test public void createDownloadersForEachEntryInTheMavenRepositoriesSection() throws InterruptedException, IOException { boolean isWindows = Platform.detect() == Platform.WINDOWS; Configuration configuration = isWindows ? Configuration.windows() : Configuration.unix(); FileSystem vfs = Jimfs.newFileSystem(configuration); Path m2Root = vfs.getPath(jimfAbsolutePath("/home/user/.m2/repository")); Files.createDirectories(m2Root); // Set up a config so we expect to see both a local and a remote maven repo. Path projectRoot = vfs.getPath(jimfAbsolutePath("/opt/local/src")); Files.createDirectories(projectRoot); BuckConfig config = FakeBuckConfig.builder() .setFilesystem(new ProjectFilesystem(projectRoot)) .setSections( "[maven_repositories]", "local = " + m2Root.toString(), "central = https://repo1.maven.org/maven2") .build(); Downloader downloader = StackedDownloader.createFromConfig(config, Optional.empty()); List<Downloader> downloaders = unpackDownloaders(downloader); boolean seenRemote = false; boolean seenLocal = false; for (Downloader seen : downloaders) { if (seen instanceof RemoteMavenDownloader) { seenRemote = true; } else if (seen instanceof OnDiskMavenDownloader) { seenLocal = true; } } assertTrue(seenLocal); assertTrue(seenRemote); } private static String jimfAbsolutePath(String path) { boolean isWindows = Platform.detect() == Platform.WINDOWS; if (isWindows) { path = "C:" + path; } return path; } @Test public void shouldFallBackToTheDeprecatedMechanismForCreatingMavenRepos() throws IOException { // Set up a config so we expect to see both a local and a remote maven repo. BuckConfig config = FakeBuckConfig.builder() .setSections("[download]", "maven_repo = https://repo1.maven.org/maven2") .build(); Downloader downloader = StackedDownloader.createFromConfig(config, Optional.empty()); assertThat(downloader, includes(RemoteMavenDownloader.class)); } @Test public void shouldIterativelyCheckEachDownloaderToSeeIfItWillReturnAnyFiles() throws URISyntaxException, IOException { Downloader noMatch = EasyMock.createNiceMock("noMatch", Downloader.class); Downloader exceptional = EasyMock.createNiceMock("exceptional", Downloader.class); Downloader works = EasyMock.createNiceMock("works", Downloader.class); Downloader neverCalled = EasyMock.createStrictMock("neverCalled", Downloader.class); BuckEventBus eventBus = BuckEventBusFactory.newInstance(); URI uri = new URI("http://example.com/cheese/peas"); Path output = Paths.get("never used"); EasyMock.expect(noMatch.fetch(eventBus, uri, output)).andReturn(false); EasyMock.expect(exceptional.fetch(eventBus, uri, output)).andThrow(new IOException("")); EasyMock.expect(works.fetch(eventBus, uri, output)).andReturn(true); // neverCalled is never called, and so needs no expectations. EasyMock.replay(noMatch, exceptional, works, neverCalled); StackedDownloader downloader = new StackedDownloader(ImmutableList.of(noMatch, exceptional, works, neverCalled)); boolean result = downloader.fetch(eventBus, uri, output); assertTrue(result); EasyMock.verify(noMatch, exceptional, works, neverCalled); } @SuppressWarnings("unchecked") private List<Downloader> unpackDownloaders(Downloader downloader) { try { Field field = downloader.getClass().getDeclaredField("delegates"); field.setAccessible(true); return (List<Downloader>) field.get(downloader); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } } @Test public void shouldThrowReadableExceptionWhenUrlPathDoesntExist() { String pathNotExist = "file://not/a/valid/path"; BuckConfig config = FakeBuckConfig.builder() .setSections("[download]", String.format("maven_repo = %s", pathNotExist)) .build(); thrown.expect(HumanReadableException.class); thrown.expectMessage( String.format( "Error occurred when attempting to use %s " + "as a local Maven repository as configured", pathNotExist)); StackedDownloader.createFromConfig(config, Optional.empty()); } @Test public void shouldThrowReadableExceptionWhenPathDoesntExist() { String pathNotExist = "//not/a/valid/path"; BuckConfig config = FakeBuckConfig.builder() .setSections("[download]", String.format("maven_repo = %s", pathNotExist)) .build(); thrown.expect(HumanReadableException.class); thrown.expectMessage( String.format( "Error occurred when attempting to use %s " + "as a local Maven repository as configured", pathNotExist)); StackedDownloader.createFromConfig(config, Optional.empty()); } @Test public void shouldUseRetryingDownloaderIfMaxNumberOfRetriesIsSet() throws IOException { BuckConfig config = FakeBuckConfig.builder().setSections("[download]", "max_number_of_retries = 1").build(); Downloader downloader = StackedDownloader.createFromConfig(config, Optional.empty()); assertThat(downloader, includes(RetryingDownloader.class)); } @Test public void shouldNotUseRetryingDownloaderIfMaxNumberOfRetriesIsSet() throws IOException { BuckConfig config = FakeBuckConfig.builder().build(); Downloader downloader = StackedDownloader.createFromConfig(config, Optional.empty()); assertThat(downloader, not(includes(RetryingDownloader.class))); } private Matcher<Downloader> includes(final Class<? extends Downloader> clazz) { return new BaseMatcher<Downloader>() { @Override public void describeTo(Description description) { description.appendText("downloader must include ").appendValue(clazz); } @Override public boolean matches(Object object) { Downloader downloader = (Downloader) object; List<Downloader> downloaders = unpackDownloaders(downloader); boolean seenRetrying = false; for (Downloader seen : downloaders) { if (clazz.isInstance(seen)) { seenRetrying = true; } } return seenRetrying; } }; } }