diff options
Diffstat (limited to 'src')
4 files changed, 219 insertions, 62 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/AbstractObjectCodecTest.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/AbstractObjectCodecTest.java index 117f4d8bac..08f675c5dd 100644 --- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/AbstractObjectCodecTest.java +++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/AbstractObjectCodecTest.java @@ -15,30 +15,25 @@ package com.google.devtools.build.lib.skyframe.serialization.testutils; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.fail; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableList; import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec; import com.google.devtools.build.lib.skyframe.serialization.SerializationException; -import com.google.protobuf.CodedInputStream; import java.io.IOException; -import java.nio.charset.StandardCharsets; import javax.annotation.Nullable; import org.junit.Before; import org.junit.Test; -/** Common ObjectCodec tests. */ +/** + * Base class for {@link ObjectCodec} tests. This is a slim wrapper around {@link ObjectCodecTester} + * and exists mostly to support existing tests. + */ public abstract class AbstractObjectCodecTest<T> { @Nullable protected ObjectCodec<T> underTest; @Nullable protected ImmutableList<T> subjects; - - /** - * Override to false to skip testDeserializeBadDataThrowsSerializationException(). Codecs that - * cannot distinguish good and bad data should do this. - */ - protected boolean shouldTestDeserializeBadData = true; + private ObjectCodecTester<T> objectCodecTester; /** Construct with the given codec and subjects. */ protected AbstractObjectCodecTest( @@ -57,41 +52,29 @@ public abstract class AbstractObjectCodecTest<T> { protected AbstractObjectCodecTest() {} @Before - public void checkInitialized() { + public void initialize() { Preconditions.checkNotNull(underTest); Preconditions.checkNotNull(subjects); + objectCodecTester = ObjectCodecTester.newBuilder(underTest) + .verificationFunction( + (original, deserialized) -> this.verifyDeserialization(deserialized, original)) + .addSubjects(subjects) + .build(); } @Test public void testSuccessfulSerializationDeserialization() throws Exception { - for (T subject : subjects) { - byte[] serialized = toBytes(subject); - Object deserialized = fromBytes(serialized); - verifyDeserialization(deserialized, subject); - } + objectCodecTester.testSerializeDeserialize(); } @Test public void testSerializationRoundTripBytes() throws Exception { - for (T subject : subjects) { - byte[] serialized = toBytes(subject); - T deserialized = fromBytes(serialized); - byte[] reserialized = toBytes(deserialized); - assertThat(reserialized).isEqualTo(serialized); - } + objectCodecTester.testStableSerialization(); } @Test public void testDeserializeBadDataThrowsSerializationException() { - if (!shouldTestDeserializeBadData) { - return; - } - try { - underTest.deserialize(CodedInputStream.newInstance("junk".getBytes(StandardCharsets.UTF_8))); - fail("Expected exception"); - } catch (SerializationException | IOException e) { - // Expected. - } + objectCodecTester.testDeserializeJunkData(); } protected T fromBytes(byte[] bytes) throws SerializationException, IOException { diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/ObjectCodecTester.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/ObjectCodecTester.java new file mode 100644 index 0000000000..1f370f68de --- /dev/null +++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/ObjectCodecTester.java @@ -0,0 +1,172 @@ +// Copyright 2017 The Bazel Authors. All rights reserved. +// +// 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.google.devtools.build.lib.skyframe.serialization.testutils; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec; +import com.google.devtools.build.lib.skyframe.serialization.SerializationException; +import com.google.protobuf.CodedInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +/** Utility for testing {@link ObjectCodec} instances. */ +public class ObjectCodecTester<T> { + + /** Interface for testing successful deserialization of an object. */ + @FunctionalInterface + public interface VerificationFunction<T> { + /** + * Verify whether or not the original object was sufficiently serialized/deserialized. Typically + * this will be some sort of assertion. + * + * @throws Exception on verification failure + */ + void verifyDeserialized(T original, Object deserialized) throws Exception; + } + + /** + * Create an {@link ObjectCodecTester.Builder} for the supplied instance. See + * {@link ObjectCodecTester.Builder} for details. + */ + public static <T> ObjectCodecTester.Builder<T> newBuilder(ObjectCodec<T> toTest) { + return new ObjectCodecTester.Builder<>(toTest); + } + + private final ObjectCodec<T> underTest; + private final ImmutableList<T> subjects; + private final boolean skipBadDataTest; + private final VerificationFunction<T> verificationFunction; + + private ObjectCodecTester( + ObjectCodec<T> underTest, + ImmutableList<T> subjects, + boolean skipBadDataTest, + VerificationFunction<T> verificationFunction) { + this.underTest = underTest; + Preconditions.checkState(!subjects.isEmpty(), "No subjects provided"); + this.subjects = subjects; + this.skipBadDataTest = skipBadDataTest; + this.verificationFunction = verificationFunction; + } + + private void runTests() throws Exception { + testSerializeDeserialize(); + testStableSerialization(); + if (!skipBadDataTest) { + testDeserializeJunkData(); + } + } + + /** Runs serialization/deserialization tests. */ + void testSerializeDeserialize() throws Exception { + for (T subject : subjects) { + byte[] serialized = toBytes(subject); + Object deserialized = fromBytes(serialized); + verificationFunction.verifyDeserialized(subject, deserialized); + } + } + + /** Runs serialized bytes stability tests. */ + void testStableSerialization() throws Exception { + for (T subject : subjects) { + byte[] serialized = toBytes(subject); + T deserialized = fromBytes(serialized); + byte[] reserialized = toBytes(deserialized); + assertThat(reserialized).isEqualTo(serialized); + } + } + + /** Runs junk-data recognition tests. */ + void testDeserializeJunkData() { + try { + underTest.deserialize(CodedInputStream.newInstance("junk".getBytes(StandardCharsets.UTF_8))); + fail("Expected exception"); + } catch (SerializationException | IOException e) { + // Expected. + } + } + + private T fromBytes(byte[] bytes) throws SerializationException, IOException { + return TestUtils.fromBytes(underTest, bytes); + } + + private byte[] toBytes(T subject) throws IOException, SerializationException { + return TestUtils.toBytes(underTest, subject); + } + + /** Builder for {@link ObjectCodecTester}. */ + public static class Builder<T> { + private final ObjectCodec<T> underTest; + private final ImmutableList.Builder<T> subjectsBuilder = ImmutableList.builder(); + private boolean skipBadDataTest = false; + private VerificationFunction<T> verificationFunction = + (original, deserialized) -> assertThat(deserialized).isEqualTo(original); + + private Builder(ObjectCodec<T> underTest) { + this.underTest = underTest; + } + + /** Add subjects to be tested for serialization/deserialization. */ + public Builder<T> addSubjects(@SuppressWarnings("unchecked") T... subjects) { + return addSubjects(ImmutableList.copyOf(subjects)); + } + + /** Add subjects to be tested for serialization/deserialization. */ + Builder<T> addSubjects(ImmutableList<T> subjects) { + subjectsBuilder.addAll(subjects); + return this; + } + + /** + * Skip tests that check for the ability to detect bad data. This may be useful for simpler + * codecs which don't do any error verification. + */ + public Builder<T> skipBadDataTest() { + this.skipBadDataTest = true; + return this; + } + + /** + * Sets {@link ObjectCodecTester.VerificationFunction} for verifying deserialization. Default + * is simple equality assertion, a custom version may be provided for more, or less, detailed + * checks. + */ + public Builder<T> verificationFunction(VerificationFunction<T> verificationFunction) { + this.verificationFunction = Preconditions.checkNotNull(verificationFunction); + return this; + } + + /** Captures the state of this builder and run all associated tests. */ + public void buildAndRunTests() throws Exception { + build().runTests(); + } + + /** + * Creates a new {@link ObjectCodecTester} from this builder. Exposed to allow running tests + * individually. + */ + ObjectCodecTester<T> build() { + return new ObjectCodecTester<>( + underTest, + subjectsBuilder.build(), + skipBadDataTest, + verificationFunction); + } + } +} diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/strings/FastStringCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/strings/FastStringCodecTest.java index 30b9a1ded2..c0ae7d02ee 100644 --- a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/strings/FastStringCodecTest.java +++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/strings/FastStringCodecTest.java @@ -15,41 +15,39 @@ package com.google.devtools.build.lib.skyframe.serialization.strings; import com.google.common.testing.EqualsTester; -import com.google.devtools.build.lib.skyframe.serialization.testutils.AbstractObjectCodecTest; +import com.google.devtools.build.lib.skyframe.serialization.testutils.ObjectCodecTester; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Tests for {@link FastStringCodec}. */ @RunWith(JUnit4.class) -public class FastStringCodecTest extends AbstractObjectCodecTest<String> { +public class FastStringCodecTest { - public FastStringCodecTest() { - super( - // TODO(michajlo): Don't bother running this test if FastStringCodec isn't available. - FastStringCodec.isAvailable() ? new FastStringCodec() : new StringCodec(), - "ow now brown cow. ow now brown cow", - "(╯°□°)╯︵┻━┻ string with utf8/ascii", - "string with ascii/utf8 (╯°□°)╯︵┻━┻", - "last character utf8 ╯", - "last char only non-ascii ƒ", - "ƒ", - "╯", - "", - Character.toString((char) 0xc3));; - } - - // hashCode is stored in String. Because we're using Unsafe to bypass standard String - // constructors, make sure it still works. @Test - public void testEqualsAndHashCodePreserved() throws Exception { - String original1 = "hello world"; - String original2 = "dlrow olleh"; + public void testCodec() throws Exception { + if (!FastStringCodec.isAvailable()) { + // Not available on this platform, skip test. + return; + } - // Equals tester tests equals and hash code. - new EqualsTester() - .addEqualityGroup(original1, fromBytes(toBytes(original1))) - .addEqualityGroup(original2, fromBytes(toBytes(original2))) - .testEquals(); + ObjectCodecTester.newBuilder(new FastStringCodec()) + .verificationFunction( + (original, deserialized) -> { + // hashCode is stored in String. Because we're using Unsafe to bypass standard String + // constructors, make sure it still works. + new EqualsTester().addEqualityGroup(original, deserialized).testEquals(); + }) + .addSubjects( + "ow now brown cow. ow now brown cow", + "(╯°□°)╯︵┻━┻ string with utf8/ascii", + "string with ascii/utf8 (╯°□°)╯︵┻━┻", + "last character utf8 ╯", + "last char only non-ascii ƒ", + "ƒ", + "╯", + "", + Character.toString((char) 0xc3)) + .buildAndRunTests(); } } diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/strings/StringCodecTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/strings/StringCodecTest.java index c1b6f5dbe3..8e8b80ea0b 100644 --- a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/strings/StringCodecTest.java +++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/strings/StringCodecTest.java @@ -14,15 +14,19 @@ package com.google.devtools.build.lib.skyframe.serialization.strings; -import com.google.devtools.build.lib.skyframe.serialization.testutils.AbstractObjectCodecTest; +import com.google.devtools.build.lib.skyframe.serialization.testutils.ObjectCodecTester; +import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** Basic tests for {@link StringCodec}. */ @RunWith(JUnit4.class) -public class StringCodecTest extends AbstractObjectCodecTest<String> { +public class StringCodecTest { - public StringCodecTest() { - super(new StringCodec(), "usually precomputed and supports weird unicodes: (╯°□°)╯︵┻━┻ "); + @Test + public void testCodec() throws Exception { + ObjectCodecTester.newBuilder(new StringCodec()) + .addSubjects("usually precomputed and supports weird unicodes: (╯°□°)╯︵┻━┻ ") + .buildAndRunTests(); } } |