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