|
1 | 1 | package io.numaproj.numaflow.accumulator; |
2 | 2 |
|
3 | 3 | import com.google.protobuf.ByteString; |
| 4 | +import com.google.protobuf.Timestamp; |
4 | 5 | import io.grpc.ManagedChannel; |
5 | 6 | import io.grpc.inprocess.InProcessChannelBuilder; |
6 | 7 | import io.grpc.inprocess.InProcessServerBuilder; |
@@ -124,6 +125,95 @@ public void testAccumulatorSingleKey() { |
124 | 125 | } |
125 | 126 | } |
126 | 127 |
|
| 128 | + @Test |
| 129 | + public void testAccumulatorEOFEchoesCloseWindow() { |
| 130 | + List<String> keys = List.of("test-accumulator"); |
| 131 | + |
| 132 | + AccumulatorOuterClass.KeyedWindow openWindow = AccumulatorOuterClass.KeyedWindow |
| 133 | + .newBuilder() |
| 134 | + .setStart(Timestamp.newBuilder().setSeconds(0).build()) |
| 135 | + .setEnd(Timestamp.newBuilder().setSeconds(60).build()) |
| 136 | + .setSlot("slot-0") |
| 137 | + .addAllKeys(keys) |
| 138 | + .build(); |
| 139 | + |
| 140 | + AccumulatorOuterClass.AccumulatorRequest openRequest = AccumulatorOuterClass.AccumulatorRequest |
| 141 | + .newBuilder() |
| 142 | + .setPayload(AccumulatorOuterClass.Payload |
| 143 | + .newBuilder() |
| 144 | + .setValue(ByteString.copyFromUtf8("test-payload")) |
| 145 | + .addAllKeys(keys) |
| 146 | + .build()) |
| 147 | + .setOperation(AccumulatorOuterClass.AccumulatorRequest.WindowOperation |
| 148 | + .newBuilder() |
| 149 | + .setEvent(AccumulatorOuterClass.AccumulatorRequest.WindowOperation.Event.OPEN) |
| 150 | + .setKeyedWindow(openWindow) |
| 151 | + .build()) |
| 152 | + .build(); |
| 153 | + |
| 154 | + AccumulatorOuterClass.AccumulatorRequest appendRequest = AccumulatorOuterClass.AccumulatorRequest |
| 155 | + .newBuilder() |
| 156 | + .setPayload(AccumulatorOuterClass.Payload |
| 157 | + .newBuilder() |
| 158 | + .setValue(ByteString.copyFromUtf8("test-payload")) |
| 159 | + .addAllKeys(keys) |
| 160 | + .build()) |
| 161 | + .setOperation(AccumulatorOuterClass.AccumulatorRequest.WindowOperation |
| 162 | + .newBuilder() |
| 163 | + .setEvent(AccumulatorOuterClass.AccumulatorRequest.WindowOperation.Event.APPEND) |
| 164 | + .setKeyedWindow(openWindow) |
| 165 | + .build()) |
| 166 | + .build(); |
| 167 | + |
| 168 | + // CLOSE carries a distinct window that must be echoed verbatim in the EOF response. |
| 169 | + AccumulatorOuterClass.KeyedWindow closeWindow = AccumulatorOuterClass.KeyedWindow |
| 170 | + .newBuilder() |
| 171 | + .setStart(Timestamp.newBuilder().setSeconds(1000).build()) |
| 172 | + .setEnd(Timestamp.newBuilder().setSeconds(2000).build()) |
| 173 | + .setSlot("slot-7") |
| 174 | + .addAllKeys(keys) |
| 175 | + .build(); |
| 176 | + AccumulatorOuterClass.AccumulatorRequest closeRequest = AccumulatorOuterClass.AccumulatorRequest |
| 177 | + .newBuilder() |
| 178 | + .setOperation(AccumulatorOuterClass.AccumulatorRequest.WindowOperation |
| 179 | + .newBuilder() |
| 180 | + .setEvent(AccumulatorOuterClass.AccumulatorRequest.WindowOperation.Event.CLOSE) |
| 181 | + .setKeyedWindow(closeWindow) |
| 182 | + .build()) |
| 183 | + .build(); |
| 184 | + |
| 185 | + // 2 data responses + 1 EOF response. |
| 186 | + AccumulatorStreamObserver responseObserver = new AccumulatorStreamObserver(3); |
| 187 | + |
| 188 | + var stub = AccumulatorGrpc.newStub(inProcessChannel); |
| 189 | + var requestStreamObserver = stub.accumulateFn(responseObserver); |
| 190 | + |
| 191 | + requestStreamObserver.onNext(openRequest); |
| 192 | + requestStreamObserver.onNext(appendRequest); |
| 193 | + requestStreamObserver.onNext(closeRequest); |
| 194 | + requestStreamObserver.onCompleted(); |
| 195 | + |
| 196 | + try { |
| 197 | + responseObserver.done.get(); |
| 198 | + } catch (InterruptedException | ExecutionException e) { |
| 199 | + fail("Error while waiting for response" + e.getMessage()); |
| 200 | + } |
| 201 | + |
| 202 | + List<AccumulatorOuterClass.AccumulatorResponse> responses = responseObserver.getResponses(); |
| 203 | + assertEquals(3, responses.size()); |
| 204 | + |
| 205 | + AccumulatorOuterClass.AccumulatorResponse eof = null; |
| 206 | + for (AccumulatorOuterClass.AccumulatorResponse response : responses) { |
| 207 | + if (response.getEOF()) { |
| 208 | + eof = response; |
| 209 | + } |
| 210 | + } |
| 211 | + assertEquals(1000, eof.getWindow().getStart().getSeconds()); |
| 212 | + assertEquals(2000, eof.getWindow().getEnd().getSeconds()); |
| 213 | + assertEquals("slot-7", eof.getWindow().getSlot()); |
| 214 | + assertEquals(keys, eof.getWindow().getKeysList()); |
| 215 | + } |
| 216 | + |
127 | 217 | private static class TestAccumFn extends Accumulator { |
128 | 218 | @Override |
129 | 219 | public void processMessage(Datum datum, OutputStreamObserver outputStream) { |
|
0 commit comments