|  | // Copyright 2015 The Bazel Authors. All rights reserved. | 
|  | // | 
|  | // Licensed 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 com.google.devtools.build.lib.remote; | 
|  |  | 
|  | import static com.google.common.truth.Truth.assertThat; | 
|  | import static junit.framework.TestCase.fail; | 
|  |  | 
|  | import build.bazel.remote.execution.v2.Digest; | 
|  | import com.google.devtools.build.lib.remote.Chunker.Chunk; | 
|  | import com.google.devtools.build.lib.remote.util.DigestUtil; | 
|  | import com.google.devtools.build.lib.vfs.DigestHashFunction; | 
|  | import com.google.protobuf.ByteString; | 
|  | import java.io.ByteArrayInputStream; | 
|  | import java.io.ByteArrayOutputStream; | 
|  | import java.io.IOException; | 
|  | import java.io.InputStream; | 
|  | import java.util.NoSuchElementException; | 
|  | import java.util.Random; | 
|  | import java.util.concurrent.atomic.AtomicReference; | 
|  | import java.util.function.Supplier; | 
|  | import org.junit.Test; | 
|  | import org.junit.runner.RunWith; | 
|  | import org.junit.runners.JUnit4; | 
|  | import org.mockito.Mockito; | 
|  |  | 
|  | /** Tests for {@link Chunker}. */ | 
|  | @RunWith(JUnit4.class) | 
|  | public class ChunkerTest { | 
|  |  | 
|  | private final DigestUtil digestUtil = new DigestUtil(DigestHashFunction.SHA256); | 
|  |  | 
|  | @Test | 
|  | public void chunkingShouldWork() throws IOException { | 
|  | Random rand = new Random(); | 
|  | byte[] expectedData = new byte[21]; | 
|  | rand.nextBytes(expectedData); | 
|  | Digest expectedDigest = digestUtil.compute(expectedData); | 
|  |  | 
|  | Chunker chunker = Chunker.builder(digestUtil).setInput(expectedData).setChunkSize(10).build(); | 
|  |  | 
|  | ByteArrayOutputStream actualData = new ByteArrayOutputStream(); | 
|  |  | 
|  | assertThat(chunker.hasNext()).isTrue(); | 
|  | Chunk next = chunker.next(); | 
|  | assertThat(next.getOffset()).isEqualTo(0); | 
|  | assertThat(next.getData()).hasSize(10); | 
|  | assertThat(next.getDigest()).isEqualTo(expectedDigest); | 
|  | next.getData().writeTo(actualData); | 
|  |  | 
|  | assertThat(chunker.hasNext()).isTrue(); | 
|  | next = chunker.next(); | 
|  | assertThat(next.getOffset()).isEqualTo(10); | 
|  | assertThat(next.getData()).hasSize(10); | 
|  | assertThat(next.getDigest()).isEqualTo(expectedDigest); | 
|  | next.getData().writeTo(actualData); | 
|  |  | 
|  | assertThat(chunker.hasNext()).isTrue(); | 
|  | next = chunker.next(); | 
|  | assertThat(next.getOffset()).isEqualTo(20); | 
|  | assertThat(next.getData()).hasSize(1); | 
|  | assertThat(next.getDigest()).isEqualTo(expectedDigest); | 
|  | next.getData().writeTo(actualData); | 
|  |  | 
|  | assertThat(chunker.hasNext()).isFalse(); | 
|  |  | 
|  | assertThat(actualData.toByteArray()).isEqualTo(expectedData); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void nextShouldThrowIfNoMoreData() throws IOException { | 
|  | byte[] data = new byte[10]; | 
|  | Chunker chunker = Chunker.builder(digestUtil).setInput(data).setChunkSize(10).build(); | 
|  |  | 
|  | assertThat(chunker.hasNext()).isTrue(); | 
|  | assertThat(chunker.next()).isNotNull(); | 
|  |  | 
|  | assertThat(chunker.hasNext()).isFalse(); | 
|  |  | 
|  | try { | 
|  | chunker.next(); | 
|  | fail("Should have thrown an exception"); | 
|  | } catch (NoSuchElementException expected) { | 
|  | // Intentionally left empty. | 
|  | } | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void emptyData() throws Exception { | 
|  | byte[] data = new byte[0]; | 
|  | Chunker chunker = Chunker.builder(digestUtil).setInput(data).build(); | 
|  |  | 
|  | assertThat(chunker.hasNext()).isTrue(); | 
|  |  | 
|  | Chunk next = chunker.next(); | 
|  |  | 
|  | assertThat(next).isNotNull(); | 
|  | assertThat(next.getData()).isEmpty(); | 
|  | assertThat(next.getOffset()).isEqualTo(0); | 
|  |  | 
|  | assertThat(chunker.hasNext()).isFalse(); | 
|  |  | 
|  | try { | 
|  | chunker.next(); | 
|  | fail("Should have thrown an exception"); | 
|  | } catch (NoSuchElementException expected) { | 
|  | // Intentionally left empty. | 
|  | } | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void reset() throws Exception { | 
|  | byte[] data = new byte[]{1, 2, 3}; | 
|  | Chunker chunker = Chunker.builder(digestUtil).setInput(data).setChunkSize(1).build(); | 
|  |  | 
|  | assertNextEquals(chunker, (byte) 1); | 
|  | assertNextEquals(chunker, (byte) 2); | 
|  |  | 
|  | chunker.reset(); | 
|  |  | 
|  | assertNextEquals(chunker, (byte) 1); | 
|  | assertNextEquals(chunker, (byte) 2); | 
|  | assertNextEquals(chunker, (byte) 3); | 
|  |  | 
|  | chunker.reset(); | 
|  |  | 
|  | assertNextEquals(chunker, (byte) 1); | 
|  | } | 
|  |  | 
|  | @Test | 
|  | public void resourcesShouldBeReleased() throws IOException { | 
|  | // Test that after having consumed all data or after reset() is called (whatever happens first) | 
|  | // the underlying InputStream should be closed. | 
|  |  | 
|  | byte[] data = new byte[] {1, 2}; | 
|  | final AtomicReference<InputStream> in = new AtomicReference<>(); | 
|  | Supplier<InputStream> supplier = () -> { | 
|  | in.set(Mockito.spy(new ByteArrayInputStream(data))); | 
|  | return in.get(); | 
|  | }; | 
|  | Digest digest = digestUtil.compute(data); | 
|  |  | 
|  | Chunker chunker = new Chunker(supplier, digest, 1, digestUtil); | 
|  | assertThat(in.get()).isNull(); | 
|  | assertNextEquals(chunker, (byte) 1); | 
|  | Mockito.verify(in.get(), Mockito.never()).close(); | 
|  | assertNextEquals(chunker, (byte) 2); | 
|  | Mockito.verify(in.get()).close(); | 
|  |  | 
|  | chunker.reset(); | 
|  | chunker.next(); | 
|  | chunker.reset(); | 
|  | Mockito.verify(in.get()).close(); | 
|  | } | 
|  |  | 
|  | private void assertNextEquals(Chunker chunker, byte... data) throws IOException { | 
|  | assertThat(chunker.hasNext()).isTrue(); | 
|  | ByteString next = chunker.next().getData(); | 
|  | assertThat(next.toByteArray()).isEqualTo(data); | 
|  | } | 
|  | } |