Skip to content

Commit dd7fe99

Browse files
dfa1claude
andcommitted
test: integration-tests module — zstd-jni interop
New test-only module proving frame format compatibility against the reference zstd-jni binding (which bundles zstd 1.5.x vs this library's 1.6.0). Keeps zstd-jni out of the dependency-free core module. - one-shot both directions across sizes {0,1,1K,64K,1M} x levels {min,1,default,max} - streaming both directions (our streams <-> zstd-jni streams) - dictionary both directions (our ZstdDictionary <-> zstd-jni ZstdDict{Compress,Decompress}) 44 interop tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 43097b1 commit dd7fe99

3 files changed

Lines changed: 292 additions & 0 deletions

File tree

integration-tests/pom.xml

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<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/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
5+
<parent>
6+
<groupId>io.github.dfa1</groupId>
7+
<artifactId>parent</artifactId>
8+
<version>0.1-SNAPSHOT</version>
9+
<relativePath>../pom.xml</relativePath>
10+
</parent>
11+
12+
<artifactId>zstd-java-integration-tests</artifactId>
13+
<name>zstd FFM Integration Tests</name>
14+
15+
<!--
16+
Format-compatibility tests against the reference zstd-jni binding: frames
17+
produced by one must decode with the other. Keeps zstd-jni out of the core
18+
module, which stays dependency-free. Not published.
19+
-->
20+
<properties>
21+
<zstd-jni.version>1.5.7-11</zstd-jni.version>
22+
<maven.deploy.skip>true</maven.deploy.skip>
23+
</properties>
24+
25+
<dependencies>
26+
<dependency>
27+
<groupId>io.github.dfa1</groupId>
28+
<artifactId>zstd-java</artifactId>
29+
<version>${project.version}</version>
30+
<scope>test</scope>
31+
</dependency>
32+
<dependency>
33+
<groupId>com.github.luben</groupId>
34+
<artifactId>zstd-jni</artifactId>
35+
<version>${zstd-jni.version}</version>
36+
<scope>test</scope>
37+
</dependency>
38+
<dependency>
39+
<groupId>org.junit.jupiter</groupId>
40+
<artifactId>junit-jupiter</artifactId>
41+
<scope>test</scope>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.assertj</groupId>
45+
<artifactId>assertj-core</artifactId>
46+
<scope>test</scope>
47+
</dependency>
48+
</dependencies>
49+
50+
<!-- The matching native library for the host platform (test scope only). -->
51+
<profiles>
52+
<profile>
53+
<id>native-osx-aarch64</id>
54+
<activation>
55+
<os><family>mac</family><arch>aarch64</arch></os>
56+
</activation>
57+
<dependencies>
58+
<dependency>
59+
<groupId>io.github.dfa1</groupId>
60+
<artifactId>zstd-java-native-osx-aarch64</artifactId>
61+
<version>${project.version}</version>
62+
<scope>test</scope>
63+
</dependency>
64+
</dependencies>
65+
</profile>
66+
<profile>
67+
<id>native-linux-x86_64</id>
68+
<activation>
69+
<os><family>unix</family><name>linux</name><arch>amd64</arch></os>
70+
</activation>
71+
<dependencies>
72+
<dependency>
73+
<groupId>io.github.dfa1</groupId>
74+
<artifactId>zstd-java-native-linux-x86_64</artifactId>
75+
<version>${project.version}</version>
76+
<scope>test</scope>
77+
</dependency>
78+
</dependencies>
79+
</profile>
80+
<profile>
81+
<id>native-linux-aarch64</id>
82+
<activation>
83+
<os><family>unix</family><name>linux</name><arch>aarch64</arch></os>
84+
</activation>
85+
<dependencies>
86+
<dependency>
87+
<groupId>io.github.dfa1</groupId>
88+
<artifactId>zstd-java-native-linux-aarch64</artifactId>
89+
<version>${project.version}</version>
90+
<scope>test</scope>
91+
</dependency>
92+
</dependencies>
93+
</profile>
94+
<profile>
95+
<id>native-osx-x86_64</id>
96+
<activation>
97+
<os><family>mac</family><arch>x86_64</arch></os>
98+
</activation>
99+
<dependencies>
100+
<dependency>
101+
<groupId>io.github.dfa1</groupId>
102+
<artifactId>zstd-java-native-osx-x86_64</artifactId>
103+
<version>${project.version}</version>
104+
<scope>test</scope>
105+
</dependency>
106+
</dependencies>
107+
</profile>
108+
<profile>
109+
<id>native-windows-x86_64</id>
110+
<activation>
111+
<os><family>windows</family><arch>amd64</arch></os>
112+
</activation>
113+
<dependencies>
114+
<dependency>
115+
<groupId>io.github.dfa1</groupId>
116+
<artifactId>zstd-java-native-windows-x86_64</artifactId>
117+
<version>${project.version}</version>
118+
<scope>test</scope>
119+
</dependency>
120+
</dependencies>
121+
</profile>
122+
<profile>
123+
<id>native-windows-aarch64</id>
124+
<activation>
125+
<os><family>windows</family><arch>aarch64</arch></os>
126+
</activation>
127+
<dependencies>
128+
<dependency>
129+
<groupId>io.github.dfa1</groupId>
130+
<artifactId>zstd-java-native-windows-aarch64</artifactId>
131+
<version>${project.version}</version>
132+
<scope>test</scope>
133+
</dependency>
134+
</dependencies>
135+
</profile>
136+
</profiles>
137+
</project>
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
package io.github.dfa1.zstd.it;
2+
3+
import com.github.luben.zstd.ZstdDictCompress;
4+
import com.github.luben.zstd.ZstdDictDecompress;
5+
import io.github.dfa1.zstd.Zstd;
6+
import io.github.dfa1.zstd.ZstdCompressCtx;
7+
import io.github.dfa1.zstd.ZstdDecompressCtx;
8+
import io.github.dfa1.zstd.ZstdDictionary;
9+
import io.github.dfa1.zstd.ZstdInputStream;
10+
import io.github.dfa1.zstd.ZstdOutputStream;
11+
import org.junit.jupiter.api.Nested;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.params.ParameterizedTest;
14+
import org.junit.jupiter.params.provider.Arguments;
15+
import org.junit.jupiter.params.provider.MethodSource;
16+
17+
import java.io.ByteArrayInputStream;
18+
import java.io.ByteArrayOutputStream;
19+
import java.nio.charset.StandardCharsets;
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import java.util.Random;
23+
import java.util.stream.Stream;
24+
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
27+
/// Format-compatibility tests: frames produced by the reference zstd-jni binding
28+
/// must decode with zstd-java and vice versa. zstd-jni bundles a different zstd
29+
/// version (1.5.x) than this library (1.6.0); the frame format is compatible, so
30+
/// these prove real interop, not just internal round-trips.
31+
class ZstdJniInteropTest {
32+
33+
static Stream<Arguments> payloadsAndLevels() {
34+
Random r = new Random(0xABCD);
35+
List<Arguments> cases = new ArrayList<>();
36+
int[] sizes = {0, 1, 1024, 64 * 1024, 1024 * 1024};
37+
int[] levels = {Zstd.minCompressionLevel(), 1, Zstd.defaultCompressionLevel(), Zstd.maxCompressionLevel()};
38+
for (int size : sizes) {
39+
byte[] data = size % 2 == 0 ? compressible(r, size) : random(r, size);
40+
for (int level : levels) {
41+
cases.add(Arguments.of(level, data));
42+
}
43+
}
44+
return cases.stream();
45+
}
46+
47+
@ParameterizedTest
48+
@MethodSource("payloadsAndLevels")
49+
void jniCompressJavaDecompress(int level, byte[] data) {
50+
byte[] frame = com.github.luben.zstd.Zstd.compress(data, level);
51+
assertThat(Zstd.decompress(frame, data.length)).isEqualTo(data);
52+
}
53+
54+
@ParameterizedTest
55+
@MethodSource("payloadsAndLevels")
56+
void javaCompressJniDecompress(int level, byte[] data) {
57+
byte[] frame = Zstd.compress(data, level);
58+
assertThat(com.github.luben.zstd.Zstd.decompress(frame, data.length)).isEqualTo(data);
59+
}
60+
61+
@Nested
62+
class Streaming {
63+
64+
@Test
65+
void javaStreamToJniStream() throws Exception {
66+
byte[] data = "interop streaming ".repeat(50_000).getBytes(StandardCharsets.UTF_8);
67+
68+
ByteArrayOutputStream sink = new ByteArrayOutputStream();
69+
try (ZstdOutputStream zout = new ZstdOutputStream(sink, 7)) {
70+
zout.write(data);
71+
}
72+
byte[] restored;
73+
try (var zin = new com.github.luben.zstd.ZstdInputStream(
74+
new ByteArrayInputStream(sink.toByteArray()))) {
75+
restored = zin.readAllBytes();
76+
}
77+
assertThat(restored).isEqualTo(data);
78+
}
79+
80+
@Test
81+
void jniStreamToJavaStream() throws Exception {
82+
byte[] data = "interop streaming ".repeat(50_000).getBytes(StandardCharsets.UTF_8);
83+
84+
ByteArrayOutputStream sink = new ByteArrayOutputStream();
85+
try (var zout = new com.github.luben.zstd.ZstdOutputStream(sink)) {
86+
zout.write(data);
87+
}
88+
byte[] restored;
89+
try (ZstdInputStream zin = new ZstdInputStream(new ByteArrayInputStream(sink.toByteArray()))) {
90+
restored = zin.readAllBytes();
91+
}
92+
assertThat(restored).isEqualTo(data);
93+
}
94+
}
95+
96+
@Nested
97+
class Dictionary {
98+
99+
@Test
100+
void javaDictCompressJniDictDecompress() {
101+
ZstdDictionary dict = trainDict();
102+
byte[] record = record(11);
103+
104+
byte[] frame;
105+
try (ZstdCompressCtx ctx = new ZstdCompressCtx()) {
106+
frame = ctx.compress(record, dict);
107+
}
108+
ZstdDictDecompress jniDict = new ZstdDictDecompress(dict.toByteArray());
109+
assertThat(com.github.luben.zstd.Zstd.decompress(frame, jniDict, record.length)).isEqualTo(record);
110+
}
111+
112+
@Test
113+
void jniDictCompressJavaDictDecompress() {
114+
ZstdDictionary dict = trainDict();
115+
byte[] record = record(22);
116+
117+
ZstdDictCompress jniDict = new ZstdDictCompress(dict.toByteArray(), Zstd.defaultCompressionLevel());
118+
byte[] frame = com.github.luben.zstd.Zstd.compress(record, jniDict);
119+
120+
byte[] restored;
121+
try (ZstdDecompressCtx ctx = new ZstdDecompressCtx()) {
122+
restored = ctx.decompress(frame, record.length, dict);
123+
}
124+
assertThat(restored).isEqualTo(record);
125+
}
126+
127+
private ZstdDictionary trainDict() {
128+
List<byte[]> samples = new ArrayList<>();
129+
for (int i = 0; i < 3000; i++) {
130+
samples.add(record(i));
131+
}
132+
return ZstdDictionary.train(samples, 8 * 1024);
133+
}
134+
135+
private byte[] record(int i) {
136+
return ("{\"id\":" + i + ",\"user\":\"u" + (i % 30) + "\",\"event\":\"click\"}")
137+
.getBytes(StandardCharsets.UTF_8);
138+
}
139+
}
140+
141+
private static byte[] random(Random r, int size) {
142+
byte[] b = new byte[size];
143+
r.nextBytes(b);
144+
return b;
145+
}
146+
147+
private static byte[] compressible(Random r, int size) {
148+
byte[] b = new byte[size];
149+
for (int i = 0; i < size; i++) {
150+
b[i] = (byte) ((i % 13 == 0) ? r.nextInt(256) : 'a' + (i % 8));
151+
}
152+
return b;
153+
}
154+
}

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
<module>native/windows-aarch64</module>
3737
<module>zstd</module>
3838
<module>bom</module>
39+
<module>integration-tests</module>
3940
<module>benchmark</module>
4041
</modules>
4142

0 commit comments

Comments
 (0)