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 internal class TestMavenArtifactFetcher( 173 repositoryUrl: String?, 174 repositoryUserName: String?, 175 repositoryPassword: String?, 176 proxyHost: String?, 177 proxyPort: Int, 178 localRepositoryDir: File, 179 private val executorService: ExecutorService 180 ) : 181 MavenArtifactFetcher( 182 repositoryUrl, 183 repositoryUserName, 184 repositoryPassword, 185 proxyHost, 186 proxyPort, 187 localRepositoryDir, 188 executorService 189 ) { 190 var numRequests = 0 191 private set 192 createFetchToFileTasknull193 override fun createFetchToFileTask(remoteUrl: URL, tempFile: File): ListenableFuture<Void> { 194 return Futures.submitAsync( 195 object : FetchToFileTask(remoteUrl, tempFile, null, null, null, 0) { 196 @Throws(Exception::class) 197 override fun call(): ListenableFuture<Void> { 198 numRequests += 1 199 return super.call() 200 } 201 }, 202 executorService 203 ) 204 } 205 } 206 207 companion object { 208 private var REPOSITORY_DIR: File 209 private var REPOSITORY_URL: String 210 private const val REPOSITORY_USERNAME = "username" 211 private const val REPOSITORY_PASSWORD = "password" 212 private const val PROXY_HOST = "123.4.5.678" 213 private const val PROXY_PORT = 9000 214 private val SHA512 = Hashing.sha512() 215 private val successCases = 216 arrayOf( 217 DependencyJar("group", "artifact", "1"), 218 DependencyJar("org.group2", "artifact2-name", "2.4.5"), 219 DependencyJar("org.robolectric", "android-all", "10-robolectric-5803371") 220 ) 221 222 init { 223 try { 224 REPOSITORY_DIR = Files.createTempDir() 225 REPOSITORY_DIR.deleteOnExit() 226 REPOSITORY_URL = REPOSITORY_DIR.toURI().toURL().toString() 227 for (dependencyJar in successCases) { 228 addTestArtifact(dependencyJar) 229 } 230 } catch (e: Exception) { 231 throw AssertionError(e) 232 } 233 } 234 235 @Throws(IOException::class) addTestArtifactnull236 fun addTestArtifact(dependencyJar: DependencyJar?) { 237 val mavenJarArtifact = MavenJarArtifact(dependencyJar) 238 try { 239 Files.createParentDirs(File(REPOSITORY_DIR, mavenJarArtifact.jarPath())) 240 val jarContents = "$mavenJarArtifact jar contents" 241 Files.write( 242 jarContents.toByteArray(StandardCharsets.UTF_8), 243 File(REPOSITORY_DIR, mavenJarArtifact.jarPath()) 244 ) 245 Files.write( 246 sha512(jarContents).toByteArray(), 247 File(REPOSITORY_DIR, mavenJarArtifact.jarSha512Path()) 248 ) 249 val pomContents = "$mavenJarArtifact pom contents" 250 Files.write( 251 pomContents.toByteArray(StandardCharsets.UTF_8), 252 File(REPOSITORY_DIR, mavenJarArtifact.pomPath()) 253 ) 254 Files.write( 255 sha512(pomContents).toByteArray(), 256 File(REPOSITORY_DIR, mavenJarArtifact.pomSha512Path()) 257 ) 258 } catch (e: MalformedURLException) { 259 throw AssertionError(e) 260 } 261 } 262 263 @Throws(IOException::class) addTestArtifactInvalidSha512null264 fun addTestArtifactInvalidSha512(dependencyJar: DependencyJar?) { 265 val mavenJarArtifact = MavenJarArtifact(dependencyJar) 266 try { 267 Files.createParentDirs(File(REPOSITORY_DIR, mavenJarArtifact.jarPath())) 268 val jarContents = "$mavenJarArtifact jar contents" 269 Files.write(jarContents.toByteArray(), File(REPOSITORY_DIR, mavenJarArtifact.jarPath())) 270 Files.write( 271 sha512("No the same content").toByteArray(), 272 File(REPOSITORY_DIR, mavenJarArtifact.jarSha512Path()) 273 ) 274 val pomContents = "$mavenJarArtifact pom contents" 275 Files.write(pomContents.toByteArray(), File(REPOSITORY_DIR, mavenJarArtifact.pomPath())) 276 Files.write( 277 sha512("Really not the same content").toByteArray(), 278 File(REPOSITORY_DIR, mavenJarArtifact.pomSha512Path()) 279 ) 280 } catch (e: MalformedURLException) { 281 throw AssertionError(e) 282 } 283 } 284 sha512null285 fun sha512(contents: String): String { 286 return SHA512.hashString(contents, StandardCharsets.UTF_8).toString() 287 } 288 289 @Throws(IOException::class) readFilenull290 fun readFile(file: File): String { 291 return String(Files.asByteSource(file).read(), StandardCharsets.UTF_8) 292 } 293 } 294 } 295