--- /dev/null 2020-02-10 14:32:17.000000000 -0500 +++ new/test/jdk/java/util/jar/Manifest/Println72.java 2020-02-10 14:32:16.000000000 -0500 @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2019, 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 static java.nio.charset.StandardCharsets.UTF_8; + +import java.io.ByteArrayOutputStream; +import java.util.List; +import java.util.ArrayList; +import java.util.jar.Manifest; +import java.util.jar.Attributes.Name; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import org.testng.annotations.Test; +import static org.testng.Assert.*; + +/** + * @test + * @compile ../../../../sun/security/tools/jarsigner/Utils.java + * @bug 6443578 6202130 + * @run testng Println72 + * @summary Tests {@link Manifest#println72} line breaking with some particular + * border case kind of test cases. + * For another test covering the complete Unicode character set see + * {@link ValueUtf8Coding}. + */ +public class Println72 { + + static final Name TEST_NAME = new Name("test"); + + static final int NAME_SEP_LENGTH = (TEST_NAME + ": ").length(); + + void test(String originalValue, int... breakPositionsBytes) throws Exception + { + String expectedValueWithBreaksInManifest = originalValue; + // iterating backwards because inserting breaks affects original + // positions + for (int i = breakPositionsBytes.length - 1; i >= 0; i--) { + int breakPositionBytes = breakPositionsBytes[i]; + + int bytesSoFar = 0; + int charsSoFar = 0; + while (bytesSoFar < breakPositionBytes) { + String s = expectedValueWithBreaksInManifest + .substring(charsSoFar, charsSoFar + 1); + charsSoFar++; + bytesSoFar += s.getBytes(UTF_8).length; + if (bytesSoFar > breakPositionBytes) { + fail("break position not aligned with characters"); + } + } + int breakPositionCharacters = charsSoFar; + + expectedValueWithBreaksInManifest = + expectedValueWithBreaksInManifest + .substring(0, breakPositionCharacters) + + "\r\n " + + expectedValueWithBreaksInManifest + .substring(breakPositionCharacters); + } + + Manifest mf = new Manifest(); + mf.getMainAttributes().put(Name.MANIFEST_VERSION, "1.0"); + mf.getMainAttributes().put(TEST_NAME, originalValue); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + mf.write(out); + byte[] mfBytes = out.toByteArray(); + + Utils.echoManifest(mfBytes, "Println72"); + byte[] actual = mfBytes; + String expected = + "Manifest-Version: 1.0\r\n" + + TEST_NAME + ": " + expectedValueWithBreaksInManifest + + "\r\n\r\n"; + try { + assertEquals(new String(actual, UTF_8), expected); + assertEquals(actual, expected.getBytes(UTF_8)); + } catch (AssertionError e) { + System.out.println("actual = " + byteArrayToIntList(actual)); + System.out.println("expected = " + byteArrayToIntList( + expected.getBytes(UTF_8))); + throw e; + } + } + + static List byteArrayToIntList(byte[] bytes) { + List list = new ArrayList<>(); + for (int i = 0; i < bytes.length; i++) { + list.add((int) bytes[i]); + } + return list; + } + + @Test + public void testEmpty() throws Exception { + test(""); // expect neither a line break nor an exception + } + + static final String COMBINING_DIACRITICAL_MARKS = + IntStream.range(0x300, 0x36F) + .mapToObj(i -> new String(Character.toChars(i))) + .collect(Collectors.joining()); + + static String getCharSeq(int numberOfBytes) { + String seq = (numberOfBytes % 2 == 1 ? "e" : "\u00E6") + + COMBINING_DIACRITICAL_MARKS.substring(0, (numberOfBytes - 1) / 2); + assertEquals(seq.getBytes(UTF_8).length, numberOfBytes); + return seq; + } + + @Test + public void testBreakOnFirstLine() throws Exception { + // Combining sequence starts immediately after name and ": " and fits + // the remaining space in the first line. Expect no break. + test(getCharSeq(66)); + + // Combining sequence starts after name and ": " and exceeds the + // remaining space in the first line by one byte. Expect to break on a + // new line because the combining sequence fits on a continuation line + // which does not start with name and ": " and provides enough space. + test(getCharSeq(67), 0); + + // Combining sequence starts after name and ": " and exceeds the + // remaining space in the first line but still just fits exactly on a + // continuation line. Expect the value to break onto a new line. + test(getCharSeq(71), 0); + + // Combining sequence starts after name and ": " and exceeds the + // remaining space in the first line and neither fits on a continuation + // line. Expect that the first line to be filled with as many codepoints + // as fit on it and expect a line break onto a continuation line after + // 66 bytes of the first line value. + test(getCharSeq(72), 72 - NAME_SEP_LENGTH); + + // Combining sequence starts after name and ": x" and exceeds the + // remaining space in the first line and neither fits on a continuation + // line. Expect that the first line to be filled with as many codepoints + // as fit on it and expect a line break onto a continuation line already + // after 65 bytes of the first line because the following character is + // a code point represented with two bytes in UTF-8 which should not + // be interrupted with a line break. + test("x" + getCharSeq(72), 72 - NAME_SEP_LENGTH - 1); + } + + @Test + public void testBreakOnContinuationLine() throws Exception { + // fits on next line by skipping one byte free on current line + test("x".repeat(72 - NAME_SEP_LENGTH + 71 - 1) + getCharSeq(71), + 72 - NAME_SEP_LENGTH, + 72 - NAME_SEP_LENGTH + 71 - 1); + + // fits on current line exactly + test("x".repeat(72 - NAME_SEP_LENGTH + 71) + getCharSeq(71), + 72 - NAME_SEP_LENGTH, + 72 - NAME_SEP_LENGTH + 71); + + // fits on next line by inserting a line break after a line that + // contains only one character yet + test("x".repeat(72 - NAME_SEP_LENGTH + 71 + 1) + getCharSeq(71), + 72 - NAME_SEP_LENGTH, + 72 - NAME_SEP_LENGTH + 71, + 72 - NAME_SEP_LENGTH + 71 + 1); + + // does not fit on the next line and the one byte not yet used on the + // current line does not hold the first code point of the combined + // character sequence which is a code point encoded with two bytes in + // UTF-8. + test("x".repeat(72 - NAME_SEP_LENGTH + 71 - 1) + getCharSeq(72), + 72 - NAME_SEP_LENGTH, + 72 - NAME_SEP_LENGTH + 71 - 1, + 72 - NAME_SEP_LENGTH + 71 - 1 + 71 - 1); + + // would not fit on the next line alone but fits on the remaining two + // bytes available on the current line and the whole subsequent line. + test("x".repeat(72 - NAME_SEP_LENGTH + 71 - 2) + getCharSeq(72), + 72 - NAME_SEP_LENGTH, + 72 - NAME_SEP_LENGTH + 71); + + // previous character filled the whole previous line completely + // but the combined character sequence with 72 bytes still does not fit + // on a single line. the last code point is a two byte one so that an + // unused byte is left unused on the second last line. + test("x".repeat(72 - NAME_SEP_LENGTH + 71) + getCharSeq(72), + 72 - NAME_SEP_LENGTH, + 72 - NAME_SEP_LENGTH + 71, + 72 - NAME_SEP_LENGTH + 71 + 71 - 1); + + // previous character left one byte used on the current line and the + // remaining 70 bytes available. the combining sequence can use all of + // these 70 bytes because after 70 bytes a new code point starts + test("x".repeat(72 - NAME_SEP_LENGTH + 71 + 1) + getCharSeq(72), + 72 - NAME_SEP_LENGTH, + 72 - NAME_SEP_LENGTH + 71, + 72 - NAME_SEP_LENGTH + 71 + 71); + } + +}