diff options
author | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2023-12-10 20:29:35 +0100 |
---|---|---|
committer | Matthias Andreas Benkard <code@mail.matthias.benkard.de> | 2023-12-10 20:32:21 +0100 |
commit | df853ef46a9c12d319bf824ac106a411f5eddabd (patch) | |
tree | f7e0112d7ff160c12186ba7d7ec4671a6487aeeb | |
parent | 3ad12086acde4cfa9d346938e876947bde6305dc (diff) |
Add property-based tests and fix the bugs discovered.
Change-Id: I8deb1a7d75078c037714541d8f6f656052c2476c
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | jgvariant-core/pom.xml | 5 | ||||
-rw-r--r-- | jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java | 18 | ||||
-rw-r--r-- | jgvariant-core/src/test/java/eu/mulk/jgvariant/core/DecoderPropertyTest.java | 89 | ||||
-rw-r--r-- | jgvariant-ostree/pom.xml | 5 | ||||
-rw-r--r-- | jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderPropertyTest.java | 66 | ||||
-rw-r--r-- | jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java | 14 | ||||
-rw-r--r-- | jgvariant-parent/pom.xml | 7 |
8 files changed, 191 insertions, 14 deletions
@@ -10,6 +10,7 @@ *~ .envrc .flattened-pom.xml +.jqwik-database /.idea /jgvariant-*/target /target diff --git a/jgvariant-core/pom.xml b/jgvariant-core/pom.xml index b37b65c..da34551 100644 --- a/jgvariant-core/pom.xml +++ b/jgvariant-core/pom.xml @@ -61,6 +61,11 @@ SPDX-License-Identifier: LGPL-3.0-or-later <artifactId>junit-jupiter-api</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>net.jqwik</groupId> + <artifactId>jqwik</artifactId> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java index 9362487..fcbb639 100644 --- a/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java +++ b/jgvariant-core/src/main/java/eu/mulk/jgvariant/core/Decoder.java @@ -342,14 +342,22 @@ public abstract class Decoder<T> { } private static int getIntN(ByteBuffer byteSlice) { - var intBytes = new byte[4]; - byteSlice.get(intBytes, 0, Math.min(4, byteSlice.limit())); - return ByteBuffer.wrap(intBytes).order(LITTLE_ENDIAN).getInt(); + return switch (byteSlice.limit()) { + case 0 -> 0; + case 1 -> + Byte.toUnsignedInt(byteSlice.order(LITTLE_ENDIAN).get()); + case 2 -> + Short.toUnsignedInt(byteSlice.order(LITTLE_ENDIAN).getShort()); + case 4 -> + byteSlice.order(LITTLE_ENDIAN).getInt(); + default -> + throw new IllegalArgumentException("invalid byte count: %d".formatted(byteSlice.limit())); + }; } @SuppressWarnings("java:S3358") private static int byteCount(int n) { - return n < (1 << 8) ? 1 : n < (1 << 16) ? 2 : 4; + return n == 0 ? 0 : n < (1 << 8) ? 1 : n < (1 << 16) ? 2 : 4; } private static int computeFramingOffsetSize(int elementsRelativeEnd, List<Integer> framingOffsets) { @@ -404,7 +412,7 @@ public abstract class Decoder<T> { elements = List.of(); } else { // An array with aligned elements and a vector of framing offsets in the end. - int framingOffsetSize = byteCount(byteSlice.limit()); + int framingOffsetSize = max(1, byteCount(byteSlice.limit())); int lastFramingOffset = getIntN(byteSlice.slice(byteSlice.limit() - framingOffsetSize, framingOffsetSize)); int elementCount = (byteSlice.limit() - lastFramingOffset) / framingOffsetSize; diff --git a/jgvariant-core/src/test/java/eu/mulk/jgvariant/core/DecoderPropertyTest.java b/jgvariant-core/src/test/java/eu/mulk/jgvariant/core/DecoderPropertyTest.java new file mode 100644 index 0000000..5e07ea0 --- /dev/null +++ b/jgvariant-core/src/test/java/eu/mulk/jgvariant/core/DecoderPropertyTest.java @@ -0,0 +1,89 @@ +package eu.mulk.jgvariant.core; + +import java.text.ParseException; +import java.util.Optional; +import net.jqwik.api.*; + +@SuppressWarnings("java:S2187") +class DecoderPropertyTest { + + @Group + class VariantRoundtripLaw implements RoundtripLaw<Variant> { + + @Override + public Decoder<Variant> decoder() { + return Decoder.ofVariant(); + } + + @Override + public Arbitrary<Variant> anyT() { + return anyVariant(); + } + } + + interface RoundtripLaw<T> { + + @Property + default boolean roundtripsWell(@ForAll(value = "anyT") T entityLeft) { + var decoder = decoder(); + var bytes = decoder.encode(entityLeft); + var entityRight = decoder.decode(bytes); + return entityLeft.equals(entityRight); + } + + Decoder<T> decoder(); + + @Provide + Arbitrary<T> anyT(); + } + + @Provide + Arbitrary<Variant> anyVariant() { + var anyString = Arbitraries.strings().map(s -> new Variant(parseSignature("s"), s)); + var anyInt = Arbitraries.integers().map(i -> new Variant(parseSignature("i"), i)); + var anyLong = Arbitraries.longs().map(l -> new Variant(parseSignature("x"), l)); + var anyDouble = Arbitraries.doubles().map(d -> new Variant(parseSignature("d"), d)); + var anyBoolean = + Arbitraries.of(Boolean.TRUE, Boolean.FALSE).map(b -> new Variant(parseSignature("b"), b)); + var anyByte = Arbitraries.bytes().map(b -> new Variant(parseSignature("y"), b)); + var anyShort = Arbitraries.shorts().map(s -> new Variant(parseSignature("n"), s)); + var anyByteArray = Arbitraries.bytes().list().map(b -> new Variant(parseSignature("ay"), b)); + var anySome = + Arbitraries.lazyOf( + () -> + anyVariant() + .map( + x -> + new Variant( + parseSignature("m" + x.signature().toString()), + Optional.of(x.value())))); + var anyNone = + Arbitraries.lazyOf( + () -> + anyVariant() + .map( + x -> + new Variant( + parseSignature("m" + x.signature().toString()), Optional.empty()))); + // FIXME missing: list, tuple, dictionary, variant + return Arbitraries.oneOf( + anyString, + anyInt, + anyLong, + anyDouble, + anyBoolean, + anyByte, + anyShort, + anyByteArray, + anySome, + anyNone); + } + + private Signature parseSignature(String s) { + try { + return Signature.parse(s); + } catch (ParseException e) { + throw new AssertionError(e); + } + } +} diff --git a/jgvariant-ostree/pom.xml b/jgvariant-ostree/pom.xml index 79f5f48..e5aebc3 100644 --- a/jgvariant-ostree/pom.xml +++ b/jgvariant-ostree/pom.xml @@ -79,6 +79,11 @@ SPDX-License-Identifier: LGPL-3.0-or-later <artifactId>inject-resources-junit-jupiter</artifactId> <scope>test</scope> </dependency> + <dependency> + <groupId>net.jqwik</groupId> + <artifactId>jqwik</artifactId> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderPropertyTest.java b/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderPropertyTest.java new file mode 100644 index 0000000..acd11c4 --- /dev/null +++ b/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderPropertyTest.java @@ -0,0 +1,66 @@ +package eu.mulk.jgvariant.ostree; + +import eu.mulk.jgvariant.core.Decoder; +import java.util.Map; +import net.jqwik.api.*; + +@SuppressWarnings("java:S2187") +class OstreeDecoderPropertyTest { + + @Group + class SummaryRoundtripLaw implements RoundtripLaw<Summary> { + + @Override + public Decoder<Summary> decoder() { + return Summary.decoder(); + } + + @Override + public Arbitrary<Summary> anyT() { + return anySummary(); + } + } + + interface RoundtripLaw<T> { + + @Property + default boolean roundtripsWell(@ForAll(value = "anyT") T entityLeft) { + var decoder = decoder(); + var bytes = decoder.encode(entityLeft); + var entityRight = decoder.decode(bytes); + return entityLeft.equals(entityRight); + } + + Decoder<T> decoder(); + + @Provide + Arbitrary<T> anyT(); + } + + @Provide + Arbitrary<Summary> anySummary() { + return Combinators.combine(anySummaryEntry().list(), anyMetadata()).as(Summary::new); + } + + @Provide + Arbitrary<Metadata> anyMetadata() { + return Arbitraries.of(new Metadata(Map.of())); + } + + @Provide + Arbitrary<Summary.Entry> anySummaryEntry() { + return Combinators.combine(Arbitraries.strings(), anySummaryEntryValue()) + .as(Summary.Entry::new); + } + + @Provide + Arbitrary<Summary.Entry.Value> anySummaryEntryValue() { + return Combinators.combine(Arbitraries.integers(), anyChecksum(), anyMetadata()) + .as(Summary.Entry.Value::new); + } + + @Provide + Arbitrary<Checksum> anyChecksum() { + return Arbitraries.of(new Checksum(new ByteString(new byte[32]))); + } +} diff --git a/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java b/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java index 4465d02..793c5a5 100644 --- a/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java +++ b/jgvariant-ostree/src/test/java/eu/mulk/jgvariant/ostree/OstreeDecoderTest.java @@ -110,10 +110,9 @@ class OstreeDecoderTest { summary.metadata().fields())); var encoded = decoder.encode(summary); + input.rewind(); assertEquals(input, encoded); - - System.out.println(summary); } @Test @@ -123,10 +122,9 @@ class OstreeDecoderTest { var commit = decoder.decode(input); var encoded = decoder.encode(commit); + input.rewind(); assertEquals(input, encoded); - - System.out.println(commit); } @Test @@ -136,10 +134,9 @@ class OstreeDecoderTest { var dirTree = decoder.decode(input); var encoded = decoder.encode(dirTree); + input.rewind(); assertEquals(input, encoded); - - System.out.println(dirTree); } @Test @@ -149,10 +146,9 @@ class OstreeDecoderTest { var dirMeta = decoder.decode(ByteBuffer.wrap(dirMetaBytes)); var encoded = decoder.encode(dirMeta); + input.rewind(); assertEquals(input, encoded); - - System.out.println(dirMeta); } @Test @@ -160,9 +156,9 @@ class OstreeDecoderTest { var decoder = DeltaSuperblock.decoder(); var input = ByteBuffer.wrap(deltaSuperblockBytes); var deltaSuperblock = decoder.decode(input); - System.out.println(deltaSuperblock); var encoded = decoder.encode(deltaSuperblock); + input.rewind(); assertEquals(input, encoded); } diff --git a/jgvariant-parent/pom.xml b/jgvariant-parent/pom.xml index 5444b7d..283c62a 100644 --- a/jgvariant-parent/pom.xml +++ b/jgvariant-parent/pom.xml @@ -76,6 +76,7 @@ SPDX-License-Identifier: LGPL-3.0-or-later <guava.version>32.1.3-jre</guava.version> <inject-resources.version>0.3.3</inject-resources.version> <jetbrains-annotations.version>24.1.0</jetbrains-annotations.version> + <jqwik.version>1.8.2</jqwik.version> <junit-jupiter.version>5.10.1</junit-jupiter.version> <nullaway.version>0.10.18</nullaway.version> <picocli.version>4.7.5</picocli.version> @@ -156,6 +157,12 @@ SPDX-License-Identifier: LGPL-3.0-or-later <version>${inject-resources.version}</version> <scope>test</scope> </dependency> + <dependency> + <groupId>net.jqwik</groupId> + <artifactId>jqwik</artifactId> + <version>${jqwik.version}</version> + <scope>test</scope> + </dependency> </dependencies> </dependencyManagement> |