test/jdk/java/net/httpclient/http2/java.net.http/jdk/internal/net/http/hpack/BinaryPrimitivesTest.java
author chegar
Wed, 07 Feb 2018 21:45:37 +0000
branchhttp-client-branch
changeset 56092 fd85b2bf2b0d
parent 56089 test/jdk/java/net/httpclient/http2/java.net.http/java/net/http/internal/hpack/BinaryPrimitivesTest.java@42208b2f224e
child 56369 24a8fafec3ff
permissions -rw-r--r--
http-client-branch: move implementation to jdk.internal.net.http

/*
 * Copyright (c) 2014, 2017, 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.
 */
package jdk.internal.net.http.hpack;

import org.testng.annotations.Test;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.fail;
import static jdk.internal.net.http.hpack.BuffersTestingKit.*;
import static jdk.internal.net.http.hpack.TestHelper.newRandom;

//
// Some of the tests below overlap in what they test. This allows to diagnose
// bugs quicker and with less pain by simply ruling out common working bits.
//
public final class BinaryPrimitivesTest {

    private final Random rnd = newRandom();

    @Test
    public void integerRead1() {
        verifyRead(bytes(0b00011111, 0b10011010, 0b00001010), 1337, 5);
    }

    @Test
    public void integerRead2() {
        verifyRead(bytes(0b00001010), 10, 5);
    }

    @Test
    public void integerRead3() {
        verifyRead(bytes(0b00101010), 42, 8);
    }

    @Test
    public void integerWrite1() {
        verifyWrite(bytes(0b00011111, 0b10011010, 0b00001010), 1337, 5);
    }

    @Test
    public void integerWrite2() {
        verifyWrite(bytes(0b00001010), 10, 5);
    }

    @Test
    public void integerWrite3() {
        verifyWrite(bytes(0b00101010), 42, 8);
    }

    //
    // Since readInteger(x) is the inverse of writeInteger(x), thus:
    //
    // for all x: readInteger(writeInteger(x)) == x
    //
    @Test
    public void integerIdentity() throws IOException {
        final int MAX_VALUE = 1 << 22;
        int totalCases = 0;
        int maxFilling = 0;
        IntegerReader r = new IntegerReader();
        IntegerWriter w = new IntegerWriter();
        ByteBuffer buf = ByteBuffer.allocate(8);
        for (int N = 1; N < 9; N++) {
            for (int expected = 0; expected <= MAX_VALUE; expected++) {
                w.reset().configure(expected, N, 1).write(buf);
                buf.flip();
                totalCases++;
                maxFilling = Math.max(maxFilling, buf.remaining());
                r.reset().configure(N).read(buf);
                assertEquals(r.get(), expected);
                buf.clear();
            }
        }
        System.out.printf("totalCases: %,d, maxFilling: %,d, maxValue: %,d%n",
                totalCases, maxFilling, MAX_VALUE);
    }

    @Test
    public void integerReadChunked() {
        final int NUM_TESTS = 1024;
        IntegerReader r = new IntegerReader();
        ByteBuffer bb = ByteBuffer.allocate(8);
        IntegerWriter w = new IntegerWriter();
        for (int i = 0; i < NUM_TESTS; i++) {
            final int N = 1 + rnd.nextInt(8);
            final int expected = rnd.nextInt(Integer.MAX_VALUE) + 1;
            w.reset().configure(expected, N, rnd.nextInt()).write(bb);
            bb.flip();

            forEachSplit(bb,
                    (buffers) -> {
                        Iterable<? extends ByteBuffer> buf = relocateBuffers(injectEmptyBuffers(buffers));
                        r.configure(N);
                        for (ByteBuffer b : buf) {
                            try {
                                r.read(b);
                            } catch (IOException e) {
                                throw new UncheckedIOException(e);
                            }
                        }
                        assertEquals(r.get(), expected);
                        r.reset();
                    });
            bb.clear();
        }
    }

    // FIXME: use maxValue in the test

    @Test
    // FIXME: tune values for better coverage
    public void integerWriteChunked() {
        ByteBuffer bb = ByteBuffer.allocate(6);
        IntegerWriter w = new IntegerWriter();
        IntegerReader r = new IntegerReader();
        for (int i = 0; i < 1024; i++) { // number of tests
            final int N = 1 + rnd.nextInt(8);
            final int payload = rnd.nextInt(255);
            final int expected = rnd.nextInt(Integer.MAX_VALUE) + 1;

            forEachSplit(bb,
                    (buffers) -> {
                        List<ByteBuffer> buf = new ArrayList<>();
                        relocateBuffers(injectEmptyBuffers(buffers)).forEach(buf::add);
                        boolean written = false;
                        w.configure(expected, N, payload); // TODO: test for payload it can be read after written
                        for (ByteBuffer b : buf) {
                            int pos = b.position();
                            written = w.write(b);
                            b.position(pos);
                        }
                        if (!written) {
                            fail("please increase bb size");
                        }
                        try {
                            r.configure(N).read(concat(buf));
                        } catch (IOException e) {
                            throw new UncheckedIOException(e);
                        }
                        // TODO: check payload here
                        assertEquals(r.get(), expected);
                        w.reset();
                        r.reset();
                        bb.clear();
                    });
        }
    }


    //
    // Since readString(x) is the inverse of writeString(x), thus:
    //
    // for all x: readString(writeString(x)) == x
    //
    @Test
    public void stringIdentity() throws IOException {
        final int MAX_STRING_LENGTH = 4096;
        ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6); // it takes 6 bytes to encode string length of Integer.MAX_VALUE
        CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
        StringReader reader = new StringReader();
        StringWriter writer = new StringWriter();
        for (int len = 0; len <= MAX_STRING_LENGTH; len++) {
            for (int i = 0; i < 64; i++) {
                // not so much "test in isolation", I know... we're testing .reset() as well
                bytes.clear();
                chars.clear();

                byte[] b = new byte[len];
                rnd.nextBytes(b);

                String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string

                boolean written = writer
                        .configure(CharBuffer.wrap(expected), 0, expected.length(), false)
                        .write(bytes);

                if (!written) {
                    fail("please increase 'bytes' size");
                }
                bytes.flip();
                reader.read(bytes, chars);
                chars.flip();
                assertEquals(chars.toString(), expected);
                reader.reset();
                writer.reset();
            }
        }
    }

//    @Test
//    public void huffmanStringWriteChunked() {
//        fail();
//    }
//
//    @Test
//    public void huffmanStringReadChunked() {
//        fail();
//    }

    @Test
    public void stringWriteChunked() {
        final int MAX_STRING_LENGTH = 8;
        final ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6);
        final CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
        final StringReader reader = new StringReader();
        final StringWriter writer = new StringWriter();
        for (int len = 0; len <= MAX_STRING_LENGTH; len++) {

            byte[] b = new byte[len];
            rnd.nextBytes(b);

            String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string

            forEachSplit(bytes, (buffers) -> {
                writer.configure(expected, 0, expected.length(), false);
                boolean written = false;
                for (ByteBuffer buf : buffers) {
                    int p0 = buf.position();
                    written = writer.write(buf);
                    buf.position(p0);
                }
                if (!written) {
                    fail("please increase 'bytes' size");
                }
                try {
                    reader.read(concat(buffers), chars);
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
                chars.flip();
                assertEquals(chars.toString(), expected);
                reader.reset();
                writer.reset();
                chars.clear();
                bytes.clear();
            });
        }
    }

    @Test
    public void stringReadChunked() {
        final int MAX_STRING_LENGTH = 16;
        final ByteBuffer bytes = ByteBuffer.allocate(MAX_STRING_LENGTH + 6);
        final CharBuffer chars = CharBuffer.allocate(MAX_STRING_LENGTH);
        final StringReader reader = new StringReader();
        final StringWriter writer = new StringWriter();
        for (int len = 0; len <= MAX_STRING_LENGTH; len++) {

            byte[] b = new byte[len];
            rnd.nextBytes(b);

            String expected = new String(b, StandardCharsets.ISO_8859_1); // reference string

            boolean written = writer
                    .configure(CharBuffer.wrap(expected), 0, expected.length(), false)
                    .write(bytes);
            writer.reset();

            if (!written) {
                fail("please increase 'bytes' size");
            }
            bytes.flip();

            forEachSplit(bytes, (buffers) -> {
                for (ByteBuffer buf : buffers) {
                    int p0 = buf.position();
                    try {
                        reader.read(buf, chars);
                    } catch (IOException e) {
                        throw new UncheckedIOException(e);
                    }
                    buf.position(p0);
                }
                chars.flip();
                assertEquals(chars.toString(), expected);
                reader.reset();
                chars.clear();
            });

            bytes.clear();
        }
    }

//    @Test
//    public void test_Huffman_String_Identity() {
//        StringWriter writer = new StringWriter();
//        StringReader reader = new StringReader();
//        // 256 * 8 gives 2048 bits in case of plain 8 bit coding
//        // 256 * 30 gives you 7680 bits or 960 bytes in case of almost
//        //          improbable event of 256 30 bits symbols in a row
//        ByteBuffer binary = ByteBuffer.allocate(960);
//        CharBuffer text = CharBuffer.allocate(960 / 5); // 5 = minimum code length
//        for (int len = 0; len < 128; len++) {
//            for (int i = 0; i < 256; i++) {
//                // not so much "test in isolation", I know...
//                binary.clear();
//
//                byte[] bytes = new byte[len];
//                rnd.nextBytes(bytes);
//
//                String s = new String(bytes, StandardCharsets.ISO_8859_1);
//
//                writer.write(CharBuffer.wrap(s), binary, true);
//                binary.flip();
//                reader.read(binary, text);
//                text.flip();
//                assertEquals(text.toString(), s);
//            }
//        }
//    }

    // TODO: atomic failures: e.g. readonly/overflow

    private static byte[] bytes(int... data) {
        byte[] bytes = new byte[data.length];
        for (int i = 0; i < data.length; i++) {
            bytes[i] = (byte) data[i];
        }
        return bytes;
    }

    private static void verifyRead(byte[] data, int expected, int N) {
        ByteBuffer buf = ByteBuffer.wrap(data, 0, data.length);
        IntegerReader reader = new IntegerReader();
        try {
            reader.configure(N).read(buf);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
        assertEquals(expected, reader.get());
    }

    private void verifyWrite(byte[] expected, int data, int N) {
        IntegerWriter w = new IntegerWriter();
        ByteBuffer buf = ByteBuffer.allocate(2 * expected.length);
        w.configure(data, N, 1).write(buf);
        buf.flip();
        assertEquals(ByteBuffer.wrap(expected), buf);
    }
}