Add Unit Tests for Feed Runtime Input Handler
Change-Id: I7088f489a7d53dee8cf6cdbf5baa7cd8d3884f55
Reviewed-on: https://asterix-gerrit.ics.uci.edu/866
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Michael Blow <michael.blow@couchbase.com>
diff --git a/hyracks-fullstack/hyracks/hyracks-api/pom.xml b/hyracks-fullstack/hyracks/hyracks-api/pom.xml
index 3961921..9336921 100644
--- a/hyracks-fullstack/hyracks/hyracks-api/pom.xml
+++ b/hyracks-fullstack/hyracks/hyracks-api/pom.xml
@@ -39,7 +39,23 @@
<properties>
<root.dir>${basedir}/../..</root.dir>
</properties>
-
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>2.4</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>test-jar</goal>
+ <goal>jar</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
<dependencies>
<dependency>
<groupId>org.json</groupId>
@@ -77,5 +93,22 @@
<artifactId>hyracks-util</artifactId>
<version>0.2.18-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>2.0.2-beta</version>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-api-mockito</artifactId>
+ <version>1.6.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.powermock</groupId>
+ <artifactId>powermock-module-junit4</artifactId>
+ <version>1.6.2</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
</project>
diff --git a/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/CountAndThrowError.java b/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/CountAndThrowError.java
new file mode 100644
index 0000000..19998a7
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/CountAndThrowError.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.api.test;
+
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.mockito.invocation.InvocationOnMock;
+
+public class CountAndThrowError extends CountAnswer {
+ private String errorMessage;
+
+ public CountAndThrowError(String errorMessage) {
+ this.errorMessage = errorMessage;
+ }
+
+ @Override
+ public Object call() throws HyracksDataException {
+ count++;
+ throw new UnknownError(errorMessage);
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ count++;
+ throw new UnknownError(errorMessage);
+ }
+}
\ No newline at end of file
diff --git a/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/CountAndThrowException.java b/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/CountAndThrowException.java
new file mode 100644
index 0000000..5a5ad59
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/CountAndThrowException.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.api.test;
+
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.mockito.invocation.InvocationOnMock;
+
+public class CountAndThrowException extends CountAnswer {
+ private String errorMessage;
+
+ public CountAndThrowException(String errorMessage) {
+ this.errorMessage = errorMessage;
+ }
+
+ @Override
+ public Object call() throws HyracksDataException {
+ count++;
+ throw new HyracksDataException(errorMessage);
+ }
+
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ count++;
+ throw new HyracksDataException(errorMessage);
+ }
+}
\ No newline at end of file
diff --git a/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/CountAnswer.java b/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/CountAnswer.java
new file mode 100644
index 0000000..e8a6654
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/CountAnswer.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.api.test;
+
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+public class CountAnswer implements Answer<Object> {
+ protected int count = 0;
+
+ @Override
+ public Object answer(InvocationOnMock invocation) throws Throwable {
+ count++;
+ return null;
+ }
+
+ public Object call() throws HyracksDataException {
+ count++;
+ return null;
+ }
+
+ public int getCallCount() {
+ return count;
+ }
+
+ public void reset() {
+ count = 0;
+ }
+}
\ No newline at end of file
diff --git a/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/FrameWriterTestUtils.java b/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/FrameWriterTestUtils.java
new file mode 100644
index 0000000..4bddfa9
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/FrameWriterTestUtils.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.api.test;
+
+import java.util.Collection;
+
+public class FrameWriterTestUtils {
+ public static final String EXCEPTION_MESSAGE = "IFrameWriter Exception in the call to the method ";
+ public static final String ERROR_MESSAGE = "IFrameWriter Error in the call to the method ";
+
+ public enum FrameWriterOperation {
+ Open,
+ NextFrame,
+ Fail,
+ Flush,
+ Close
+ }
+
+ public static TestFrameWriter create(Collection<FrameWriterOperation> exceptionThrowingOperations,
+ Collection<FrameWriterOperation> errorThrowingOperations) {
+ CountAnswer openAnswer =
+ createAnswer(FrameWriterOperation.Open, exceptionThrowingOperations, errorThrowingOperations);
+ CountAnswer nextAnswer =
+ createAnswer(FrameWriterOperation.NextFrame, exceptionThrowingOperations, errorThrowingOperations);
+ CountAnswer flushAnswer =
+ createAnswer(FrameWriterOperation.Flush, exceptionThrowingOperations, errorThrowingOperations);
+ CountAnswer failAnswer =
+ createAnswer(FrameWriterOperation.Fail, exceptionThrowingOperations, errorThrowingOperations);
+ CountAnswer closeAnswer =
+ createAnswer(FrameWriterOperation.Close, exceptionThrowingOperations, errorThrowingOperations);
+ return new TestFrameWriter(openAnswer, nextAnswer, flushAnswer, failAnswer, closeAnswer);
+ }
+
+ public static CountAnswer createAnswer(FrameWriterOperation operation,
+ Collection<FrameWriterOperation> exceptionThrowingOperations,
+ Collection<FrameWriterOperation> errorThrowingOperations) {
+ if (exceptionThrowingOperations.contains(operation)) {
+ return new CountAndThrowException(EXCEPTION_MESSAGE + operation.toString());
+ } else if (exceptionThrowingOperations.contains(operation)) {
+ return new CountAndThrowError(ERROR_MESSAGE + operation.toString());
+ } else {
+ return new CountAnswer();
+ }
+ }
+
+ public static TestControlledFrameWriter create(int initialFrameSize) {
+ return new TestControlledFrameWriter(initialFrameSize);
+ }
+}
diff --git a/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/TestControlledFrameWriter.java b/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/TestControlledFrameWriter.java
new file mode 100644
index 0000000..2a3f70d
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/TestControlledFrameWriter.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.api.test;
+
+import java.nio.ByteBuffer;
+
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+
+public class TestControlledFrameWriter extends TestFrameWriter {
+ private boolean frozen = false;
+ private boolean timed = false;
+ private long duration = Long.MAX_VALUE;
+ private final int initialFrameSize;
+ private volatile int currentMultiplier = 0;
+ private volatile int kicks = 0;
+
+ public TestControlledFrameWriter(int initialFrameSize) {
+ super(new CountAnswer(), new CountAnswer(), new CountAnswer(), new CountAnswer(), new CountAnswer());
+ this.initialFrameSize = initialFrameSize;
+ }
+
+ public int getCurrentMultiplier() {
+ return currentMultiplier;
+ }
+
+ public synchronized void freeze() {
+ frozen = true;
+ }
+
+ public synchronized void time(long ms) {
+ frozen = true;
+ timed = true;
+ duration = ms;
+ }
+
+ public synchronized void unfreeze() {
+ frozen = false;
+ notify();
+ }
+
+ public synchronized void kick() {
+ kicks++;
+ notify();
+ }
+
+ @Override
+ public synchronized void nextFrame(ByteBuffer buffer) throws HyracksDataException {
+ super.nextFrame(buffer);
+ currentMultiplier = buffer.capacity() / initialFrameSize;
+ if (frozen) {
+ try {
+ if (timed) {
+ wait(duration);
+ } else {
+ while (frozen && kicks == 0) {
+ wait();
+ }
+ kicks--;
+ }
+ } catch (InterruptedException e) {
+ throw new HyracksDataException(e);
+ }
+ }
+ currentMultiplier = 0;
+ }
+}
diff --git a/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/TestFrameWriter.java b/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/TestFrameWriter.java
new file mode 100644
index 0000000..b3492fe
--- /dev/null
+++ b/hyracks-fullstack/hyracks/hyracks-api/src/test/java/org/apache/hyracks/api/test/TestFrameWriter.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.hyracks.api.test;
+
+import java.nio.ByteBuffer;
+
+import org.apache.hyracks.api.comm.IFrameWriter;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+
+public class TestFrameWriter implements IFrameWriter {
+ private final CountAnswer openAnswer;
+ private final CountAnswer nextAnswer;
+ private final CountAnswer flushAnswer;
+ private final CountAnswer failAnswer;
+ private final CountAnswer closeAnswer;
+ private long openDuration = 0L;
+ private long nextDuration = 0L;
+ private long flushDuration = 0L;
+ private long failDuration = 0L;
+ private long closeDuration = 0L;
+
+ public TestFrameWriter(CountAnswer openAnswer, CountAnswer nextAnswer, CountAnswer flushAnswer,
+ CountAnswer failAnswer, CountAnswer closeAnswer) {
+ this.openAnswer = openAnswer;
+ this.nextAnswer = nextAnswer;
+ this.closeAnswer = closeAnswer;
+ this.flushAnswer = flushAnswer;
+ this.failAnswer = failAnswer;
+ }
+
+ @Override
+ public void open() throws HyracksDataException {
+ delay(openDuration);
+ openAnswer.call();
+ }
+
+ public int openCount() {
+ return openAnswer.getCallCount();
+ }
+
+ @Override
+ public void nextFrame(ByteBuffer buffer) throws HyracksDataException {
+ delay(nextDuration);
+ nextAnswer.call();
+ }
+
+ public int nextFrameCount() {
+ return nextAnswer.getCallCount();
+ }
+
+ @Override
+ public void flush() throws HyracksDataException {
+ delay(flushDuration);
+ flushAnswer.call();
+ }
+
+ public int flushCount() {
+ return flushAnswer.getCallCount();
+ }
+
+ @Override
+ public void fail() throws HyracksDataException {
+ delay(failDuration);
+ failAnswer.call();
+ }
+
+ public int failCount() {
+ return failAnswer.getCallCount();
+ }
+
+ @Override
+ public void close() throws HyracksDataException {
+ delay(closeDuration);
+ closeAnswer.call();
+ }
+
+ public int closeCount() {
+ return closeAnswer.getCallCount();
+ }
+
+ public synchronized boolean validate(boolean finished) {
+ if (failAnswer.getCallCount() > 1 || closeAnswer.getCallCount() > 1 || openAnswer.getCallCount() > 1) {
+ return false;
+ }
+ if (openAnswer.getCallCount() == 0
+ && (nextAnswer.getCallCount() > 0 || failAnswer.getCallCount() > 0 || closeAnswer.getCallCount() > 0)) {
+ return false;
+ }
+ if (finished) {
+ if (closeAnswer.getCallCount() == 0 && (nextAnswer.getCallCount() > 0 || failAnswer.getCallCount() > 0
+ || openAnswer.getCallCount() > 0)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void reset() {
+ openAnswer.reset();
+ nextAnswer.reset();
+ flushAnswer.reset();
+ failAnswer.reset();
+ closeAnswer.reset();
+ }
+
+ public long getOpenDuration() {
+ return openDuration;
+ }
+
+ public void setOpenDuration(long openDuration) {
+ this.openDuration = openDuration;
+ }
+
+ public long getNextDuration() {
+ return nextDuration;
+ }
+
+ public void setNextDuration(long nextDuration) {
+ this.nextDuration = nextDuration;
+ }
+
+ public long getFlushDuration() {
+ return flushDuration;
+ }
+
+ public void setFlushDuration(long flushDuration) {
+ this.flushDuration = flushDuration;
+ }
+
+ public long getFailDuration() {
+ return failDuration;
+ }
+
+ public void setFailDuration(long failDuration) {
+ this.failDuration = failDuration;
+ }
+
+ public long getCloseDuration() {
+ return closeDuration;
+ }
+
+ public void setCloseDuration(long closeDuration) {
+ this.closeDuration = closeDuration;
+ }
+
+ private void delay(long duration) throws HyracksDataException {
+ if (duration > 0) {
+ try {
+ synchronized (this) {
+ wait(duration);
+ }
+ } catch (InterruptedException e) {
+ throw new HyracksDataException(e);
+ }
+ }
+ }
+}
diff --git a/hyracks-fullstack/hyracks/hyracks-storage-am-btree/pom.xml b/hyracks-fullstack/hyracks/hyracks-storage-am-btree/pom.xml
index 581fde4..d07d633 100644
--- a/hyracks-fullstack/hyracks/hyracks-storage-am-btree/pom.xml
+++ b/hyracks-fullstack/hyracks/hyracks-storage-am-btree/pom.xml
@@ -39,6 +39,13 @@
<dependencies>
<dependency>
<groupId>org.apache.hyracks</groupId>
+ <artifactId>hyracks-api</artifactId>
+ <version>0.2.18-SNAPSHOT</version>
+ <type>test-jar</type>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.hyracks</groupId>
<artifactId>hyracks-storage-common</artifactId>
<version>0.2.18-SNAPSHOT</version>
<type>jar</type>
diff --git a/hyracks-fullstack/hyracks/hyracks-storage-am-btree/src/test/java/org/apache/hyracks/storage/am/btree/test/FramewriterTest.java b/hyracks-fullstack/hyracks/hyracks-storage-am-btree/src/test/java/org/apache/hyracks/storage/am/btree/test/FramewriterTest.java
index df3a211..d3e7a3a 100644
--- a/hyracks-fullstack/hyracks/hyracks-storage-am-btree/src/test/java/org/apache/hyracks/storage/am/btree/test/FramewriterTest.java
+++ b/hyracks-fullstack/hyracks/hyracks-storage-am-btree/src/test/java/org/apache/hyracks/storage/am/btree/test/FramewriterTest.java
@@ -30,6 +30,9 @@
import org.apache.hyracks.api.dataflow.value.ISerializerDeserializer;
import org.apache.hyracks.api.dataflow.value.RecordDescriptor;
import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.api.test.CountAndThrowError;
+import org.apache.hyracks.api.test.CountAndThrowException;
+import org.apache.hyracks.api.test.CountAnswer;
import org.apache.hyracks.dataflow.common.comm.io.ArrayTupleBuilder;
import org.apache.hyracks.dataflow.common.comm.io.FrameTupleAccessor;
import org.apache.hyracks.dataflow.common.comm.io.FrameTupleAppender;
@@ -92,8 +95,8 @@
public boolean validate(boolean finished) {
// get number of open calls
int openCount = openException.getCallCount() + openNormal.getCallCount() + openError.getCallCount();
- int nextFrameCount = nextFrameException.getCallCount() + nextFrameNormal.getCallCount()
- + nextFrameError.getCallCount();
+ int nextFrameCount =
+ nextFrameException.getCallCount() + nextFrameNormal.getCallCount() + nextFrameError.getCallCount();
int failCount = failException.getCallCount() + failNormal.getCallCount() + failError.getCallCount();
int closeCount = closeException.getCallCount() + closeNormal.getCallCount() + closeError.getCallCount();
@@ -422,8 +425,9 @@
public AbstractTreeIndexOperatorDescriptor[] mockIndexOpDesc() throws HyracksDataException, IndexException {
IIndexDataflowHelperFactory[] indexDataflowHelperFactories = mockIndexHelperFactories();
ISearchOperationCallbackFactory[] searchOpCallbackFactories = mockSearchOpCallbackFactories();
- AbstractTreeIndexOperatorDescriptor[] opDescs = new AbstractTreeIndexOperatorDescriptor[indexDataflowHelperFactories.length
- * searchOpCallbackFactories.length];
+ AbstractTreeIndexOperatorDescriptor[] opDescs =
+ new AbstractTreeIndexOperatorDescriptor[indexDataflowHelperFactories.length
+ * searchOpCallbackFactories.length];
int k = 0;
for (int i = 0; i < indexDataflowHelperFactories.length; i++) {
for (int j = 0; j < searchOpCallbackFactories.length; j++) {
@@ -452,52 +456,6 @@
return opCallback;
}
- public class CountAnswer implements Answer<Object> {
- protected int count = 0;
-
- @Override
- public Object answer(InvocationOnMock invocation) throws Throwable {
- count++;
- return null;
- }
-
- public int getCallCount() {
- return count;
- }
-
- public void reset() {
- count = 0;
- }
- }
-
- public class CountAndThrowException extends CountAnswer {
- private String errorMessage;
-
- public CountAndThrowException(String errorMessage) {
- this.errorMessage = errorMessage;
- }
-
- @Override
- public Object answer(InvocationOnMock invocation) throws Throwable {
- count++;
- throw new HyracksDataException(errorMessage);
- }
- }
-
- public class CountAndThrowError extends CountAnswer {
- private String errorMessage;
-
- public CountAndThrowError(String errorMessage) {
- this.errorMessage = errorMessage;
- }
-
- @Override
- public Object answer(InvocationOnMock invocation) throws Throwable {
- count++;
- throw new UnknownError(errorMessage);
- }
- }
-
public IFrameWriter[] createOutputWriters() throws Exception {
CountAnswer[] opens = new CountAnswer[] { openNormal, openException, openError };
CountAnswer[] nextFrames = new CountAnswer[] { nextFrameNormal, nextFrameException, nextFrameError };