001package org.eclipse.aether.connector.basic; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import static org.eclipse.aether.connector.basic.TestChecksumAlgorithmSelector.MD5; 023import static org.eclipse.aether.connector.basic.TestChecksumAlgorithmSelector.SHA1; 024import static org.junit.Assert.*; 025 026import java.io.File; 027import java.io.IOException; 028import java.net.URI; 029import java.util.ArrayList; 030import java.util.Arrays; 031import java.util.HashMap; 032import java.util.LinkedHashMap; 033import java.util.List; 034import java.util.Map; 035 036import org.eclipse.aether.internal.test.util.TestFileProcessor; 037import org.eclipse.aether.internal.test.util.TestFileUtils; 038import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory; 039import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy; 040import org.eclipse.aether.spi.connector.checksum.ChecksumPolicy.ChecksumKind; 041import org.eclipse.aether.spi.connector.layout.RepositoryLayout; 042import org.eclipse.aether.transfer.ChecksumFailureException; 043import org.junit.Before; 044import org.junit.Test; 045 046public class ChecksumValidatorTest 047{ 048 049 private static class StubChecksumPolicy 050 implements ChecksumPolicy 051 { 052 053 boolean inspectAll; 054 055 boolean tolerateFailure; 056 057 private final ArrayList<String> callbacks = new ArrayList<>(); 058 059 private Object conclusion; 060 061 @Override 062 public boolean onChecksumMatch( String algorithm, ChecksumKind kind ) 063 { 064 callbacks.add( String.format( "match(%s, %s)", algorithm, kind ) ); 065 if ( inspectAll ) 066 { 067 if ( conclusion == null ) 068 { 069 conclusion = true; 070 } 071 return false; 072 } 073 return true; 074 } 075 076 @Override 077 public void onChecksumMismatch( String algorithm, ChecksumKind kind, ChecksumFailureException exception ) 078 throws ChecksumFailureException 079 { 080 callbacks.add( String.format( "mismatch(%s, %s)", algorithm, kind ) ); 081 if ( inspectAll ) 082 { 083 conclusion = exception; 084 return; 085 } 086 throw exception; 087 } 088 089 @Override 090 public void onChecksumError( String algorithm, ChecksumKind kind, ChecksumFailureException exception ) 091 { 092 callbacks.add( String.format( "error(%s, %s, %s)", algorithm, kind, exception.getCause().getMessage() ) ); 093 } 094 095 @Override 096 public void onNoMoreChecksums() 097 throws ChecksumFailureException 098 { 099 callbacks.add( String.format( "noMore()" ) ); 100 if ( conclusion instanceof ChecksumFailureException ) 101 { 102 throw (ChecksumFailureException) conclusion; 103 } 104 else if ( !Boolean.TRUE.equals( conclusion ) ) 105 { 106 throw new ChecksumFailureException( "no checksums" ); 107 } 108 } 109 110 @Override 111 public void onTransferRetry() 112 { 113 callbacks.add( String.format( "retry()" ) ); 114 } 115 116 @Override 117 public boolean onTransferChecksumFailure( ChecksumFailureException exception ) 118 { 119 callbacks.add( String.format( "fail(%s)", exception.getMessage() ) ); 120 return tolerateFailure; 121 } 122 123 void assertCallbacks( String... callbacks ) 124 { 125 assertEquals( Arrays.asList( callbacks ), this.callbacks ); 126 } 127 128 } 129 130 private static class StubChecksumFetcher 131 implements ChecksumValidator.ChecksumFetcher 132 { 133 134 HashMap<URI, Object> checksums = new HashMap<>(); 135 136 ArrayList<File> checksumFiles = new ArrayList<>(); 137 138 private final ArrayList<URI> fetchedFiles = new ArrayList<>(); 139 140 @Override 141 public boolean fetchChecksum( URI remote, File local ) 142 throws Exception 143 { 144 fetchedFiles.add( remote ); 145 Object checksum = checksums.get( remote ); 146 if ( checksum == null ) 147 { 148 return false; 149 } 150 if ( checksum instanceof Exception ) 151 { 152 throw (Exception) checksum; 153 } 154 TestFileUtils.writeString( local, checksum.toString() ); 155 checksumFiles.add( local ); 156 return true; 157 } 158 159 void mock( String algo, Object value ) 160 { 161 checksums.put( toUri( algo ), value ); 162 } 163 164 void assertFetchedFiles( String... algos ) 165 { 166 List<URI> expected = new ArrayList<>(); 167 for ( String algo : algos ) 168 { 169 expected.add( toUri( algo ) ); 170 } 171 assertEquals( expected, fetchedFiles ); 172 } 173 174 private static URI toUri( String algo ) 175 { 176 return newChecksum( algo ).getLocation(); 177 } 178 179 } 180 181 private StubChecksumPolicy policy; 182 183 private StubChecksumFetcher fetcher; 184 185 private File dataFile; 186 187 private static final TestChecksumAlgorithmSelector selector = new TestChecksumAlgorithmSelector(); 188 189 private List<ChecksumAlgorithmFactory> newChecksumAlgorithmFactories( String... factories ) 190 { 191 List<ChecksumAlgorithmFactory> checksums = new ArrayList<>(); 192 for ( String factory : factories ) 193 { 194 checksums.add( selector.select( factory ) ); 195 } 196 return checksums; 197 } 198 199 private static RepositoryLayout.ChecksumLocation newChecksum( String factory ) 200 { 201 return RepositoryLayout.ChecksumLocation.forLocation( URI.create( "file" ), selector.select( factory ) ); 202 } 203 204 private List<RepositoryLayout.ChecksumLocation> newChecksums( List<ChecksumAlgorithmFactory> checksumAlgorithmFactories ) 205 { 206 List<RepositoryLayout.ChecksumLocation> checksums = new ArrayList<>(); 207 for ( ChecksumAlgorithmFactory factory : checksumAlgorithmFactories ) 208 { 209 checksums.add( RepositoryLayout.ChecksumLocation.forLocation( URI.create( "file" ), factory ) ); 210 } 211 return checksums; 212 } 213 214 private ChecksumValidator newValidator( String... factories ) 215 { 216 return newValidator( null, factories ); 217 } 218 219 private ChecksumValidator newValidator( Map<String, String> providedChecksums, String... factories ) 220 { 221 List<ChecksumAlgorithmFactory> checksumAlgorithmFactories = newChecksumAlgorithmFactories( factories ); 222 return new ChecksumValidator( dataFile, checksumAlgorithmFactories, new TestFileProcessor(), fetcher, policy, providedChecksums, newChecksums( checksumAlgorithmFactories ) ); 223 } 224 225 private Map<String, ?> checksums( String... algoDigestPairs ) 226 { 227 Map<String, Object> checksums = new LinkedHashMap<>(); 228 for ( int i = 0; i < algoDigestPairs.length; i += 2 ) 229 { 230 String algo = algoDigestPairs[i]; 231 String digest = algoDigestPairs[i + 1]; 232 if ( digest == null ) 233 { 234 checksums.put( algo, new IOException( "error" ) ); 235 } 236 else 237 { 238 checksums.put( algo, digest ); 239 } 240 } 241 return checksums; 242 } 243 244 @Before 245 public void init() 246 throws Exception 247 { 248 dataFile = TestFileUtils.createTempFile( "" ); 249 dataFile.delete(); 250 policy = new StubChecksumPolicy(); 251 fetcher = new StubChecksumFetcher(); 252 } 253 254 @Test 255 public void testValidate_NullPolicy() 256 throws Exception 257 { 258 policy = null; 259 ChecksumValidator validator = newValidator( SHA1 ); 260 validator.validate( checksums( SHA1, "ignored" ), null ); 261 fetcher.assertFetchedFiles(); 262 } 263 264 @Test 265 public void testValidate_AcceptOnFirstMatch() 266 throws Exception 267 { 268 ChecksumValidator validator = newValidator( SHA1 ); 269 fetcher.mock( SHA1, "foo" ); 270 validator.validate( checksums( SHA1, "foo" ), null ); 271 fetcher.assertFetchedFiles( SHA1 ); 272 policy.assertCallbacks( "match(SHA-1, REMOTE_EXTERNAL)" ); 273 } 274 275 @Test 276 public void testValidate_FailOnFirstMismatch() 277 { 278 ChecksumValidator validator = newValidator( SHA1 ); 279 fetcher.mock( SHA1, "foo" ); 280 try 281 { 282 validator.validate( checksums( SHA1, "not-foo" ), null ); 283 fail( "expected exception" ); 284 } 285 catch ( ChecksumFailureException e ) 286 { 287 assertEquals( "foo", e.getExpected() ); 288 assertEquals( ChecksumKind.REMOTE_EXTERNAL.name(), e.getExpectedKind() ); 289 assertEquals( "not-foo", e.getActual() ); 290 assertTrue( e.isRetryWorthy() ); 291 } 292 fetcher.assertFetchedFiles( SHA1 ); 293 policy.assertCallbacks( "mismatch(SHA-1, REMOTE_EXTERNAL)" ); 294 } 295 296 @Test 297 public void testValidate_AcceptOnEnd() 298 throws Exception 299 { 300 policy.inspectAll = true; 301 ChecksumValidator validator = newValidator( SHA1, MD5 ); 302 fetcher.mock( SHA1, "foo" ); 303 fetcher.mock( MD5, "bar" ); 304 validator.validate( checksums( SHA1, "foo", MD5, "bar" ), null ); 305 fetcher.assertFetchedFiles( SHA1, MD5 ); 306 policy.assertCallbacks( "match(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()" ); 307 } 308 309 @Test 310 public void testValidate_FailOnEnd() 311 { 312 policy.inspectAll = true; 313 ChecksumValidator validator = newValidator( SHA1, MD5 ); 314 fetcher.mock( SHA1, "foo" ); 315 fetcher.mock( MD5, "bar" ); 316 try 317 { 318 validator.validate( checksums( SHA1, "not-foo", MD5, "bar" ), null ); 319 fail( "expected exception" ); 320 } 321 catch ( ChecksumFailureException e ) 322 { 323 assertEquals( "foo", e.getExpected() ); 324 assertEquals( ChecksumKind.REMOTE_EXTERNAL.name(), e.getExpectedKind() ); 325 assertEquals( "not-foo", e.getActual() ); 326 assertTrue( e.isRetryWorthy() ); 327 } 328 fetcher.assertFetchedFiles( SHA1, MD5 ); 329 policy.assertCallbacks( "mismatch(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()" ); 330 } 331 332 @Test 333 public void testValidate_IncludedBeforeExternal() 334 throws Exception 335 { 336 policy.inspectAll = true; 337 HashMap<String, String> provided = new HashMap<>(); 338 provided.put( SHA1, "foo" ); 339 ChecksumValidator validator = newValidator( provided, SHA1, MD5 ); 340 fetcher.mock( SHA1, "foo" ); 341 fetcher.mock( MD5, "bar" ); 342 validator.validate( checksums( SHA1, "foo", MD5, "bar" ), checksums( SHA1, "foo", MD5, "bar" ) ); 343 fetcher.assertFetchedFiles( SHA1, MD5 ); 344 policy.assertCallbacks( "match(SHA-1, PROVIDED)", "match(SHA-1, REMOTE_INCLUDED)", "match(MD5, REMOTE_INCLUDED)", 345 "match(SHA-1, REMOTE_EXTERNAL)", "match(MD5, REMOTE_EXTERNAL)", "noMore()" ); 346 } 347 348 @Test 349 public void testValidate_CaseInsensitive() 350 throws Exception 351 { 352 policy.inspectAll = true; 353 ChecksumValidator validator = newValidator( SHA1 ); 354 fetcher.mock( SHA1, "FOO" ); 355 validator.validate( checksums( SHA1, "foo" ), checksums( SHA1, "foo" ) ); 356 policy.assertCallbacks( "match(SHA-1, REMOTE_INCLUDED)", "match(SHA-1, REMOTE_EXTERNAL)", "noMore()" ); 357 } 358 359 @Test 360 public void testValidate_MissingRemoteChecksum() 361 throws Exception 362 { 363 ChecksumValidator validator = newValidator( SHA1, MD5 ); 364 fetcher.mock( MD5, "bar" ); 365 validator.validate( checksums( MD5, "bar" ), null ); 366 fetcher.assertFetchedFiles( SHA1, MD5 ); 367 policy.assertCallbacks( "match(MD5, REMOTE_EXTERNAL)" ); 368 } 369 370 @Test 371 public void testValidate_InaccessibleRemoteChecksum() 372 throws Exception 373 { 374 ChecksumValidator validator = newValidator( SHA1, MD5 ); 375 fetcher.mock( SHA1, new IOException( "inaccessible" ) ); 376 fetcher.mock( MD5, "bar" ); 377 validator.validate( checksums( MD5, "bar" ), null ); 378 fetcher.assertFetchedFiles( SHA1, MD5 ); 379 policy.assertCallbacks( "error(SHA-1, REMOTE_EXTERNAL, inaccessible)", "match(MD5, REMOTE_EXTERNAL)" ); 380 } 381 382 @Test 383 public void testValidate_InaccessibleLocalChecksum() 384 throws Exception 385 { 386 ChecksumValidator validator = newValidator( SHA1, MD5 ); 387 fetcher.mock( SHA1, "foo" ); 388 fetcher.mock( MD5, "bar" ); 389 validator.validate( checksums( SHA1, null, MD5, "bar" ), null ); 390 fetcher.assertFetchedFiles( MD5 ); 391 policy.assertCallbacks( "error(SHA-1, REMOTE_EXTERNAL, error)", "match(MD5, REMOTE_EXTERNAL)" ); 392 } 393 394 @Test 395 public void testHandle_Accept() 396 { 397 policy.tolerateFailure = true; 398 ChecksumValidator validator = newValidator( SHA1 ); 399 assertTrue( validator.handle( new ChecksumFailureException( "accept" ) ) ); 400 policy.assertCallbacks( "fail(accept)" ); 401 } 402 403 @Test 404 public void testHandle_Reject() 405 { 406 policy.tolerateFailure = false; 407 ChecksumValidator validator = newValidator( SHA1 ); 408 assertFalse( validator.handle( new ChecksumFailureException( "reject" ) ) ); 409 policy.assertCallbacks( "fail(reject)" ); 410 } 411 412 @Test 413 public void testRetry_ResetPolicy() 414 { 415 ChecksumValidator validator = newValidator( SHA1 ); 416 validator.retry(); 417 policy.assertCallbacks( "retry()" ); 418 } 419 420 @Test 421 public void testRetry_RemoveTempFiles() 422 throws Exception 423 { 424 ChecksumValidator validator = newValidator( SHA1 ); 425 fetcher.mock( SHA1, "foo" ); 426 validator.validate( checksums( SHA1, "foo" ), null ); 427 fetcher.assertFetchedFiles( SHA1 ); 428 assertEquals( 1, fetcher.checksumFiles.size() ); 429 for ( File file : fetcher.checksumFiles ) 430 { 431 assertTrue( file.getAbsolutePath(), file.isFile() ); 432 } 433 validator.retry(); 434 for ( File file : fetcher.checksumFiles ) 435 { 436 assertFalse( file.getAbsolutePath(), file.exists() ); 437 } 438 } 439 440 @Test 441 public void testCommit_SaveChecksumFiles() 442 throws Exception 443 { 444 policy.inspectAll = true; 445 ChecksumValidator validator = newValidator( SHA1, MD5 ); 446 fetcher.mock( MD5, "bar" ); 447 validator.validate( checksums( SHA1, "foo", MD5, "bar" ), checksums( SHA1, "foo" ) ); 448 assertEquals( 1, fetcher.checksumFiles.size() ); 449 for ( File file : fetcher.checksumFiles ) 450 { 451 assertTrue( file.getAbsolutePath(), file.isFile() ); 452 } 453 validator.commit(); 454 File checksumFile = new File( dataFile.getPath() + ".sha1" ); 455 assertTrue( checksumFile.getAbsolutePath(), checksumFile.isFile() ); 456 assertEquals( "foo", TestFileUtils.readString( checksumFile ) ); 457 checksumFile = new File( dataFile.getPath() + ".md5" ); 458 assertTrue( checksumFile.getAbsolutePath(), checksumFile.isFile() ); 459 assertEquals( "bar", TestFileUtils.readString( checksumFile ) ); 460 for ( File file : fetcher.checksumFiles ) 461 { 462 assertFalse( file.getAbsolutePath(), file.exists() ); 463 } 464 } 465 466 @Test 467 public void testClose_RemoveTempFiles() 468 throws Exception 469 { 470 ChecksumValidator validator = newValidator( SHA1 ); 471 fetcher.mock( SHA1, "foo" ); 472 validator.validate( checksums( SHA1, "foo" ), null ); 473 fetcher.assertFetchedFiles( SHA1 ); 474 assertEquals( 1, fetcher.checksumFiles.size() ); 475 for ( File file : fetcher.checksumFiles ) 476 { 477 assertTrue( file.getAbsolutePath(), file.isFile() ); 478 } 479 validator.close(); 480 for ( File file : fetcher.checksumFiles ) 481 { 482 assertFalse( file.getAbsolutePath(), file.exists() ); 483 } 484 } 485 486}