1 package org.robolectric.internal.dependency
2 
3 import com.google.common.hash.Hashing
4 import com.google.common.io.Files
5 import com.google.common.truth.Truth.assertThat
6 import com.google.common.util.concurrent.Futures
7 import com.google.common.util.concurrent.ListenableFuture
8 import com.google.common.util.concurrent.MoreExecutors
9 import java.io.File
10 import java.io.IOException
11 import java.net.MalformedURLException
12 import java.net.URL
13 import java.nio.charset.StandardCharsets
14 import java.util.concurrent.ExecutorService
15 import org.junit.Assert
16 import org.junit.Before
17 import org.junit.Test
18 import org.junit.runner.RunWith
19 import org.junit.runners.JUnit4
20 
21 @RunWith(JUnit4::class)
22 class MavenDependencyResolverTest {
23   private lateinit var localRepositoryDir: File
24   private lateinit var executorService: ExecutorService
25   private lateinit var mavenDependencyResolver: MavenDependencyResolver
26   private lateinit var mavenArtifactFetcher: TestMavenArtifactFetcher
27 
28   @Before
29   @Throws(Exception::class)
setUpnull30   fun setUp() {
31     executorService = MoreExecutors.newDirectExecutorService()
32     localRepositoryDir = Files.createTempDir()
33     localRepositoryDir.deleteOnExit()
34     mavenArtifactFetcher =
35       TestMavenArtifactFetcher(
36         REPOSITORY_URL,
37         REPOSITORY_USERNAME,
38         REPOSITORY_PASSWORD,
39         PROXY_HOST,
40         PROXY_PORT,
41         localRepositoryDir,
42         executorService,
43       )
44     mavenDependencyResolver = TestMavenDependencyResolver()
45   }
46 
47   @Throws(Exception::class)
48   @Test
localArtifactUrl_placesFilesCorrectlyForSingleURLnull49   fun localArtifactUrl_placesFilesCorrectlyForSingleURL() {
50     val dependencyJar = successCases[0]
51     mavenDependencyResolver.getLocalArtifactUrl(dependencyJar)
52     assertThat(mavenArtifactFetcher.numRequests).isEqualTo(4)
53     val artifact = MavenJarArtifact(dependencyJar)
54     checkJarArtifact(artifact)
55   }
56 
57   @Throws(Exception::class)
58   @Test
localArtifactUrl_placesFilesCorrectlyForMultipleURLnull59   fun localArtifactUrl_placesFilesCorrectlyForMultipleURL() {
60     mavenDependencyResolver.getLocalArtifactUrls(*successCases)
61     assertThat(mavenArtifactFetcher.numRequests).isEqualTo(4 * successCases.size)
62     for (dependencyJar in successCases) {
63       val artifact = MavenJarArtifact(dependencyJar)
64       checkJarArtifact(artifact)
65     }
66   }
67 
68   /** Checks the case where the existing artifact directory is valid. */
69   @Throws(Exception::class)
70   @Test
localArtifactUrl_handlesExistingArtifactDirectorynull71   fun localArtifactUrl_handlesExistingArtifactDirectory() {
72     val dependencyJar = DependencyJar("group", "artifact", "1")
73     val mavenJarArtifact = MavenJarArtifact(dependencyJar)
74     val jarFile = File(localRepositoryDir, mavenJarArtifact.jarPath())
75     Files.createParentDirs(jarFile)
76     assertThat(jarFile.parentFile.isDirectory).isTrue()
77     mavenDependencyResolver.getLocalArtifactUrl(dependencyJar)
78     checkJarArtifact(mavenJarArtifact)
79   }
80 
81   /**
82    * Checks the case where there is some existing artifact metadata in the artifact directory, but
83    * not the JAR.
84    */
85   @Throws(Exception::class)
86   @Test
localArtifactUrl_handlesExistingMetadataFilenull87   fun localArtifactUrl_handlesExistingMetadataFile() {
88     val dependencyJar = DependencyJar("group", "artifact", "1")
89     val mavenJarArtifact = MavenJarArtifact(dependencyJar)
90     val pomFile = File(localRepositoryDir, mavenJarArtifact.pomPath())
91     pomFile.parentFile.mkdirs()
92     Files.write(ByteArray(0), pomFile)
93     assertThat(pomFile.exists()).isTrue()
94     mavenDependencyResolver.getLocalArtifactUrl(dependencyJar)
95     checkJarArtifact(mavenJarArtifact)
96   }
97 
98   @Throws(Exception::class)
checkJarArtifactnull99   private fun checkJarArtifact(artifact: MavenJarArtifact) {
100     val jar = File(localRepositoryDir, artifact.jarPath())
101     val pom = File(localRepositoryDir, artifact.pomPath())
102     val jarSha512 = File(localRepositoryDir, artifact.jarSha512Path())
103     val pomSha512 = File(localRepositoryDir, artifact.pomSha512Path())
104     assertThat(jar.exists()).isTrue()
105     assertThat(readFile(jar)).isEqualTo("$artifact jar contents")
106     assertThat(pom.exists()).isTrue()
107     assertThat(readFile(pom)).isEqualTo("$artifact pom contents")
108     assertThat(jarSha512.exists()).isTrue()
109     assertThat(readFile(jarSha512)).isEqualTo(sha512("$artifact jar contents"))
110     assertThat(pom.exists()).isTrue()
111     assertThat(readFile(pomSha512)).isEqualTo(sha512("$artifact pom contents"))
112   }
113 
114   @Throws(Exception::class)
115   @Test
localArtifactUrl_doesNotFetchWhenArtifactsExistnull116   fun localArtifactUrl_doesNotFetchWhenArtifactsExist() {
117     val dependencyJar = DependencyJar("group", "artifact", "1")
118     val mavenJarArtifact = MavenJarArtifact(dependencyJar)
119     val artifactFile = File(localRepositoryDir, mavenJarArtifact.jarPath())
120     artifactFile.parentFile.mkdirs()
121     Files.write(ByteArray(0), artifactFile)
122     assertThat(artifactFile.exists()).isTrue()
123     mavenDependencyResolver.getLocalArtifactUrl(dependencyJar)
124     assertThat(mavenArtifactFetcher.numRequests).isEqualTo(0)
125   }
126 
127   @Throws(Exception::class)
128   @Test
localArtifactUrl_handlesFileNotFoundnull129   fun localArtifactUrl_handlesFileNotFound() {
130     val dependencyJar = DependencyJar("group", "missing-artifact", "1")
131     Assert.assertThrows(AssertionError::class.java) {
132       mavenDependencyResolver.getLocalArtifactUrl(dependencyJar)
133     }
134   }
135 
136   @Throws(Exception::class)
137   @Test
localArtifactUrl_handlesInvalidSha512null138   fun localArtifactUrl_handlesInvalidSha512() {
139     val dependencyJar = DependencyJar("group", "artifact-invalid-sha512", "1")
140     addTestArtifactInvalidSha512(dependencyJar)
141     Assert.assertThrows(AssertionError::class.java) {
142       mavenDependencyResolver.getLocalArtifactUrl(dependencyJar)
143     }
144   }
145 
146   internal inner class TestMavenDependencyResolver : MavenDependencyResolver() {
createMavenFetchernull147     override fun createMavenFetcher(
148       repositoryUrl: String?,
149       repositoryUserName: String?,
150       repositoryPassword: String?,
151       proxyHost: String?,
152       proxyPort: Int,
153       localRepositoryDir: File,
154       executorService: ExecutorService,
155     ): MavenArtifactFetcher {
156       return mavenArtifactFetcher
157     }
158 
createExecutorServicenull159     override fun createExecutorService(): ExecutorService {
160       return executorService
161     }
162 
createLockFilenull163     override fun createLockFile(): File {
164       return try {
165         File.createTempFile("MavenDependencyResolverTest", null)
166       } catch (e: IOException) {
167         throw AssertionError(e)
168       }
169     }
170   }
171 
172   @Suppress("LongParameterList")
173   internal class TestMavenArtifactFetcher(
174     repositoryUrl: String?,
175     repositoryUserName: String?,
176     repositoryPassword: String?,
177     proxyHost: String?,
178     proxyPort: Int,
179     localRepositoryDir: File,
180     private val executorService: ExecutorService,
181   ) :
182     MavenArtifactFetcher(
183       repositoryUrl,
184       repositoryUserName,
185       repositoryPassword,
186       proxyHost,
187       proxyPort,
188       localRepositoryDir,
189       executorService,
190     ) {
191     var numRequests = 0
192       private set
193 
createFetchToFileTasknull194     override fun createFetchToFileTask(remoteUrl: URL, tempFile: File): ListenableFuture<Void> {
195       return Futures.submitAsync(
196         object : FetchToFileTask(remoteUrl, tempFile, null, null, null, 0) {
197           @Throws(Exception::class)
198           override fun call(): ListenableFuture<Void> {
199             numRequests += 1
200             return super.call()
201           }
202         },
203         executorService,
204       )
205     }
206   }
207 
208   companion object {
209     private var REPOSITORY_DIR: File
210     private var REPOSITORY_URL: String
211     private const val REPOSITORY_USERNAME = "username"
212     private const val REPOSITORY_PASSWORD = "password"
213     private const val PROXY_HOST = "123.4.5.678"
214     private const val PROXY_PORT = 9000
215     private val SHA512 = Hashing.sha512()
216     private val successCases =
217       arrayOf(
218         DependencyJar("group", "artifact", "1"),
219         DependencyJar("org.group2", "artifact2-name", "2.4.5"),
220         DependencyJar("org.robolectric", "android-all", "10-robolectric-5803371"),
221       )
222 
223     init {
224       try {
225         REPOSITORY_DIR = Files.createTempDir()
226         REPOSITORY_DIR.deleteOnExit()
227         REPOSITORY_URL = REPOSITORY_DIR.toURI().toURL().toString()
228         for (dependencyJar in successCases) {
229           addTestArtifact(dependencyJar)
230         }
231       } catch (e: Exception) {
232         throw AssertionError(e)
233       }
234     }
235 
236     @Throws(IOException::class)
addTestArtifactnull237     fun addTestArtifact(dependencyJar: DependencyJar?) {
238       val mavenJarArtifact = MavenJarArtifact(dependencyJar)
239       try {
240         Files.createParentDirs(File(REPOSITORY_DIR, mavenJarArtifact.jarPath()))
241         val jarContents = "$mavenJarArtifact jar contents"
242         Files.write(
243           jarContents.toByteArray(StandardCharsets.UTF_8),
244           File(REPOSITORY_DIR, mavenJarArtifact.jarPath()),
245         )
246         Files.write(
247           sha512(jarContents).toByteArray(),
248           File(REPOSITORY_DIR, mavenJarArtifact.jarSha512Path()),
249         )
250         val pomContents = "$mavenJarArtifact pom contents"
251         Files.write(
252           pomContents.toByteArray(StandardCharsets.UTF_8),
253           File(REPOSITORY_DIR, mavenJarArtifact.pomPath()),
254         )
255         Files.write(
256           sha512(pomContents).toByteArray(),
257           File(REPOSITORY_DIR, mavenJarArtifact.pomSha512Path()),
258         )
259       } catch (e: MalformedURLException) {
260         throw AssertionError(e)
261       }
262     }
263 
264     @Throws(IOException::class)
addTestArtifactInvalidSha512null265     fun addTestArtifactInvalidSha512(dependencyJar: DependencyJar?) {
266       val mavenJarArtifact = MavenJarArtifact(dependencyJar)
267       try {
268         Files.createParentDirs(File(REPOSITORY_DIR, mavenJarArtifact.jarPath()))
269         val jarContents = "$mavenJarArtifact jar contents"
270         Files.write(jarContents.toByteArray(), File(REPOSITORY_DIR, mavenJarArtifact.jarPath()))
271         Files.write(
272           sha512("No the same content").toByteArray(),
273           File(REPOSITORY_DIR, mavenJarArtifact.jarSha512Path()),
274         )
275         val pomContents = "$mavenJarArtifact pom contents"
276         Files.write(pomContents.toByteArray(), File(REPOSITORY_DIR, mavenJarArtifact.pomPath()))
277         Files.write(
278           sha512("Really not the same content").toByteArray(),
279           File(REPOSITORY_DIR, mavenJarArtifact.pomSha512Path()),
280         )
281       } catch (e: MalformedURLException) {
282         throw AssertionError(e)
283       }
284     }
285 
sha512null286     fun sha512(contents: String): String {
287       return SHA512.hashString(contents, StandardCharsets.UTF_8).toString()
288     }
289 
290     @Throws(IOException::class)
readFilenull291     fun readFile(file: File): String {
292       return String(Files.asByteSource(file).read(), StandardCharsets.UTF_8)
293     }
294   }
295 }
296