Skip to content

Commit e3d33e1

Browse files
dfa1claude
andcommitted
test: cover NativeLibrary platform mapping across all branches
Extract a pure, package-private classifier(osName, osArch) from the system-property-reading classifier() and open up libExtension, so the platform-mapping logic can be unit-tested with injected os.name / os.arch instead of only the CI host's values. Adds branch coverage for the osx / windows / linux and arch-alias paths that never run on a single platform, plus the unsupported-arch failure. Also pins toLowerCase to Locale.ROOT. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 634719d commit e3d33e1

2 files changed

Lines changed: 96 additions & 11 deletions

File tree

zstd/src/main/java/io/github/dfa1/zstd/NativeLibrary.java

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.nio.file.StandardCopyOption;
1414
import java.nio.file.attribute.FileAttribute;
1515
import java.nio.file.attribute.PosixFilePermissions;
16+
import java.util.Locale;
1617

1718
/// Infrastructure — loads the bundled `libzstd` shared library and binds
1819
/// native symbols to {@link MethodHandle}s via the Foreign Function & Memory API.
@@ -74,7 +75,7 @@ private static FileAttribute<?>[] ownerOnlyAttributes() {
7475
return new FileAttribute<?>[0];
7576
}
7677

77-
private static String libExtension(String classifier) {
78+
static String libExtension(String classifier) {
7879
if (classifier.startsWith("osx")) {
7980
return "dylib";
8081
}
@@ -85,27 +86,39 @@ private static String libExtension(String classifier) {
8586
}
8687

8788
private static String classifier() {
88-
String os = System.getProperty("os.name", "").toLowerCase();
89-
String arch = System.getProperty("os.arch", "").toLowerCase();
90-
String osName;
89+
return classifier(System.getProperty("os.name", ""), System.getProperty("os.arch", ""));
90+
}
91+
92+
/// Maps a JVM `os.name` / `os.arch` pair to a native-jar classifier
93+
/// (`<os>-<arch>`). A pure function of its inputs so it can be unit-tested
94+
/// across platforms without touching the real system properties.
95+
///
96+
/// @param osName the raw `os.name` value (any case)
97+
/// @param osArch the raw `os.arch` value (any case)
98+
/// @return the `<os>-<arch>` classifier naming the platform's native jar
99+
/// @throws UnsatisfiedLinkError if the CPU architecture is not supported
100+
static String classifier(String osName, String osArch) {
101+
String os = osName.toLowerCase(Locale.ROOT);
102+
String arch = osArch.toLowerCase(Locale.ROOT);
103+
String osPart;
91104
if (os.contains("mac") || os.contains("darwin")) {
92-
osName = "osx";
105+
osPart = "osx";
93106
} else if (os.contains("win")) {
94-
osName = "windows";
107+
osPart = "windows";
95108
} else {
96-
osName = "linux";
109+
osPart = "linux";
97110
}
98-
String archName;
111+
String archPart;
99112
if (arch.equals("aarch64") || arch.equals("arm64")) {
100-
archName = "aarch64";
113+
archPart = "aarch64";
101114
} else if (arch.equals("x86_64") || arch.equals("amd64")) {
102-
archName = "x86_64";
115+
archPart = "x86_64";
103116
} else {
104117
throw new UnsatisfiedLinkError(
105118
"Unsupported CPU architecture '" + arch + "'; zstd-java ships native libraries "
106119
+ "only for x86_64 and aarch64");
107120
}
108-
return osName + "-" + archName;
121+
return osPart + "-" + archPart;
109122
}
110123

111124
private NativeLibrary() {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package io.github.dfa1.zstd;
2+
3+
import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
4+
import org.junit.jupiter.api.Nested;
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.params.ParameterizedTest;
7+
import org.junit.jupiter.params.provider.CsvSource;
8+
9+
import static org.assertj.core.api.Assertions.assertThat;
10+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
11+
12+
class NativeLibraryTest {
13+
14+
@Nested
15+
class Classifier {
16+
17+
@ParameterizedTest
18+
@CsvSource({
19+
// os.name variants -> os part
20+
"Mac OS X, aarch64, osx-aarch64",
21+
"Darwin, arm64, osx-aarch64",
22+
"Windows 11, amd64, windows-x86_64",
23+
"Windows 11, aarch64, windows-aarch64",
24+
"Linux, x86_64, linux-x86_64",
25+
// unknown os falls back to linux; arch aliases normalise
26+
"FreeBSD, aarch64, linux-aarch64",
27+
"Linux, amd64, linux-x86_64",
28+
"Linux, arm64, linux-aarch64",
29+
})
30+
void mapsOsAndArchToClassifier(String osName, String osArch, String expected) {
31+
// Given a JVM os.name / os.arch pair, in mixed case
32+
// When mapped to a native-jar classifier
33+
String classifier = NativeLibrary.classifier(osName, osArch);
34+
35+
// Then it names the matching platform's native jar
36+
assertThat(classifier).isEqualTo(expected);
37+
}
38+
39+
@Test
40+
void rejectsUnsupportedArchitecture() {
41+
// Given a CPU architecture this library ships no native jar for
42+
ThrowingCallable result = () -> NativeLibrary.classifier("Linux", "sparc");
43+
44+
// Then it fails fast naming the offending arch, not a cryptic dlopen error
45+
assertThatThrownBy(result)
46+
.isInstanceOf(UnsatisfiedLinkError.class)
47+
.hasMessageContaining("sparc");
48+
}
49+
}
50+
51+
@Nested
52+
class LibraryExtension {
53+
54+
@ParameterizedTest
55+
@CsvSource({
56+
"osx-aarch64, dylib",
57+
"osx-x86_64, dylib",
58+
"windows-x86_64, dll",
59+
"windows-aarch64, dll",
60+
"linux-x86_64, so",
61+
"linux-aarch64, so",
62+
})
63+
void mapsClassifierToSharedLibraryExtension(String classifier, String extension) {
64+
// Given a platform classifier
65+
// When its shared-library extension is resolved
66+
String ext = NativeLibrary.libExtension(classifier);
67+
68+
// Then it matches the platform's native library suffix
69+
assertThat(ext).isEqualTo(extension);
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)