diff options
Diffstat (limited to 'okhttp-hpacktests')
8 files changed, 495 insertions, 0 deletions
diff --git a/okhttp-hpacktests/README.md b/okhttp-hpacktests/README.md new file mode 100644 index 0000000..6b85c9a --- /dev/null +++ b/okhttp-hpacktests/README.md @@ -0,0 +1,19 @@ +OkHttp HPACK tests +================== + +These tests use the [hpack-test-case][1] project to validate OkHttp's HPACK +implementation. The HPACK test cases are in a separate git submodule, so to +initialize them, you must run: + + git submodule init + git submodule update + +TODO +---- + + * Add maven goal to avoid manual call to git submodule init. + * Make hpack-test-case update itself from git, and run new tests. + * Add maven goal to generate stories and a pull request to hpack-test-case + to have others validate our output. + +[1]: https://github.com/http2jp/hpack-test-case diff --git a/okhttp-hpacktests/pom.xml b/okhttp-hpacktests/pom.xml new file mode 100644 index 0000000..70a59f2 --- /dev/null +++ b/okhttp-hpacktests/pom.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>com.squareup.okhttp</groupId> + <artifactId>parent</artifactId> + <version>2.2.0-SNAPSHOT</version> + </parent> + + <artifactId>okhttp-hpacktests</artifactId> + <name>OkHttp HPACK Tests</name> + + <dependencies> + <dependency> + <groupId>com.squareup.okio</groupId> + <artifactId>okio</artifactId> + </dependency> + <dependency> + <groupId>com.squareup.okhttp</groupId> + <artifactId>okhttp</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.squareup.okhttp</groupId> + <artifactId>mockwebserver</artifactId> + <version>${project.version}</version> + <scope>test</scope> + </dependency> + <!-- Gson: Java to Json conversion --> + <dependency> + <groupId>com.google.code.gson</groupId> + <artifactId>gson</artifactId> + <scope>compile</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <!-- Do not deploy this as an artifact to Maven central. --> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-deploy-plugin</artifactId> + <configuration> + <skip>true</skip> + </configuration> + </plugin> + </plugins> + </build> +</project> diff --git a/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeInteropTest.java b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeInteropTest.java new file mode 100644 index 0000000..30e1a7b --- /dev/null +++ b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeInteropTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2014 Square, Inc. + * + * 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 com.squareup.okhttp.internal.spdy; + +import com.squareup.okhttp.internal.spdy.hpackjson.Story; +import java.util.Collection; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import static com.squareup.okhttp.internal.spdy.hpackjson.HpackJsonUtil.storiesForCurrentDraft; + +@RunWith(Parameterized.class) +public class HpackDecodeInteropTest extends HpackDecodeTestBase { + + public HpackDecodeInteropTest(Story story) { + super(story); + } + + @Parameterized.Parameters(name="{0}") + public static Collection<Story[]> createStories() throws Exception { + return createStories(storiesForCurrentDraft()); + } + + @Test + public void testGoodDecoderInterop() throws Exception { + testDecoder(); + } +} diff --git a/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeTestBase.java b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeTestBase.java new file mode 100644 index 0000000..1bd9b00 --- /dev/null +++ b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackDecodeTestBase.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2014 Square, Inc. + * + * 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 com.squareup.okhttp.internal.spdy; + +import com.squareup.okhttp.internal.spdy.hpackjson.Case; +import com.squareup.okhttp.internal.spdy.hpackjson.HpackJsonUtil; +import com.squareup.okhttp.internal.spdy.hpackjson.Story; +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.List; +import okio.Buffer; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * Tests Hpack implementation using https://github.com/http2jp/hpack-test-case/ + */ +public class HpackDecodeTestBase { + + /** + * Reads all stories in the folders provided, asserts if no story found. + */ + protected static Collection<Story[]> createStories(String[] interopTests) + throws Exception { + List<Story[]> result = new ArrayList<>(); + for (String interopTestName : interopTests) { + List<Story> stories = HpackJsonUtil.readStories(interopTestName); + if (stories.isEmpty()) { + fail("No stories for: " + interopTestName); + } + for (Story story : stories) { + result.add(new Story[] { story }); + } + } + return result; + } + + private final Buffer bytesIn = new Buffer(); + private final HpackDraft10.Reader hpackReader = new HpackDraft10.Reader(4096, bytesIn); + + private final Story story; + + public HpackDecodeTestBase(Story story) { + this.story = story; + } + + /** + * Expects wire to be set for all cases, and compares the decoder's output to + * expected headers. + */ + protected void testDecoder() throws Exception { + testDecoder(story); + } + + protected void testDecoder(Story story) throws Exception { + for (Case caze : story.getCases()) { + bytesIn.write(caze.getWire()); + hpackReader.readHeaders(); + assertSetEquals(String.format("seqno=%d", caze.getSeqno()), caze.getHeaders(), + hpackReader.getAndResetHeaderList()); + } + } + /** + * Checks if {@code expected} and {@code observed} are equal when viewed as a + * set and headers are deduped. + * + * TODO: See if duped headers should be preserved on decode and verify. + */ + private static void assertSetEquals( + String message, List<Header> expected, List<Header> observed) { + assertEquals(message, new LinkedHashSet<>(expected), new LinkedHashSet<>(observed)); + } + + protected Story getStory() { + return story; + } +} diff --git a/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackRoundTripTest.java b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackRoundTripTest.java new file mode 100644 index 0000000..a78dab5 --- /dev/null +++ b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/HpackRoundTripTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2014 Square, Inc. + * + * 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 com.squareup.okhttp.internal.spdy; + +import com.squareup.okhttp.internal.spdy.hpackjson.Case; +import com.squareup.okhttp.internal.spdy.hpackjson.Story; +import okio.Buffer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Collection; + +/** + * Tests for round-tripping headers through hpack.. + */ +// TODO: update hpack-test-case with the output of our encoder. +// This test will hide complementary bugs in the encoder and decoder, +// We should test that the encoder is producing responses that are +// d] +@RunWith(Parameterized.class) +public class HpackRoundTripTest extends HpackDecodeTestBase { + + private static final String[] RAW_DATA = { "raw-data" }; + + @Parameterized.Parameters(name="{0}") + public static Collection<Story[]> getStories() throws Exception { + return createStories(RAW_DATA); + } + + private Buffer bytesOut = new Buffer(); + private HpackDraft10.Writer hpackWriter = new HpackDraft10.Writer(bytesOut); + + public HpackRoundTripTest(Story story) { + super(story); + } + + @Test + public void testRoundTrip() throws Exception { + Story story = getStory().clone(); + // Mutate cases in base class. + for (Case caze : story.getCases()) { + hpackWriter.writeHeaders(caze.getHeaders()); + caze.setWire(bytesOut.readByteString()); + } + + testDecoder(story); + } + +} diff --git a/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/hpackjson/Case.java b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/hpackjson/Case.java new file mode 100644 index 0000000..d5d2728 --- /dev/null +++ b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/hpackjson/Case.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2014 Square, Inc. + * + * 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 com.squareup.okhttp.internal.spdy.hpackjson; + +import com.squareup.okhttp.internal.spdy.Header; +import okio.ByteString; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Representation of an individual case (set of headers and wire format). + * There are many cases for a single story. This class is used reflectively + * with Gson to parse stories. + */ +public class Case implements Cloneable { + + private int seqno; + private String wire; + private List<Map<String, String>> headers; + + public List<Header> getHeaders() { + List<Header> result = new ArrayList<>(); + for (Map<String, String> inputHeader : headers) { + Map.Entry<String, String> entry = inputHeader.entrySet().iterator().next(); + result.add(new Header(entry.getKey(), entry.getValue())); + } + return result; + } + + public ByteString getWire() { + return ByteString.decodeHex(wire); + } + + public int getSeqno() { + return seqno; + } + + public void setWire(ByteString wire) { + this.wire = wire.hex(); + } + + @Override + protected Case clone() throws CloneNotSupportedException { + Case result = new Case(); + result.seqno = seqno; + result.wire = wire; + result.headers = new ArrayList<>(); + for (Map<String, String> header : headers) { + result.headers.add(new LinkedHashMap<String, String>(header)); + } + return result; + } +} diff --git a/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/hpackjson/HpackJsonUtil.java b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/hpackjson/HpackJsonUtil.java new file mode 100644 index 0000000..9d721ab --- /dev/null +++ b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/hpackjson/HpackJsonUtil.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2014 Square, Inc. + * + * 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 com.squareup.okhttp.internal.spdy.hpackjson; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Utilities for reading HPACK tests. + */ +public final class HpackJsonUtil { + private static final int CURRENT_DRAFT = 9; + + private static final String STORY_RESOURCE_FORMAT = "/hpack-test-case/%s/story_%02d.json"; + + private static final Gson GSON = new GsonBuilder().create(); + + private static Story readStory(InputStream jsonResource) throws IOException { + return GSON.fromJson(new InputStreamReader(jsonResource, "UTF-8"), Story.class); + } + + /** Iterate through the hpack-test-case resources, only picking stories for the current draft. */ + public static String[] storiesForCurrentDraft() throws URISyntaxException { + File testCaseDirectory = new File(HpackJsonUtil.class.getResource("/hpack-test-case").toURI()); + List<String> storyNames = new ArrayList<String>(); + for (File path : testCaseDirectory.listFiles()) { + if (path.isDirectory() && Arrays.asList(path.list()).contains("story_00.json")) { + try { + Story firstStory = readStory(new FileInputStream(new File(path, "story_00.json"))); + if (firstStory.getDraft() == CURRENT_DRAFT) { + storyNames.add(path.getName()); + } + } catch (IOException ignored) { + // Skip this path. + } + } + } + return storyNames.toArray(new String[storyNames.size()]); + } + + /** + * Reads stories named "story_xx.json" from the folder provided. + */ + public static List<Story> readStories(String testFolderName) throws Exception { + List<Story> result = new ArrayList<>(); + int i = 0; + while (true) { // break after last test. + String storyResourceName = String.format(STORY_RESOURCE_FORMAT, testFolderName, i); + InputStream storyInputStream = HpackJsonUtil.class.getResourceAsStream(storyResourceName); + if (storyInputStream == null) { + break; + } + try { + Story story = readStory(storyInputStream); + story.setFileName(storyResourceName); + result.add(story); + i++; + } finally { + storyInputStream.close(); + } + } + return result; + } + + private HpackJsonUtil() { } // Utilities only. +}
\ No newline at end of file diff --git a/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/hpackjson/Story.java b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/hpackjson/Story.java new file mode 100644 index 0000000..5ff2b07 --- /dev/null +++ b/okhttp-hpacktests/src/test/java/com/squareup/okhttp/internal/spdy/hpackjson/Story.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2014 Square, Inc. + * + * 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 com.squareup.okhttp.internal.spdy.hpackjson; + +import java.util.ArrayList; +import java.util.List; + +/** + * Representation of one story, a set of request headers to encode or decode. + * This class is used reflectively with Gson to parse stories from files. + */ +public class Story implements Cloneable { + + private transient String fileName; + private List<Case> cases; + private int draft; + private String description; + + /** + * The filename is only used in the toString representation. + */ + void setFileName(String fileName) { + this.fileName = fileName; + } + + public List<Case> getCases() { + return cases; + } + + /** We only expect stories that match the draft we've implemented to pass. */ + public int getDraft() { + return draft; + } + + @Override + public Story clone() throws CloneNotSupportedException { + Story story = new Story(); + story.fileName = this.fileName; + story.cases = new ArrayList<>(); + for (Case caze : cases) { + story.cases.add(caze.clone()); + } + story.draft = draft; + story.description = description; + return story; + } + + @Override + public String toString() { + // Used as the test name. + return fileName; + } +} |