1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import org.testng.Assert;
  25 import org.testng.annotations.AfterClass;
  26 import org.testng.annotations.BeforeClass;
  27 import org.testng.annotations.DataProvider;
  28 import org.testng.annotations.Test;
  29 
  30 import java.io.File;
  31 import java.io.IOException;
  32 import java.nio.file.FileSystem;
  33 import java.nio.file.Files;
  34 import java.nio.file.Path;
  35 import java.nio.file.Paths;
  36 import java.nio.file.spi.FileSystemProvider;
  37 import java.util.Map;
  38 
  39 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
  40 
  41 /* @test
  42  * @bug 8202285
  43  * @build Mismatch
  44  * @run testng Mismatch
  45  * @summary Unit test for the mismatch method
  46  */
  47 
  48 @Test(groups = "mismatch")
  49 public class Mismatch {
  50     // the standard buffer size
  51     final static int BUFFER_SIZE = 8192;
  52 
  53     private static final int MISMATCH_NO = -1;
  54 
  55     // Map to be used for creating a ZIP archive
  56     private static final Map<String, String> ZIPFS_MAP = Map.of("create", "true");
  57 
  58     // temporary test directory where all test files will be created
  59     Path testDir;
  60 
  61     Path test0a; // empty file, size = 0
  62     Path test0b; // empty file, size = 0
  63 
  64     /**
  65      * The following files are used to test cases where the files are smaller,
  66      * equal and greater than the buffer size of 8192.
  67      */
  68     Path test1024a; // a test file with size 1024
  69     Path test1024b; // a test file with size 1024, identical to test1024a
  70     Path test1024m1014a; // a test file with size 1024, with an altered byte at 1014
  71     Path test1024m1014b; // a test file with size 1024, with an altered byte at 1014
  72     Path test1024m60; // a test file with size 1024, with an altered byte at 60
  73 
  74     Path test8192a; // a test file with size 8192
  75     Path test8192b; // a test file with size 8192, identical to test8192a
  76     Path test8192m8182a; // a test file with size 8192, with an altered byte at 8182
  77     Path test8192m8182b; // a test file with size 8192, with an altered byte at 8182
  78     Path test8192m60; // a test file with size 8192, with an altered byte at 60
  79 
  80     /**
  81      * Files with a size >= BUFFER_SIZE to verify the situation where they will be
  82      * read a few times before a mismatch is found in the last full or partial buffer
  83      */
  84     Path test65536a; // a test file with size 65536
  85     Path test65536b; // a test file with size 65536, identical to test65536a
  86     Path test65536m65526a; // a test file with size 65536, with an altered byte at 65526
  87     Path test65536m65526b; // a test file with size 65536, with an altered byte at 65526
  88     Path test65536m60; // a test file with size 65536, with an altered byte at 60
  89     Path test69632a; // a test file with size 69632
  90     Path test69632b; // a test file with size 69632, identical to test69632a
  91     Path test69632m69622a; // a test file with size 69632, with an altered byte at 69622
  92     Path test69632m69622b; // a test file with size 69632, with an altered byte at 69622
  93     Path test69632m60; // a test file with size 69632, with an altered byte at 60
  94 
  95     /**
  96      * Larger files (>=1048576) to exercise the process
  97      */
  98     Path test1048576a; // a test file with size 1048576
  99     Path test1048576b; // a test file with size 1048576, identical to test1048576a
 100     Path test1048576m1048566a; // a test file with size 1048576, with an altered byte at 1048566
 101     Path test1048576m1048566b; // a test file with size 1048576, with an altered byte at 1048566
 102     Path test1048576m60; // a test file with size 1048576, with an altered byte at 60
 103     Path test1052672m1052662a; // a test file with size 1052672, with an altered byte at 1052662
 104     Path test1052672m1052662b; // a test file with size 1052672, with an altered byte at 1052662
 105     Path test1052672m60; // a test file with size 1052672, with an altered byte at 60
 106 
 107 
 108     @BeforeClass
 109     void setup() throws IOException {
 110         /**
 111          * Create files with alphanumeric contents in a temporary directory
 112          */
 113         testDir = Files.createTempDirectory("testMismatch");
 114 
 115         // create empty files
 116         int size = 0;
 117         test0a = createANFile(testDir, "test0a", 0, -1, (byte)' ');
 118         test0b = createANFile(testDir, "test0b", 0, -1, (byte)' ');
 119 
 120         // The Impl uses a standard buffer of 8192
 121         // Test cases with files <= and > 8192
 122         size = 1024;
 123         test1024a = createANFile(testDir, "test1024a", size, -1, (byte)' ');
 124         test1024b = createANFile(testDir, "test1024b", size, -1, (byte)' ');
 125         test1024m1014a = createANFile(testDir, "test1024m1014a", size, size - 10, (byte)'@');
 126         test1024m1014b = createANFile(testDir, "test1024m1014b", size, size - 10, (byte)'#');
 127         test1024m60 = createANFile(testDir, "test1024m60", size, 60, (byte)'$');
 128 
 129         size = BUFFER_SIZE;
 130         test8192a = createANFile(testDir, "test8192a", size, -1, (byte)' ');
 131         test8192b = createANFile(testDir, "test8192b", size, -1, (byte)' ');
 132         test8192m8182a = createANFile(testDir, "test8192m8182a", size, size - 10, (byte)'@');
 133         test8192m8182b = createANFile(testDir, "test8192m8182b", size, size - 10, (byte)'#');
 134         test8192m60 = createANFile(testDir, "test8192m60", size, 60, (byte)'$');
 135 
 136         // create files with size several times > BUFFER_SIZE to be used for tests that verify
 137         // the situations where they are read into full buffers a few times
 138         size = BUFFER_SIZE << 3;
 139         test65536a = createANFile(testDir, "test65536a", size, -1, (byte)' ');
 140         test65536b = createANFile(testDir, "test65536b", size, -1, (byte)' ');
 141         test65536m65526a = createANFile(testDir, "test65536m65526a", size, size - 10, (byte)'@');
 142         test65536m65526b = createANFile(testDir, "test65536m65526b", size, size - 10, (byte)'#');
 143         test65536m60 = createANFile(testDir, "test65536m60", size, 60, (byte)'$');
 144         // create files with sizes that will be iterated several times with full buffers, and
 145         // then a partial one at the last
 146         size = 69632;
 147         test69632a = createANFile(testDir, "test69632a", size, -1, (byte)' ');
 148         test69632b = createANFile(testDir, "test69632b", size, -1, (byte)' ');
 149         test69632m69622a = createANFile(testDir, "test69632m69622a", size, size - 10, (byte)'@');
 150         test69632m69622b = createANFile(testDir, "test69632m69622b", size, size - 10, (byte)'#');
 151         test69632m60 = createANFile(testDir, "test69632m60", size, 60, (byte)'$');
 152 
 153         // create larger files with >= 1048576. The mismatching will be similar. These are just
 154         // tests to exercise the process with larger files
 155         size = 1048576;
 156         test1048576a = createANFile(testDir, "test1048576a", size, -1, (byte)' ');
 157         test1048576b = createANFile(testDir, "test1048576b", size, -1, (byte)' ');
 158         test1048576m1048566a = createANFile(testDir, "test1048576m1048566a", size, size - 10, (byte)'@');
 159         test1048576m1048566b = createANFile(testDir, "test1048576m1048566b", size, size - 10, (byte)'#');
 160         test1048576m60 = createANFile(testDir, "test1048576m60", size, 60, (byte)'$');
 161         size = 1052672;
 162         test1052672m1052662a = createANFile(testDir, "test1052672m1052662a", size, size - 10, (byte)'@');
 163         test1052672m1052662b = createANFile(testDir, "test1052672m1052662b", size, size - 10, (byte)'#');
 164         test1052672m60 = createANFile(testDir, "test1052672m60", size, 60, (byte)'$');
 165     }
 166 
 167     @AfterClass
 168     void cleanup() throws IOException {
 169         // clean up files created under the test directory
 170         Files.walk(testDir).map(Path::toFile).forEach(File::delete);
 171         Files.deleteIfExists(testDir);
 172     }
 173 
 174     /*
 175      * DataProvider for mismatch test. Provides the following fields:
 176      * path1 -- the path to a file
 177      * path2 -- the path to another file
 178      * expected -- expected result of the mismatch method
 179      * note -- a note about the test
 180      */
 181     @DataProvider(name = "testMismatch")
 182     public Object[][] getDataForMismatch() throws IOException {
 183         Path foo = Paths.get("foo");
 184 
 185         return new Object[][]{
 186             // Spec Case 1: the two paths locate the same file , even if one does not exist
 187             {foo, foo, MISMATCH_NO, "Same file, no mismatch"},
 188             {test0a, test0a, MISMATCH_NO, "Same file, no mismatch"},
 189             {test1024a, test1024a, MISMATCH_NO, "Same file, no mismatch"},
 190 
 191             // Spec Case 2:  The two files are the same size, and every byte in the first file
 192             // is identical to the corresponding byte in the second file.
 193             {test0a, test0b, MISMATCH_NO, "Sizes == 0, no mismatch"},
 194             {test1024a, test1024b, MISMATCH_NO, "size = 1024 < buffer = 8192, no mismatch"},
 195             {test8192a, test8192b, MISMATCH_NO, "size = 8192 = buffer = 8192, no mismatch"},
 196             {test65536a, test65536b, MISMATCH_NO, "read 8 * full buffer, no mismatch"},
 197             {test69632a, test69632b, MISMATCH_NO, "read 8 * full buffer plus a partial buffer, no mismatch"},
 198 
 199             // Spec Case 3: the value returned is the position of the first mismatched byte
 200             // Impl: the impl uses a buffer 8192. The testcases below covers a range of files
 201             // with sizes <= and > the buffer size. The last buffer is either full or partially full.
 202             {test1024m1014a, test1024m1014b, 1014, "read one partial buffer, mismatch = 1014"},
 203             {test1024m1014a, test1024m60, 60, "read one partial buffer, mismatch = 60"},
 204             {test1024m1014a, test8192m8182a, 1014, "one partial vs full buffer, mismatch = 1014"},
 205             {test8192m8182a, test8192m8182b, 8182, "read one full buffer, mismatch = 8182"},
 206             {test65536m65526a, test65536m65526b, 65526,
 207                 "read several times, mismatch in the last full buffer, mismatch = 65526"},
 208             {test65536m60, test65536m65526a, 60, "mismatch in the first buffer, mismatch = 60"},
 209             {test69632m69622a, test69632m69622b, 69622,
 210                 "mismatch in the last partial buffer, mismatch = 69622"},
 211             {test69632m69622a, test69632m60, 60, "mismatch in the first buffer, mismatch = 60"},
 212             {test1048576m1048566a, test1048576m1048566b, 1048566,
 213                 "mismatch in the last full buffer, mismatch = 1048566"},
 214             {test1048576m60, test1048576m1048566b, 60, "mismatch in the first buffer, mismatch = 60"},
 215             {test1052672m1052662a, test1052672m1052662b, 1052662,
 216                 "mismatch in the last partial buffer, mismatch = 1052662"},
 217             {test1052672m1052662a, test1052672m60, 60, "mismatch in the first buffer, mismatch = 60"},
 218 
 219             // Spec Case 4:  returns the size of the smaller file (in bytes) when the files are
 220             // different sizes and every byte of the smaller file is identical to the corresponding
 221             // byte of the larger file.
 222             // Impl: similar to case 3, covers a range of file sizes
 223             {test0a, test1024a, 0, "Size of one of files = 0, mismatch at 0"},
 224             {test1024a, test8192a, 1024, "mismatch is the length of the smaller file: 1024"},
 225             {test1024a, test65536a, 1024, "mismatch is the length of the smaller file: 1024"},
 226             {test8192a, test65536a, 8192, "mismatch is the length of the smaller file: 8192"},
 227             {test69632a, test65536a, 65536, "mismatch is the length of the smaller file: 65536"},
 228             {test1048576a, test69632a, 69632, "mismatch is the length of the smaller file: 69632"},
 229 
 230             // Spec Case 5: This method is always reflexive (for Path f , mismatch(f,f) returns -1L)
 231             // See tests for Spec Case 1.
 232 
 233             // Spec Case 6: If the file system and files remain static, then this method is symmetric
 234             // (for two Paths f and g, mismatch(f,g) will return the same value as mismatch(g,f)).
 235             // The following tests are selected from tests for Spec Case 3 with the order of
 236             // file paths switched, the returned values are the same as those for Case 3:
 237             {test1024m1014b, test1024m1014a, 1014, "read one partial buffer, mismatch = 1014"},
 238             {test1024m60, test1024m1014a, 60, "read one partial buffer, mismatch = 60"},
 239             {test8192m8182a, test1024m1014a, 1014, "one partial vs full buffer, mismatch = 1014"},
 240         };
 241     }
 242 
 243     /*
 244      * DataProvider for mismatch tests involving ZipFS using a few test cases selected
 245      * from those of the original mismatch tests.
 246      */
 247     @DataProvider(name = "testMismatchZipfs")
 248     public Object[][] getDataForMismatchZipfs() throws IOException {
 249 
 250         return new Object[][]{
 251             {test1024a, test1024a, MISMATCH_NO, "Compares the file and its copy in zip, no mismatch"},
 252             {test8192m8182a, test8192m8182b, 8182,
 253                 "Compares a copy of test8192m8182a in zip and test8192m8182b, shall return 8182"},
 254             {test1048576a, test65536a, 65536, "mismatch is the length of the smaller file: 65536"},
 255         };
 256     }
 257 
 258     /*
 259      * DataProvider for verifying null handling.
 260      */
 261     @DataProvider(name = "testFileNull")
 262     public Object[][] getDataForNull() throws IOException {
 263         return new Object[][]{
 264             {(Path)null, (Path)null},
 265             {(Path)null, test1024a},
 266             {test1024a, (Path)null},
 267         };
 268     }
 269 
 270     /*
 271      * DataProvider for verifying how the mismatch method handles the situation
 272      * when one or both files do not exist.
 273      */
 274     @DataProvider(name = "testFileNotExist")
 275     public Object[][] getDataForFileNotExist() throws IOException {
 276         return new Object[][]{
 277             {Paths.get("foo"), Paths.get("bar")},
 278             {Paths.get("foo"), test1024a},
 279             {test1024a, Paths.get("bar")},
 280         };
 281     }
 282 
 283     /**
 284      * Tests the mismatch method. Refer to the dataProvider testMismatch for more
 285      * details about the cases.
 286      * @param path the path to a file
 287      * @param path2 the path to another file
 288      * @param expected the expected result
 289      * @param msg the message about the test
 290      * @throws IOException if the test fails
 291      */
 292     @Test(dataProvider = "testMismatch", priority = 0)
 293     public void testMismatch(Path path, Path path2, long expected, String msg)
 294         throws IOException {
 295         long result = Files.mismatch(path, path2);
 296         Assert.assertTrue(result == expected, msg);
 297     }
 298 
 299     /**
 300      * Tests the mismatch method by comparing files with those in a ZIP file.
 301      * @param path the path to a file
 302      * @param path2 the path to another file to be added into a ZIP file
 303      * @param expected the expected result
 304      * @param msg the message about the test
 305      * @throws IOException if the test fails
 306      */
 307     @Test(dataProvider = "testMismatchZipfs", priority = 1)
 308     public void testMismatchZipfs(Path path, Path path2, long expected, String msg)
 309         throws IOException {
 310         Path zipPath = Paths.get(testDir.toString(), "TestWithFSZip.zip");
 311         try (FileSystem fs = getZipFSProvider().newFileSystem(zipPath, ZIPFS_MAP)) {
 312             Path copy = fs.getPath(path.getFileName().toString());
 313             Files.copy(path, copy, REPLACE_EXISTING);
 314 
 315             if (path2 == null) {
 316                 Assert.assertTrue(Files.mismatch(copy, path) == expected, msg);
 317             } else {
 318                 Assert.assertTrue(Files.mismatch(copy, path2) == expected, msg);
 319             }
 320         }
 321     }
 322 
 323     /**
 324      * Verifies that NullPointerException is thrown when one or both files are null.
 325      * @param path the path to a file
 326      * @param path2 the path to another file
 327      * @throws NullPointerException as expected
 328      */
 329     @Test(dataProvider = "testFileNull", priority = 2, expectedExceptions = NullPointerException.class)
 330     public void testMismatchNull(Path path, Path path2) throws Exception {
 331         long result = Files.mismatch(path, path2);
 332     }
 333 
 334     /**
 335      * Verifies that IOException is thrown when one or both files do not exist.
 336      * @param path the path to a file
 337      * @param path2 the path to another file
 338      * @throws IOException as expected
 339      */
 340     @Test(dataProvider = "testFileNotExist", priority = 2, expectedExceptions = IOException.class)
 341     public void testMismatchNotExist(Path path, Path path2) throws IOException {
 342         long result = Files.mismatch(path, path2);
 343     }
 344 
 345     /**
 346      * Creates a file with alphanumeric content with one character altered
 347      * at the specified position.
 348      *
 349      * @param dir the directory in which the file is to be created
 350      * @param purpose the purpose of the file
 351      * @param size the size of the file
 352      * @param pos the position where the alternative char is to be added. If it
 353      *            is smaller than zero, no alternation shall be made.
 354      * @param c the character
 355      * @return path of the created file
 356      * @throws IOException
 357      */
 358     private static Path createANFile(Path dir, String purpose, int size, int pos,
 359                                      byte c) throws IOException {
 360         Path path = Files.createFile(Paths.get(dir.toString(), purpose + ".txt"));
 361         if (size > 0) {
 362             writeAlphanumericFile(path, size, pos, c);
 363         }
 364         return path;
 365     }
 366 
 367     private static void writeAlphanumericFile(Path path, int size, int pos, byte c)
 368         throws IOException {
 369         byte[] a = createAlphanumericArray(size);
 370         if (pos > 0) a[pos] = c;
 371         Files.write(path, a);
 372     }
 373 
 374     private static byte[] createAlphanumericArray(int length) {
 375         byte[] bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 \n".getBytes();
 376         byte[] a = new byte[length];
 377         fillArray(a, bytes);
 378         return a;
 379     }
 380 
 381     private static FileSystemProvider getZipFSProvider() {
 382         for (FileSystemProvider provider : FileSystemProvider.installedProviders()) {
 383             if ("jar".equals(provider.getScheme())) {
 384                 return provider;
 385             }
 386         }
 387         return null;
 388     }
 389 
 390     /**
 391      * Fills the byte array by repeating the bytes from the sample array sequentially
 392      * until it is completely filled.
 393      *
 394      * @param samples the sample array
 395      * @return
 396      */
 397     private static void fillArray(byte[] a, byte[] samples) {
 398         int i1 = 0;
 399         for (int i=0; i<a.length; i++) {
 400             if (i1 < samples.length) {
 401                 a[i] = samples[i1++];
 402             } else {
 403                 i1 = 0;
 404                 a[i] = samples[i1++];
 405             }
 406         }
 407     }
 408 }