Skip to content

Commit 5b0e6d6

Browse files
committed
test: add edge cases for ByteStringUtil
1 parent 56b525e commit 5b0e6d6

2 files changed

Lines changed: 64 additions & 14 deletions

File tree

src/main/java/io/weaviate/client6/v1/internal/grpc/ByteStringUtil.java

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,47 +57,58 @@ public static ByteString encodeVectorMulti(float[][] vectors) {
5757
}
5858

5959
/**
60-
* Decode ByteString to float[].
60+
* Decode ByteString to {@code float[]}.
6161
*
6262
* @throws IllegalArgumentException if ByteString size is not
6363
* a multiple of {@link Float#BYTES}.
6464
*/
6565
public static float[] decodeVectorSingle(ByteString bs) {
6666
if (bs.size() % Float.BYTES != 0) {
6767
throw new IllegalArgumentException(
68-
"ByteString is size " + bs.size() + ", not a multiple of " + String.valueOf(Float.BYTES) + " (Float.BYTES)");
68+
"ByteString size " + bs.size() + " is not a multiple of " + String.valueOf(Float.BYTES) + " (Float.BYTES)");
6969
}
7070
float[] vector = new float[bs.size() / Float.BYTES];
7171
bs.asReadOnlyByteBuffer().order(BYTE_ORDER).asFloatBuffer().get(vector);
7272
return vector;
7373
}
7474

7575
/**
76-
* Decode ByteString to float[][].
76+
* Decode ByteString to {@code float[][]}.
7777
*
78-
* @throws IllegalArgumentException if ByteString size is not
79-
* a multiple of {@link Float#BYTES}.
78+
* <p>
79+
* The expected structure of the byte string of total size N is:
80+
* <ul>
81+
* <li>[2 bytes]: dimensionality of the inner vector ({@code dim})
82+
* <li>[N-2 bytes]: concatenated inner vectors. N-2 must be a multiple of
83+
* {@code Float.BYTES * dim}
84+
* </ul>
85+
*
86+
* @throws IllegalArgumentException if ByteString is not of a valid size.
8087
*/
8188
public static float[][] decodeVectorMulti(ByteString bs) {
8289
if (bs == null || bs.size() == 0) {
8390
return new float[0][0];
8491
}
8592

8693
ByteBuffer buf = bs.asReadOnlyByteBuffer().order(BYTE_ORDER);
94+
short dim = buf.getShort(); // advances current position
95+
if (dim == 0) {
96+
return new float[0][0];
97+
}
8798

88-
// Dimensions are encoded in the first 2 bytes.
89-
short dimensions = buf.getShort(); // advances current position
90-
91-
// TODO: throw IllegalArgumentException if fbuf.remaining not a multile of
92-
// Float.BYTES
93-
FloatBuffer fbuf = buf.asFloatBuffer();
94-
int n = fbuf.remaining() / dimensions; // fbuf size is buf / Float.BYTES
99+
FloatBuffer fbuf = buf.asFloatBuffer(); // fbuf size is buf / Float.BYTES
100+
if (fbuf.remaining() % dim != 0) {
101+
throw new IllegalArgumentException(
102+
"Remaing ByteString size " + fbuf.remaining() + " is not a multiple of " + dim
103+
+ " (dim)");
104+
}
105+
int n = fbuf.remaining() / dim;
95106

96107
// Reading from buffer advances current position,
97108
// so we always read from offset=0.
98-
float[][] vectors = new float[n][dimensions];
109+
float[][] vectors = new float[n][dim];
99110
for (int i = 0; i < n; i++) {
100-
fbuf.get(vectors[i], 0, dimensions);
111+
fbuf.get(vectors[i], 0, dim);
101112
}
102113
return vectors;
103114
}

src/test/java/io/weaviate/client6/v1/internal/grpc/ByteStringUtilTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,43 @@ public void test_decodeUuid() {
5454
String got = ByteStringUtil.decodeUuid(ByteString.copyFrom(bytes)).toString();
5555
assertEquals(want, got);
5656
}
57+
58+
@Test
59+
public void test_decodeVector_1d_empty() {
60+
byte[] bytes = new byte[0];
61+
float[] got = ByteStringUtil.decodeVectorSingle(ByteString.copyFrom(bytes));
62+
assertEquals(0, got.length);
63+
}
64+
65+
@Test
66+
public void test_decodeVector_2d_empty() {
67+
byte[] bytes = new byte[0];
68+
float[][] got = ByteStringUtil.decodeVectorMulti(ByteString.copyFrom(bytes));
69+
assertEquals(0, got.length);
70+
}
71+
72+
@Test
73+
public void test_decodeVector_2d_dim_zero() {
74+
byte[] bytes = new byte[] { 0, 0 };
75+
float[][] got = ByteStringUtil.decodeVectorMulti(ByteString.copyFrom(bytes));
76+
assertEquals(0, got.length);
77+
}
78+
79+
@Test(expected = IllegalArgumentException.class)
80+
public void test_decodeVector_1d_illegal() {
81+
byte[] bytes = new byte[Float.BYTES - 1]; // must be a multiple of Float.BYTES
82+
ByteStringUtil.decodeVectorSingle(ByteString.copyFrom(bytes));
83+
}
84+
85+
@Test(expected = IllegalArgumentException.class)
86+
public void test_decodeVector_2d_illegal() {
87+
// The first Short.BYTES is the dimensionality of each array.
88+
// The size of the rest must be a multiple of Float.BYTES * dimensionality.
89+
var dimensionality = 5;
90+
byte[] bytes = new byte[Short.BYTES + (Float.BYTES * dimensionality - 1)];
91+
bytes[0] = 0;
92+
bytes[1] = (byte) dimensionality;
93+
94+
ByteStringUtil.decodeVectorMulti(ByteString.copyFrom(bytes));
95+
}
5796
}

0 commit comments

Comments
 (0)