-
Notifications
You must be signed in to change notification settings - Fork 81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
test: framework for testing rpc #454
Conversation
Codecov Report
@@ Coverage Diff @@
## master #454 +/- ##
============================================
+ Coverage 63.12% 65.12% +2.00%
- Complexity 610 618 +8
============================================
Files 32 32
Lines 5133 5144 +11
Branches 490 492 +2
============================================
+ Hits 3240 3350 +110
+ Misses 1729 1630 -99
Partials 164 164
Continue to review full report at Codecov.
|
|
||
// Objects for a test case: one object per one RPC mock. | ||
// A test creates as many MockData objects as many RPC call it issues. | ||
private static final Queue<MockData> TEST_MOCK_DATA = new LinkedList<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ArrayDeque instead of LinkedList
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
request.getStreamingContent().writeTo(outputStream); | ||
GZIPInputStream is = | ||
new GZIPInputStream(new ByteArrayInputStream(outputStream.toByteArray())); | ||
byte[] buffer = new byte[1000000]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't safe or correct. Use ByteStreams.toByteArray(is)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, agree. Forgot to fix this before PR. Done.
private static final StorageRpcFactory RPC_FACTORY_MOCK = | ||
mock(StorageRpcFactory.class, UNEXPECTED_CALL_ANSWER); | ||
|
||
private static class TestRequest extends LowLevelHttpRequest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make this an outer class for clarity
} | ||
}; | ||
|
||
private static class TestResponse extends LowLevelHttpResponse { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make this an outer class for clarity
|
||
// Objects for a test case: one object per one RPC mock. | ||
// A test creates as many MockData objects as many RPC call it issues. | ||
private static final Queue<MockData> TEST_MOCK_DATA = new ArrayDeque<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this static? That's likely to cause coupling between tests. This can probably be an instance field.
private HttpStorageRpc rpc; | ||
|
||
// Objects for a test case: one object per one RPC mock. | ||
// A test creates as many MockData objects as many RPC call it issues. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as it issues RPC calls
} | ||
} | ||
|
||
static class MockData { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
outer class
@Before | ||
public void setUp() throws Exception { | ||
rpc = new HttpStorageRpc(STORAGE_OPTIONS); | ||
TEST_MOCK_DATA.clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, this really should be an instance variable so you don't have to worry about clearing it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
refactored to be an instance variable
@@ -51,13 +51,122 @@ | |||
import org.mockito.invocation.InvocationOnMock; | |||
import org.mockito.stubbing.Answer; | |||
|
|||
class TestRequest extends LowLevelHttpRequest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant these should be in new files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure? These classes unlikely will be used by anyone else but HttpStorageRpcTest.
I can move them to separate files if you insist, but IMHO it will only make understanding harder.
If it is just a matter of taste, I would prefer to see them as inner classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if there's an explicit style rule but Google almost never uses non-public outer classes in the same .java file.
In general inner classes encourage violation of data encapsulation and the single responsibility principle.
And perhaps most importantly here, the sheer size of a file becomes a problem. I found this harder to read as one large class than as several smaller classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation. I'll move the classes with code to new files and, if you don't mind, keep the trivial classes (MockData and RpcRequestHolder) as inner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All inner classes moved to new files
new GZIPInputStream(new ByteArrayInputStream(outputStream.toByteArray())); | ||
return new String(ByteStreams.toByteArray(gzipInputStream), UTF_8); | ||
} catch (IOException e) { | ||
throw new Error(e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
RuntimeException, or better yet just bubble the IOException
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I selected Error because An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch. Most such errors are abnormal conditions.
IOException thrown from this try-catch block will signal about problems in the core Java libraries, not in the Google libraries under test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I replaced Error
with AssertionError
which JUnit.fail()
throws. I can't replace with fail()
because the method returns a value.
new GZIPInputStream(new ByteArrayInputStream(outputStream.toByteArray())); | ||
return new String(ByteStreams.toByteArray(gzipInputStream), UTF_8); | ||
} catch (IOException e) { | ||
throw new AssertionError("Unexpected exception", e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just bubble the IOException, no need to catch it at all
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | ||
request.getStreamingContent().writeTo(outputStream); | ||
GZIPInputStream gzipInputStream = | ||
new GZIPInputStream(new ByteArrayInputStream(outputStream.toByteArray())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since you already have a byte array use an inflater, not a stream
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind if I kept using GZIPInputStream? java.util.zip.Inflater introduces new DataFormatException the test should handle. The inflater requires a byte buffer of a certain size, but the size is unknown. And I think, I can't use the java.util.zip.Inflater from the box to decompress GZIP content:
To fully decode the gzip format with this class, you will need to process the gzip header as described in RFC 1952, use inflater to decompress the raw deflate data, compute the crc32 of the uncompressed data using that class, and then verify the crc and length (modulo 2^32) in the gzip trailer, again as specified in the RFC.
Or do you mean another inflater?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, it's weird to copy this into a byte array first. You should be able to go straight from the LowLevelHttpRequest to the decompressor, but LowLevelHttpRequest doesn't offer an input stream to read from, which is really strange. Maybe use a piped stream but that would need an extra thread.
|
||
// Objects for a test case: one object per one RPC mock. | ||
// A test creates as many MockData objects as it issues RPC calls. | ||
private Queue<MockData> testMockData; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can initialize the queue here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are you sure it's necessary? This queue is re-initialized before each test case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not necessary, just a little cleaner. When possible, it's good habit to initialize and declare in the same line to avoid unexpected failure initialize or reuse of variables
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
|
||
// Objects for a test case: one object per one RPC mock. | ||
// A test creates as many MockData objects as it issues RPC calls. | ||
private Queue<MockData> testMockData; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not necessary, just a little cleaner. When possible, it's good habit to initialize and declare in the same line to avoid unexpected failure initialize or reuse of variables
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | ||
request.getStreamingContent().writeTo(outputStream); | ||
GZIPInputStream gzipInputStream = | ||
new GZIPInputStream(new ByteArrayInputStream(outputStream.toByteArray())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, it's weird to copy this into a byte array first. You should be able to go straight from the LowLevelHttpRequest to the decompressor, but LowLevelHttpRequest doesn't offer an input stream to read from, which is really strange. Maybe use a piped stream but that would need an extra thread.
Closing for now, will consider in the future at a later time. |
The suggested approach allows us to add new Storage RPC calls covered by unit tests.
The existing RPC calls could be also covered by unit tests.
Fixes #450