From fb20f610110cec480e51b4b4c4d40da99e60a200 Mon Sep 17 00:00:00 2001 From: Almaz Mingaleev Date: Mon, 26 Feb 2024 15:27:23 +0000 Subject: Add SplittableRandom test coverage. Fix: 326837274 Fix: 325776560 Fix: 325776507 Test: atest CtsLibcoreTestCases:libcore.java.util.SplittableRandomTest Change-Id: Idee5a3b2cdfbdcbce23b31dac6d5d07aa28ba6ff --- .../libcore/java/util/SplittableRandomTest.java | 96 ++++++++++++++++++++-- 1 file changed, 90 insertions(+), 6 deletions(-) diff --git a/luni/src/test/java/libcore/java/util/SplittableRandomTest.java b/luni/src/test/java/libcore/java/util/SplittableRandomTest.java index e1bb9309736..85f83c77732 100644 --- a/luni/src/test/java/libcore/java/util/SplittableRandomTest.java +++ b/luni/src/test/java/libcore/java/util/SplittableRandomTest.java @@ -16,9 +16,11 @@ package libcore.java.util; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThrows; import java.util.SplittableRandom; +import java.util.random.RandomGenerator.SplittableGenerator; import org.junit.Assert; import org.junit.Test; @@ -34,18 +36,36 @@ public class SplittableRandomTest { SplittableRandom random1 = new SplittableRandom(seed); SplittableRandom random2 = new SplittableRandom(seed); - assertEquals(random1, random2); + assertGeneratorsAreEquals(random1, random2); } @Test - public void split_throwsNullWhenSourceIsNull() { + public void split_throwsNPE_whenSourceIsNull() { SplittableRandom random = new SplittableRandom(42); assertThrows(NullPointerException.class, () -> random.split(/* source= */ null)); + assertThrows(NullPointerException.class, () -> random.splits(1, /* source= */ null)); + assertThrows(NullPointerException.class, () -> random.splits(/* source= */ null)); } @Test - public void splitInstances_areTheSameWhenTheyAreSplitWithIdenticalSource() { + public void split_throwsIAE_whenSizeIsNonPositive() { + SplittableRandom random = new SplittableRandom(42); + + assertThrows(IllegalArgumentException.class, () -> random.splits(-1, random)); + assertThrows(IllegalArgumentException.class, () -> random.splits(-1)); + } + + @Test + public void splits_streamOfSizeZero_areEmpty() { + var random = new SplittableRandom(); + + assertEquals(0L, random.splits(0).count()); + assertEquals(0L, random.splits(0, random).count()); + } + + @Test + public void splitInstances_areTheSame_whenTheyAreSplitWithIdenticalSource() { var seed = 1001; var random1 = new SplittableRandom(seed); var random2 = new SplittableRandom(seed); @@ -55,12 +75,76 @@ public class SplittableRandomTest { var splitRandom1 = random1.split(new SplittableRandom(sourceSeed)); var splitRandom2 = random2.split(new SplittableRandom(sourceSeed)); - assertEquals(splitRandom1, splitRandom2); + assertGeneratorsAreEquals(splitRandom1, splitRandom2); + } + + @Test + public void splitsInstances_areTheSame_whenTheyAreSplitWithIdenticalSource_boundedStream() { + var seed = 1001; + + var random1 = new SplittableRandom(seed); + var random2 = new SplittableRandom(seed); + + var sourceSeed = 9999; + var size = 10; + + var splitRandom1 = random1.splits(size, new SplittableRandom(sourceSeed)).toList(); + var splitRandom2 = random2.splits(size, new SplittableRandom(sourceSeed)).toList(); + + assertEquals(size, splitRandom1.size()); + assertEquals(size, splitRandom2.size()); + + for (int i = 0; i < size; ++i) { + assertGeneratorsAreEquals(splitRandom1.get(i), splitRandom2.get(i)); + } + } + + + @Test + public void splitsInstances_areTheSame_whenTheyAreSplitWithIdenticalSource_unboundedStream() { + var seed = 1001; + + var random1 = new SplittableRandom(seed); + var random2 = new SplittableRandom(seed); + + var sourceSeed = 9999; + var size = 10; + + var splitRandom1 = random1.splits(new SplittableRandom(sourceSeed)).limit(size).toList(); + var splitRandom2 = random2.splits(new SplittableRandom(sourceSeed)).limit(size).toList(); + + assertEquals(size, splitRandom1.size()); + assertEquals(size, splitRandom2.size()); + + for (int i = 0; i < size; ++i) { + assertGeneratorsAreEquals(splitRandom1.get(i), splitRandom2.get(i)); + } + } + + @Test + public void splitsInstances_areTheSame_whenSourceIsIdentical() { + var seed = 1001; + + var random1 = new SplittableRandom(seed); + var random2 = new SplittableRandom(seed); + + var size = 10; + + var splitRandom1 = random1.splits(size).toList(); + var splitRandom2 = random2.splits(size).toList(); + + assertEquals(size, splitRandom1.size()); + assertEquals(size, splitRandom2.size()); + + for (int i = 0; i < size; ++i) { + assertGeneratorsAreEquals(splitRandom1.get(i), splitRandom2.get(i)); + } } - private static void assertEquals(SplittableRandom random1, SplittableRandom random2) { + private static void assertGeneratorsAreEquals(SplittableGenerator random1, + SplittableGenerator random2) { for (int i = 0; i < 1_000; ++i) { - Assert.assertEquals(random1.nextLong(), random2.nextLong()); + assertEquals(random1.nextLong(), random2.nextLong()); } } -- cgit v1.2.3 From 3197d05cc2fe50b9146c7ddb3d461713a59e3aec Mon Sep 17 00:00:00 2001 From: Almaz Mingaleev Date: Mon, 26 Feb 2024 16:12:37 +0000 Subject: Add RandomGenerator.SplittableGenerator test coverage. Fix: 325776330 Fix: 326836519 Test: atest CtsLibcoreTestCases:libcore.java.util.random.SplittableGeneratorTest Change-Id: I9f43a5145926a6eb97296718ec2490f435f68cbd --- .../java/util/random/SplittableGeneratorTest.java | 66 ++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 luni/src/test/java/libcore/java/util/random/SplittableGeneratorTest.java diff --git a/luni/src/test/java/libcore/java/util/random/SplittableGeneratorTest.java b/luni/src/test/java/libcore/java/util/random/SplittableGeneratorTest.java new file mode 100644 index 00000000000..9f0f99cc2fc --- /dev/null +++ b/luni/src/test/java/libcore/java/util/random/SplittableGeneratorTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.java.util.random; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.SplittableRandom; +import java.util.random.RandomGenerator; +import java.util.random.RandomGenerator.SplittableGenerator; +import java.util.random.RandomGeneratorFactory; + +@RunWith(JUnit4.class) +public class SplittableGeneratorTest { + + @Test + public void splits_produceTheSameResult_whenSourceIsIdentical() { + var sourceSeed = 12345; + var size = 10; + + SplittableGenerator random1 = new SplittableRandom(sourceSeed); + SplittableGenerator random2 = new SplittableRandom(sourceSeed); + + var splitRandom1 = random1.splits().limit(size).toList(); + var splitRandom2 = random2.splits().limit(size).toList(); + + assertEquals(size, splitRandom1.size()); + assertEquals(size, splitRandom2.size()); + + for (int i = 0; i < size; ++i) { + assertGeneratorsAreEquals(splitRandom1.get(i), splitRandom2.get(i)); + } + } + + @Test + public void of_acceptsAllSplittablesFromRandomGeneratorFactory() { + RandomGeneratorFactory.all() + .filter(RandomGeneratorFactory::isSplittable) + .forEach(factory -> SplittableGenerator.of(factory.name())); + } + + private static void assertGeneratorsAreEquals(SplittableGenerator random1, + SplittableGenerator random2) { + for (int i = 0; i < 1_000; ++i) { + assertEquals(random1.nextLong(), random2.nextLong()); + } + } + +} -- cgit v1.2.3 From ab03ae0edd58d170f101f83fe12848436b00cefe Mon Sep 17 00:00:00 2001 From: Almaz Mingaleev Date: Tue, 27 Feb 2024 10:16:01 +0000 Subject: Import test.java.util.zip from jdk-17.0.10-ga List of files: ojluni/src/test/java/util/zip/CloseInflaterDeflaterTest.java ojluni/src/test/java/util/zip/DeInflate.java ojluni/src/test/java/util/zip/DeflaterDictionaryTests.java ojluni/src/test/java/util/zip/ZipFile/TestCleaner.java Generated by tools/expected_upstream/ojluni_merge_to_main.py Bug: 254213857 Test: N/A No-Typo-Check: Imported files Change-Id: Ia7fc95cdb5bf1551c4a24e68b65d36f799b537c2 --- EXPECTED_UPSTREAM | 4 + .../java/util/zip/CloseInflaterDeflaterTest.java | 209 +++++++++++++ ojluni/src/test/java/util/zip/DeInflate.java | 333 +++++++++++++++++++++ .../java/util/zip/DeflaterDictionaryTests.java | 252 ++++++++++++++++ .../test/java/util/zip/ZipFile/TestCleaner.java | 156 ++++++++++ 5 files changed, 954 insertions(+) create mode 100644 ojluni/src/test/java/util/zip/CloseInflaterDeflaterTest.java create mode 100644 ojluni/src/test/java/util/zip/DeInflate.java create mode 100644 ojluni/src/test/java/util/zip/DeflaterDictionaryTests.java create mode 100644 ojluni/src/test/java/util/zip/ZipFile/TestCleaner.java diff --git a/EXPECTED_UPSTREAM b/EXPECTED_UPSTREAM index 9957c025eb8..419cbfd28aa 100644 --- a/EXPECTED_UPSTREAM +++ b/EXPECTED_UPSTREAM @@ -2612,11 +2612,15 @@ ojluni/src/test/java/util/stream/testlib/org/openjdk/testlib/java/util/Spliterat ojluni/src/test/java/util/stream/testlib/org/openjdk/testlib/java/util/stream/DefaultMethodStreams.java,jdk17u/jdk-17.0.6-ga,test/jdk/lib/testlibrary/bootlib/java.base/java/util/stream/DefaultMethodStreams.java ojluni/src/test/java/util/stream/testlib/org/openjdk/testlib/java/util/stream/ThrowableHelper.java,jdk17u/jdk-17.0.6-ga,test/jdk/lib/testlibrary/bootlib/java.base/java/util/stream/ThrowableHelper.java ojluni/src/test/java/util/zip/ChecksumBase.java,jdk17u/jdk-17.0.6-ga,test/jdk/java/util/zip/ChecksumBase.java +ojluni/src/test/java/util/zip/CloseInflaterDeflaterTest.java,jdk17u/jdk-17.0.10-ga,test/jdk/java/util/zip/CloseInflaterDeflaterTest.java +ojluni/src/test/java/util/zip/DeInflate.java,jdk17u/jdk-17.0.10-ga,test/jdk/java/util/zip/DeInflate.java +ojluni/src/test/java/util/zip/DeflaterDictionaryTests.java,jdk17u/jdk-17.0.10-ga,test/jdk/java/util/zip/DeflaterDictionaryTests.java ojluni/src/test/java/util/zip/TestCRC32.java,jdk17u/jdk-17.0.6-ga,test/jdk/java/util/zip/TestCRC32.java ojluni/src/test/java/util/zip/TestCRC32C.java,jdk17u/jdk-17.0.6-ga,test/jdk/java/util/zip/TestCRC32C.java ojluni/src/test/java/util/zip/TestChecksum.java,jdk17u/jdk-17.0.6-ga,test/jdk/java/util/zip/TestChecksum.java ojluni/src/test/java/util/zip/TestExtraTime.java,jdk17u/jdk-17.0.6-ga,test/jdk/java/util/zip/TestExtraTime.java ojluni/src/test/java/util/zip/TestLocalTime.java,jdk17u/jdk-17.0.6-ga,test/jdk/java/util/zip/TestLocalTime.java +ojluni/src/test/java/util/zip/ZipFile/TestCleaner.java,jdk17u/jdk-17.0.10-ga,test/jdk/java/util/zip/ZipFile/TestCleaner.java ojluni/src/test/java/util/zip/ZipFile/Zip64SizeTest.java,jdk11u/jdk-11.0.13-ga,test/jdk/java/util/zip/ZipFile/Zip64SizeTest.java ojluni/src/tools/make/gensrc/GensrcBuffer.gmk,jdk17u/jdk-17.0.6-ga,make/modules/java.base/gensrc/GensrcBuffer.gmk ojluni/src/tools/make/gensrc/GensrcCharsetCoder.gmk,jdk17u/jdk-17.0.6-ga,make/modules/java.base/gensrc/GensrcCharsetCoder.gmk diff --git a/ojluni/src/test/java/util/zip/CloseInflaterDeflaterTest.java b/ojluni/src/test/java/util/zip/CloseInflaterDeflaterTest.java new file mode 100644 index 00000000000..b8f91971060 --- /dev/null +++ b/ojluni/src/test/java/util/zip/CloseInflaterDeflaterTest.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8193682 8278794 8284771 + * @summary Test Infinite loop while writing on closed Deflater and Inflater. + * @run testng CloseInflaterDeflaterTest + */ +import java.io.*; +import java.util.Random; +import java.util.jar.JarOutputStream; +import java.util.zip.DeflaterInputStream; +import java.util.zip.DeflaterOutputStream; +import java.util.zip.GZIPOutputStream; +import java.util.zip.InflaterOutputStream; +import java.util.zip.ZipOutputStream; +import java.util.zip.ZipEntry; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; +import static org.testng.Assert.assertThrows; + + +public class CloseInflaterDeflaterTest { + + // Number of bytes to write/read from Deflater/Inflater + private static final int INPUT_LENGTH= 512; + // OutputStream that will throw an exception during a write operation + private static OutputStream outStream = new OutputStream() { + @Override + public void write(byte[] b, int off, int len) throws IOException { + throw new IOException(); + } + @Override + public void write(byte[] b) throws IOException {} + @Override + public void write(int b) throws IOException {} + }; + // InputStream that will throw an exception during a read operation + private static InputStream inStream = new InputStream() { + @Override + public int read(byte[] b, int off, int len) throws IOException { + throw new IOException(); + } + @Override + public int read(byte[] b) throws IOException { throw new IOException();} + @Override + public int read() throws IOException { throw new IOException();} + }; + // Input bytes for read/write operation + private static byte[] inputBytes = new byte[INPUT_LENGTH]; + // Random function to add bytes to inputBytes + private static Random rand = new Random(); + + /** + * DataProvider to specify whether to use close() or finish() of OutputStream + * + * @return Entry object indicating which method to use for closing OutputStream + */ + @DataProvider + public Object[][] testOutputStreams() { + return new Object[][] { + { true }, + { false }, + }; + } + + /** + * DataProvider to specify on which outputstream closeEntry() has to be called + * + * @return Entry object returning either JarOutputStream or ZipOutputStream + */ + @DataProvider + public Object[][] testZipAndJar() throws IOException{ + return new Object[][] { + { new JarOutputStream(outStream)}, + { new ZipOutputStream(outStream)}, + }; + } + + /** + * Add inputBytes array with random bytes to write into OutputStream + */ + @BeforeTest + public void before_test() + { + rand.nextBytes(inputBytes); + } + + /** + * Test for infinite loop by writing bytes to closed GZIPOutputStream + * + * @param useCloseMethod indicates whether to use Close() or finish() method + * @throws IOException if an error occurs + */ + @Test(dataProvider = "testOutputStreams") + public void testGZip(boolean useCloseMethod) throws IOException { + GZIPOutputStream gzip = new GZIPOutputStream(outStream); + gzip.write(inputBytes, 0, INPUT_LENGTH); + assertThrows(IOException.class, () -> { + // Close GZIPOutputStream + if (useCloseMethod) { + gzip.close(); + } else { + gzip.finish(); + } + }); + // Write on a closed GZIPOutputStream, closed Deflater IOException expected + assertThrows(NullPointerException.class , () -> gzip.write(inputBytes, 0, INPUT_LENGTH)); + } + + /** + * Test for infinite loop by writing bytes to closed DeflaterOutputStream + * + * @param useCloseMethod indicates whether to use Close() or finish() method + * @throws IOException if an error occurs + */ + @Test(dataProvider = "testOutputStreams") + public void testDeflaterOutputStream(boolean useCloseMethod) throws IOException { + DeflaterOutputStream def = new DeflaterOutputStream(outStream); + assertThrows(IOException.class , () -> def.write(inputBytes, 0, INPUT_LENGTH)); + assertThrows(IOException.class, () -> { + // Close DeflaterOutputStream + if (useCloseMethod) { + def.close(); + } else { + def.finish(); + } + }); + // Write on a closed DeflaterOutputStream, 'Deflater has been closed' NPE is expected + assertThrows(NullPointerException.class , () -> def.write(inputBytes, 0, INPUT_LENGTH)); + } + + /** + * Test for infinite loop by reading bytes from closed DeflaterInputStream + * + * @throws IOException if an error occurs + */ + @Test + public void testDeflaterInputStream() throws IOException { + DeflaterInputStream def = new DeflaterInputStream(inStream); + assertThrows(IOException.class , () -> def.read(inputBytes, 0, INPUT_LENGTH)); + // Close DeflaterInputStream + def.close(); + // Read from a closed DeflaterInputStream, closed Deflater IOException expected + assertThrows(IOException.class , () -> def.read(inputBytes, 0, INPUT_LENGTH)); + } + + /** + * Test for infinite loop by writing bytes to closed InflaterOutputStream + * + * Note: Disabling this test as it is failing intermittently. + * @param useCloseMethod indicates whether to use Close() or finish() method + * @throws IOException if an error occurs + */ + @Test(dataProvider = "testOutputStreams",enabled=false) + public void testInflaterOutputStream(boolean useCloseMethod) throws IOException { + InflaterOutputStream inf = new InflaterOutputStream(outStream); + assertThrows(IOException.class , () -> inf.write(inputBytes, 0, INPUT_LENGTH)); + assertThrows(IOException.class , () -> { + // Close InflaterOutputStream + if (useCloseMethod) { + inf.close(); + } else { + inf.finish(); + } + }); + // Write on a closed InflaterOutputStream , closed Inflater IOException expected + assertThrows(IOException.class , () -> inf.write(inputBytes, 0, INPUT_LENGTH)); + } + + /** + * Test for infinite loop by writing bytes to closed ZipOutputStream/JarOutputStream + * + * @param zip will be the instance of either JarOutputStream or ZipOutputStream + * @throws IOException if an error occurs + */ + @Test(dataProvider = "testZipAndJar") + public void testZipCloseEntry(ZipOutputStream zip) throws IOException { + assertThrows(IOException.class , () -> zip.putNextEntry(new ZipEntry(""))); + zip.write(inputBytes, 0, INPUT_LENGTH); + assertThrows(IOException.class , () -> zip.closeEntry()); + // Write on a closed ZipOutputStream , 'Deflater has been closed' NPE is expected + assertThrows(NullPointerException.class , () -> zip.write(inputBytes, 0, INPUT_LENGTH)); + } + +} diff --git a/ojluni/src/test/java/util/zip/DeInflate.java b/ojluni/src/test/java/util/zip/DeInflate.java new file mode 100644 index 00000000000..d3785f0360e --- /dev/null +++ b/ojluni/src/test/java/util/zip/DeInflate.java @@ -0,0 +1,333 @@ +/* + * Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 7110149 8184306 6341887 + * @summary Test basic deflater & inflater functionality + * @key randomness + */ + +import java.io.*; +import java.nio.*; +import java.util.*; +import java.util.zip.*; + + +public class DeInflate { + + private static Random rnd = new Random(); + + + static void checkStream(Deflater def, byte[] in, int len, + byte[] out1, byte[] out2, boolean nowrap) + throws Throwable + { + Arrays.fill(out1, (byte)0); + Arrays.fill(out2, (byte)0); + + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + try (DeflaterOutputStream defos = new DeflaterOutputStream(baos, def)) { + defos.write(in, 0, len); + } + out1 = baos.toByteArray(); + } + int m = out1.length; + + Inflater inf = new Inflater(nowrap); + inf.setInput(out1, 0, m); + int n = inf.inflate(out2); + + if (n != len || + !Arrays.equals(Arrays.copyOf(in, len), Arrays.copyOf(out2, len)) || + inf.inflate(out2) != 0) { + System.out.printf("m=%d, n=%d, len=%d, eq=%b%n", + m, n, len, Arrays.equals(in, out2)); + throw new RuntimeException("De/inflater failed:" + def); + } + } + + static void checkByteBuffer(Deflater def, Inflater inf, + ByteBuffer in, ByteBuffer out1, ByteBuffer out2, + byte[] expected, int len, byte[] result, + boolean out1ReadOnlyWhenInflate) + throws Throwable { + def.reset(); + inf.reset(); + + def.setInput(in); + def.finish(); + int m = def.deflate(out1); + + out1.flip(); + if (out1ReadOnlyWhenInflate) + out1 = out1.asReadOnlyBuffer(); + inf.setInput(out1); + int n = inf.inflate(out2); + + out2.flip(); + out2.get(result, 0, n); + + if (n != len || out2.position() != len || + !Arrays.equals(Arrays.copyOf(expected, len), Arrays.copyOf(result, len)) || + inf.inflate(result) != 0) { + throw new RuntimeException("De/inflater(buffer) failed:" + def); + } + } + + static void checkByteBufferReadonly(Deflater def, Inflater inf, + ByteBuffer in, ByteBuffer out1, ByteBuffer out2) + throws Throwable { + def.reset(); + inf.reset(); + def.setInput(in); + def.finish(); + int m = -1; + if (!out2.isReadOnly()) + out2 = out2.asReadOnlyBuffer(); + try { + m = def.deflate(out2); + throw new RuntimeException("deflater: ReadOnlyBufferException: failed"); + } catch (ReadOnlyBufferException robe) {} + m = def.deflate(out1); + out1.flip(); + inf.setInput(out1); + try { + inf.inflate(out2); + throw new RuntimeException("inflater: ReadOnlyBufferException: failed"); + } catch (ReadOnlyBufferException robe) {} + } + + /** + * Uses the {@code def} deflater to deflate the input data {@code in} of length {@code len}. + * A new {@link Inflater} is then created within this method to inflate the deflated data. The + * inflated data is then compared with the {@code in} to assert that it matches the original + * input data. + * This method repeats these checks for the different overloaded methods of + * {@code Deflater.deflate(...)} and {@code Inflater.inflate(...)} + * + * @param def the deflater to use for deflating the contents in {@code in} + * @param in the input content + * @param len the length of the input content to use + * @param nowrap will be passed to the constructor of the {@code Inflater} used in this + * method + * @throws Throwable if any error occurs during the check + */ + static void check(Deflater def, byte[] in, int len, boolean nowrap) + throws Throwable + { + byte[] tempBuffer = new byte[1024]; + byte[] out1, out2; + int m = 0, n = 0; + Inflater inf = new Inflater(nowrap); + def.setInput(in, 0, len); + def.finish(); + + try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + while (!def.finished()) { + int temp_counter = def.deflate(tempBuffer); + m += temp_counter; + baos.write(tempBuffer, 0, temp_counter); + } + out1 = baos.toByteArray(); + baos.reset(); + + inf.setInput(out1, 0, m); + + while (!inf.finished()) { + int temp_counter = inf.inflate(tempBuffer); + n += temp_counter; + baos.write(tempBuffer, 0, temp_counter); + } + out2 = baos.toByteArray(); + if (n != len || + !Arrays.equals(in, 0, len, out2, 0, len) || + inf.inflate(out2) != 0) { + System.out.printf("m=%d, n=%d, len=%d, eq=%b%n", + m, n, len, Arrays.equals(in, out2)); + throw new RuntimeException("De/inflater failed:" + def); + } + } + + // readable + Arrays.fill(out1, (byte)0); + Arrays.fill(out2, (byte)0); + ByteBuffer bbIn = ByteBuffer.wrap(in, 0, len); + ByteBuffer bbOut1 = ByteBuffer.wrap(out1); + ByteBuffer bbOut2 = ByteBuffer.wrap(out2); + checkByteBuffer(def, inf, bbIn, bbOut1, bbOut2, in, len, out2, false); + checkByteBufferReadonly(def, inf, bbIn, bbOut1, bbOut2); + + // readonly in + Arrays.fill(out1, (byte)0); + Arrays.fill(out2, (byte)0); + bbIn = ByteBuffer.wrap(in, 0, len).asReadOnlyBuffer(); + bbOut1 = ByteBuffer.wrap(out1); + bbOut2 = ByteBuffer.wrap(out2); + checkByteBuffer(def, inf, bbIn, bbOut1, bbOut2, in, len, out2, false); + checkByteBufferReadonly(def, inf, bbIn, bbOut1, bbOut2); + + // readonly out1 when inflate + Arrays.fill(out1, (byte)0); + Arrays.fill(out2, (byte)0); + bbIn = ByteBuffer.wrap(in, 0, len); + bbOut1 = ByteBuffer.wrap(out1); + bbOut2 = ByteBuffer.wrap(out2); + checkByteBuffer(def, inf, bbIn, bbOut1, bbOut2, in, len, out2, true); + checkByteBufferReadonly(def, inf, bbIn, bbOut1, bbOut2); + + // direct + bbIn = ByteBuffer.allocateDirect(in.length); + bbIn.put(in, 0, n).flip(); + bbOut1 = ByteBuffer.allocateDirect(out1.length); + bbOut2 = ByteBuffer.allocateDirect(out2.length); + checkByteBuffer(def, inf, bbIn, bbOut1, bbOut2, in, len, out2, false); + checkByteBufferReadonly(def, inf, bbIn, bbOut1, bbOut2); + } + + static void checkDict(Deflater def, Inflater inf, byte[] src, + byte[] dstDef, byte[] dstInf, + ByteBuffer dictDef, ByteBuffer dictInf) throws Throwable { + def.reset(); + inf.reset(); + + def.setDictionary(dictDef); + def.setInput(src); + def.finish(); + int n = def.deflate(dstDef); + + inf.setInput(dstDef, 0, n); + n = inf.inflate(dstInf); + if (n != 0 || !inf.needsDictionary()) { + throw new RuntimeException("checkDict failed: need dict to continue"); + } + inf.setDictionary(dictInf); + n = inf.inflate(dstInf); + // System.out.println("result: " + new String(dstInf, 0, n)); + if (n != src.length || !Arrays.equals(Arrays.copyOf(dstInf, n), src)) { + throw new RuntimeException("checkDict failed: inflate result"); + } + } + + static void checkDict(int level, int strategy) throws Throwable { + + Deflater def = newDeflater(level, strategy, false, new byte[0]); + Inflater inf = new Inflater(); + + byte[] src = "hello world, hello world, hello sherman".getBytes(); + byte[] dict = "hello".getBytes(); + + byte[] dstDef = new byte[1024]; + byte[] dstInf = new byte[1024]; + + def.setDictionary(dict); + def.setInput(src); + def.finish(); + int n = def.deflate(dstDef); + + inf.setInput(dstDef, 0, n); + n = inf.inflate(dstInf); + if (n != 0 || !inf.needsDictionary()) { + throw new RuntimeException("checkDict failed: need dict to continue"); + } + inf.setDictionary(dict); + n = inf.inflate(dstInf); + //System.out.println("result: " + new String(dstInf, 0, n)); + if (n != src.length || !Arrays.equals(Arrays.copyOf(dstInf, n), src)) { + throw new RuntimeException("checkDict failed: inflate result"); + } + + ByteBuffer dictDef = ByteBuffer.wrap(dict); + ByteBuffer dictInf = ByteBuffer.wrap(dict); + checkDict(def, inf, src, dstDef, dstInf, dictDef, dictInf); + + dictDef = ByteBuffer.allocateDirect(dict.length); + dictInf = ByteBuffer.allocateDirect(dict.length); + dictDef.put(dict).flip(); + dictInf.put(dict).flip(); + checkDict(def, inf, src, dstDef, dstInf, dictDef, dictInf); + + def.end(); + inf.end(); + } + + private static Deflater newDeflater(int level, int strategy, boolean dowrap, byte[] tmp) { + Deflater def = new Deflater(level, dowrap); + if (strategy != Deflater.DEFAULT_STRATEGY) { + def.setStrategy(strategy); + // The first invocation after setLevel/Strategy() + // with a different level/stragety returns 0, if + // there is no need to flush out anything for the + // previous setting/"data", this is tricky and + // appears un-documented. + def.deflate(tmp); + } + return def; + } + + private static Deflater resetDeflater(Deflater def, int level, int strategy) { + def.setLevel(level); + def.setStrategy(strategy); + def.reset(); + return def; + } + + public static void main(String[] args) throws Throwable { + + byte[] dataIn = new byte[1024 * 512]; + rnd.nextBytes(dataIn); + byte[] dataOut1 = new byte[dataIn.length + 1024]; + byte[] dataOut2 = new byte[dataIn.length]; + + Deflater defNotWrap = new Deflater(Deflater.DEFAULT_COMPRESSION, false); + Deflater defWrap = new Deflater(Deflater.DEFAULT_COMPRESSION, true); + + for (int level = Deflater.DEFAULT_COMPRESSION; + level <= Deflater.BEST_COMPRESSION; level++) { + for (int strategy = Deflater.DEFAULT_STRATEGY; + strategy <= Deflater.HUFFMAN_ONLY; strategy++) { + for (boolean dowrap : new boolean[] { false, true }) { + System.out.println("level:" + level + + ", strategy: " + strategy + + ", dowrap: " + dowrap); + for (int i = 0; i < 5; i++) { + int len = (i == 0)? dataIn.length + : new Random().nextInt(dataIn.length); + System.out.println("iteration: " + (i + 1) + " input length: " + len); + // use a new deflater + Deflater def = newDeflater(level, strategy, dowrap, dataOut2); + check(def, dataIn, len, dowrap); + def.end(); + + // reuse the deflater (with reset) and test on stream, which + // uses a "smaller" buffer (smaller than the overall data) + def = resetDeflater(dowrap ? defWrap : defNotWrap, level, strategy); + checkStream(def, dataIn, len, dataOut1, dataOut2, dowrap); + } + } + // test setDictionary() + checkDict(level, strategy); + } + } + } +} diff --git a/ojluni/src/test/java/util/zip/DeflaterDictionaryTests.java b/ojluni/src/test/java/util/zip/DeflaterDictionaryTests.java new file mode 100644 index 00000000000..17d2b735806 --- /dev/null +++ b/ojluni/src/test/java/util/zip/DeflaterDictionaryTests.java @@ -0,0 +1,252 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import org.testng.Assert; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.testng.Assert.assertThrows; + +/** + * @test + * @bug 8252739 + * @summary Verify Deflater.setDictionary(dictionary, offset, length) uses the offset + * @run testng/othervm DeflaterDictionaryTests + */ +public class DeflaterDictionaryTests { + // Output buffer size + private static final int RESULT_SIZE = 1024; + // Data to compress + private static final String SRC_DATA = "Welcome to the US Open;".repeat(6); + // Dictionary to be used + private static final String DICTIONARY = "US Open"; + private static final int DICTIONARY_OFFSET = 1; + private static final int DICTIONARY_LENGTH = 3; + + /** + * DataProvider with offsets which should be valid for setDictionary + * + * @return valid offset values + */ + @DataProvider(name = "validDictionaryOffsets") + protected Object[][] validDictionaryOffsets() { + return new Object[][]{ + {0}, + {DICTIONARY_OFFSET}, + {DICTIONARY_LENGTH} + }; + } + + /** + * DataProvider with invalid offsets for setDictionary + * + * @return invalid offset values + */ + @DataProvider(name = "invalidDictionaryOffsets") + protected Object[][] invalidDictionaryOffsets() { + return new Object[][]{ + {-1}, + {DICTIONARY_LENGTH + 2}, + {DICTIONARY.length()} + }; + } + + /** + * Validate that an offset can be used with Deflater::setDictionary + * + * @param dictionary_offset offset value to be used + * @throws Exception if an error occurs + */ + @Test(dataProvider = "validDictionaryOffsets") + public void testByteArray(int dictionary_offset) throws Exception { + byte[] input = SRC_DATA.getBytes(UTF_8); + byte[] output = new byte[RESULT_SIZE]; + Deflater deflater = new Deflater(); + Inflater inflater = new Inflater(); + try { + // Compress the bytes + deflater.setDictionary(DICTIONARY.getBytes(UTF_8), dictionary_offset, DICTIONARY_LENGTH); + deflater.setInput(input); + deflater.finish(); + int compressedDataLength = deflater.deflate(output, 0, output.length, Deflater.NO_FLUSH); + System.out.printf("Deflater::getTotalOut:%s, Deflater::getAdler: %s," + + " compressed length: %s%n", deflater.getTotalOut(), + deflater.getTotalOut(), compressedDataLength); + deflater.finished(); + + // Decompress the bytes + inflater.setInput(output, 0, compressedDataLength); + byte[] result = new byte[RESULT_SIZE]; + int resultLength = inflater.inflate(result); + if (inflater.needsDictionary()) { + System.out.println("Specifying Dictionary"); + inflater.setDictionary(DICTIONARY.getBytes(UTF_8), dictionary_offset, DICTIONARY_LENGTH); + resultLength = inflater.inflate(result); + } else { + System.out.println("Did not need to use a Dictionary"); + } + inflater.finished(); + System.out.printf("Inflater::getAdler:%s, length: %s%n", + inflater.getAdler(), resultLength); + + Assert.assertEquals(SRC_DATA.length(), resultLength); + Assert.assertEquals(input, Arrays.copyOf(result, resultLength)); + } finally { + // Release Resources + deflater.end(); + inflater.end(); + } + } + + /** + * Validate that a ByteBuffer can be used with Deflater::setDictionary + * + * @throws Exception if an error occurs + */ + @Test + public void testHeapByteBuffer() throws Exception { + byte[] input = SRC_DATA.getBytes(UTF_8); + byte[] output = new byte[RESULT_SIZE]; + ByteBuffer dictDef = ByteBuffer.wrap(DICTIONARY.getBytes(UTF_8), DICTIONARY_OFFSET, DICTIONARY_LENGTH); + ByteBuffer dictInf = ByteBuffer.wrap(DICTIONARY.getBytes(UTF_8), DICTIONARY_OFFSET, DICTIONARY_LENGTH); + Deflater deflater = new Deflater(); + Inflater inflater = new Inflater(); + try { + // Compress the bytes + deflater.setDictionary(dictDef); + deflater.setInput(input); + deflater.finish(); + int compressedDataLength = deflater.deflate(output, 0, output.length, Deflater.NO_FLUSH); + System.out.printf("Deflater::getTotalOut:%s, Deflater::getAdler: %s," + + " compressed length: %s%n", deflater.getTotalOut(), + deflater.getTotalOut(), compressedDataLength); + deflater.finished(); + + // Decompress the bytes + inflater.setInput(output, 0, compressedDataLength); + byte[] result = new byte[RESULT_SIZE]; + int resultLength = inflater.inflate(result); + if (inflater.needsDictionary()) { + System.out.println("Specifying Dictionary"); + inflater.setDictionary(dictInf); + resultLength = inflater.inflate(result); + } else { + System.out.println("Did not need to use a Dictionary"); + } + inflater.finished(); + System.out.printf("Inflater::getAdler:%s, length: %s%n", + inflater.getAdler(), resultLength); + + Assert.assertEquals(SRC_DATA.length(), resultLength); + Assert.assertEquals(input, Arrays.copyOf(result, resultLength)); + } finally { + // Release Resources + deflater.end(); + inflater.end(); + } + } + + /** + * Validate that ByteBuffer::allocateDirect can be used with Deflater::setDictionary + * + * @throws Exception if an error occurs + */ + @Test + public void testByteBufferDirect() throws Exception { + byte[] input = SRC_DATA.getBytes(UTF_8); + byte[] output = new byte[RESULT_SIZE]; + ByteBuffer dictDef = ByteBuffer.allocateDirect(DICTIONARY.length()); + ByteBuffer dictInf = ByteBuffer.allocateDirect(DICTIONARY.length()); + dictDef.put(DICTIONARY.getBytes(UTF_8)); + dictInf.put(DICTIONARY.getBytes(UTF_8)); + dictDef.position(DICTIONARY_OFFSET); + dictDef.limit(DICTIONARY_LENGTH); + dictInf.position(DICTIONARY_OFFSET); + dictInf.limit(DICTIONARY_LENGTH); + Deflater deflater = new Deflater(); + Inflater inflater = new Inflater(); + try { + // Compress the bytes + deflater.setDictionary(dictDef.slice()); + deflater.setInput(input); + deflater.finish(); + int compressedDataLength = deflater.deflate(output, 0, output.length, Deflater.NO_FLUSH); + System.out.printf("Deflater::getTotalOut:%s, Deflater::getAdler: %s," + + " compressed length: %s%n", deflater.getTotalOut(), + deflater.getTotalOut(), compressedDataLength); + deflater.finished(); + + // Decompress the bytes + inflater.setInput(output, 0, compressedDataLength); + byte[] result = new byte[RESULT_SIZE]; + int resultLength = inflater.inflate(result); + if (inflater.needsDictionary()) { + System.out.println("Specifying Dictionary"); + inflater.setDictionary(dictInf.slice()); + resultLength = inflater.inflate(result); + } else { + System.out.println("Did not need to use a Dictionary"); + } + inflater.finished(); + System.out.printf("Inflater::getAdler:%s, length: %s%n", + inflater.getAdler(), resultLength); + + Assert.assertEquals(SRC_DATA.length(), resultLength); + Assert.assertEquals(input, Arrays.copyOf(result, resultLength)); + } finally { + // Release Resources + deflater.end(); + inflater.end(); + } + } + + /** + * Validate that an invalid offset used with setDictionary will + * throw an Exception + * + * @param dictionary_offset offset value to be used + */ + @Test(dataProvider = "invalidDictionaryOffsets") + public void testInvalidOffsets(int dictionary_offset) { + byte[] dictionary = DICTIONARY.getBytes(UTF_8); + + Deflater deflater = new Deflater(); + Inflater inflater = new Inflater(); + try { + assertThrows(ArrayIndexOutOfBoundsException.class, () -> + deflater.setDictionary(dictionary, dictionary_offset, DICTIONARY_LENGTH)); + assertThrows(ArrayIndexOutOfBoundsException.class, () -> + inflater.setDictionary(dictionary, dictionary_offset, DICTIONARY_LENGTH)); + } finally { + // Release Resources + deflater.end(); + inflater.end(); + } + } +} \ No newline at end of file diff --git a/ojluni/src/test/java/util/zip/ZipFile/TestCleaner.java b/ojluni/src/test/java/util/zip/ZipFile/TestCleaner.java new file mode 100644 index 00000000000..d24aa5045fd --- /dev/null +++ b/ojluni/src/test/java/util/zip/ZipFile/TestCleaner.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @bug 8185582 8197989 + * @modules java.base/java.util.zip:open java.base/jdk.internal.vm.annotation + * @summary Check the resources of Inflater, Deflater and ZipFile are always + * cleaned/released when the instance is not unreachable + */ + +import java.io.*; +import java.lang.reflect.*; +import java.util.*; +import java.util.zip.*; +import jdk.internal.vm.annotation.DontInline; +import static java.nio.charset.StandardCharsets.US_ASCII; + +public class TestCleaner { + + public static void main(String[] args) throws Throwable { + testDeInflater(); + testZipFile(); + } + + private static long addrOf(Object obj) { + try { + Field addr = obj.getClass().getDeclaredField("address"); + if (!addr.trySetAccessible()) { + return -1; + } + return addr.getLong(obj); + } catch (Exception x) { + return -1; + } + } + + // verify the "native resource" of In/Deflater has been cleaned + private static void testDeInflater() throws Throwable { + Field zsRefDef = Deflater.class.getDeclaredField("zsRef"); + Field zsRefInf = Inflater.class.getDeclaredField("zsRef"); + if (!zsRefDef.trySetAccessible() || !zsRefInf.trySetAccessible()) { + throw new RuntimeException("'zsRef' is not accesible"); + } + if (addrOf(zsRefDef.get(new Deflater())) == -1 || + addrOf(zsRefInf.get(new Inflater())) == -1) { + throw new RuntimeException("'addr' is not accesible"); + } + List list = new ArrayList<>(); + byte[] buf1 = new byte[1024]; + byte[] buf2 = new byte[1024]; + for (int i = 0; i < 10; i++) { + var def = new Deflater(); + list.add(zsRefDef.get(def)); + def.setInput("hello".getBytes()); + def.finish(); + int n = def.deflate(buf1); + + var inf = new Inflater(); + list.add(zsRefInf.get(inf)); + inf.setInput(buf1, 0, n); + n = inf.inflate(buf2); + if (!"hello".equals(new String(buf2, 0, n))) { + throw new RuntimeException("compression/decompression failed"); + } + } + + int n = 10; + long cnt = list.size(); + while (n-- > 0 && cnt != 0) { + Thread.sleep(100); + System.gc(); + cnt = list.stream().filter(o -> addrOf(o) != 0).count(); + } + if (cnt != 0) + throw new RuntimeException("cleaner failed to clean : " + cnt); + + } + + @DontInline + private static Object openAndCloseZipFile(File zip) throws Throwable { + try { + try (var fos = new FileOutputStream(zip); + var zos = new ZipOutputStream(fos)) { + zos.putNextEntry(new ZipEntry("hello")); + zos.write("hello".getBytes(US_ASCII)); + zos.closeEntry(); + } + + var zf = new ZipFile(zip); + var es = zf.entries(); + while (es.hasMoreElements()) { + zf.getInputStream(es.nextElement()).read(); + } + + Field fieldRes = ZipFile.class.getDeclaredField("res"); + if (!fieldRes.trySetAccessible()) { + throw new RuntimeException("'ZipFile.res' is not accesible"); + } + Object zfRes = fieldRes.get(zf); + if (zfRes == null) { + throw new RuntimeException("'ZipFile.res' is null"); + } + Field fieldZsrc = zfRes.getClass().getDeclaredField("zsrc"); + if (!fieldZsrc.trySetAccessible()) { + throw new RuntimeException("'ZipFile.zsrc' is not accesible"); + } + return fieldZsrc.get(zfRes); + } finally { + zip.delete(); + } + } + + + private static void testZipFile() throws Throwable { + File dir = new File(System.getProperty("test.dir", ".")); + File zip = File.createTempFile("testzf", "zip", dir); + + Object zsrc = openAndCloseZipFile(zip); + if (zsrc != null) { + Field zfileField = zsrc.getClass().getDeclaredField("zfile"); + if (!zfileField.trySetAccessible()) { + throw new RuntimeException("'ZipFile.Source.zfile' is not accesible"); + } + //System.out.println("zffile: " + zfileField.get(zsrc)); + int n = 10; + while (n-- > 0 && zfileField.get(zsrc) != null) { + System.out.println("waiting gc ... " + n); + System.gc(); + Thread.sleep(100); + } + if (zfileField.get(zsrc) != null) { + throw new RuntimeException("cleaner failed to clean zipfile."); + } + } + } +} -- cgit v1.2.3 From 291cfd124ef97db10b89f3af8ae6f624d883cbac Mon Sep 17 00:00:00 2001 From: Almaz Mingaleev Date: Mon, 4 Mar 2024 14:16:11 +0000 Subject: Add ArbitraryJumpableGenerator tests. These methods have coverage in RandomTestCoverage, but there is no ArbitraryJumpable generator provided by RandomGeneratorFactory. Fix: 326837197 Fix: 326837373 Fix: 326836871 Test: atest CtsLibcoreTestCases:libcore.java.util Change-Id: I9da666a4a3fe82d57731ce296d1162b907daa143 --- .../util/ArbitrarilyJumpableGeneratorTest.java | 121 +++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 luni/src/test/java/libcore/java/util/ArbitrarilyJumpableGeneratorTest.java diff --git a/luni/src/test/java/libcore/java/util/ArbitrarilyJumpableGeneratorTest.java b/luni/src/test/java/libcore/java/util/ArbitrarilyJumpableGeneratorTest.java new file mode 100644 index 00000000000..bdd48e8cbe4 --- /dev/null +++ b/luni/src/test/java/libcore/java/util/ArbitrarilyJumpableGeneratorTest.java @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package libcore.java.util; + +import static org.junit.Assert.assertEquals; + +import static java.util.stream.Collectors.toSet; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import java.util.Set; +import java.util.random.RandomGenerator; +import java.util.stream.Collectors; + +@RunWith(JUnit4.class) +public class ArbitrarilyJumpableGeneratorTest { + + private static final class StubGenerator + implements RandomGenerator.ArbitrarilyJumpableGenerator { + + private double state; + + StubGenerator(double state) { + this.state = state; + } + + @Override + public long nextLong() { + return 0; + } + + @Override + public double jumpDistance() { + return 1.0d; + } + + @Override + public double leapDistance() { + return 2.0d; + } + + @Override + public ArbitrarilyJumpableGenerator copy() { + return new StubGenerator(state); + } + + @Override + public void jumpPowerOfTwo(int logDistance) { + state += Math.pow(2, logDistance); + } + + @Override + public void jump(double distance) { + state += distance; + } + } + + @Test + public void jump_jumpsToJumpDistance() { + double initState = 10.0d; + StubGenerator rng = new StubGenerator(initState); + + rng.jump(); + + assertEquals(initState + rng.jumpDistance(), rng.state, 0.001d); + } + + @Test + public void leap_jumpsToLeapDistance() { + double initState = 10.0d; + StubGenerator rng = new StubGenerator(initState); + + rng.leap(); + + assertEquals(initState + rng.leapDistance(), rng.state, 0.001d); + } + + @Test + public void jump_advancesEachElementInStream() { + double initState = 1d; + StubGenerator rng = new StubGenerator(initState); + + double jumpDistance = 10; + int streamSize = 5; + + var generators = rng.jumps(streamSize, jumpDistance).toList(); + + assertEquals(initState + jumpDistance * streamSize, rng.state, 0.001d); + assertEquals(streamSize, generators.size()); + + Set actualStates = generators.stream() + .map(generator -> (StubGenerator) generator) + .map(stubGenerator -> stubGenerator.state) + .collect(toSet()); + + Set expectedStates = Set.of( + initState, + initState + jumpDistance, + initState + 2 * jumpDistance, + initState + 3 * jumpDistance, + initState + 4 * jumpDistance); + + assertEquals(expectedStates, actualStates); + } +} -- cgit v1.2.3