blob: 025bfa90598ee069056fc9486e2a2ab0deb87222 [file] [log] [blame]
package com.android.clockwork.bluetooth;
import static org.mockito.Mockito.verify;
import android.os.ParcelFileDescriptor;
import android.system.OsConstants;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
import org.robolectric.shadow.api.Shadow;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
* Test for {@link BluetoothSocketMonitor}
*/
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowParcelFileDescriptor.class, ShadowOs.class})
public class BluetoothSocketMonitorTest {
// Specifies a timeout to fail early in case tests get stuck while doing IO.
@Rule public Timeout testTimeout = Timeout.seconds(60);
@Mock
BluetoothSocketMonitor.Listener mMockListener;
private BluetoothSocketMonitor mSocketMonitor;
/**
* Used to simulate data written and read by the bluetoothd process.
*/
private ParcelFileDescriptor mBluetoothRemoteFd;
/**
* Used to simulate data written and read by the sysproxy process.
*/
private ParcelFileDescriptor mSysproxyRemoteFd;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mSocketMonitor = new BluetoothSocketMonitor(mMockListener);
ParcelFileDescriptor[] fds = ParcelFileDescriptor.createSocketPair(
OsConstants.SOCK_SEQPACKET);
ParcelFileDescriptor bluetoothLocalFd = fds[0];
mBluetoothRemoteFd = fds[1];
mSysproxyRemoteFd = mSocketMonitor.start(bluetoothLocalFd);
}
@After
public void tearDown() throws Exception {
// To ensure tests don't forget to close their file descriptors.
blockUntilClosed(mBluetoothRemoteFd);
blockUntilClosed(mSysproxyRemoteFd);
}
@Test
public void testForwardsDataWrittenOnBluetoothFdToSysproxyFd() throws Exception {
// Simulates Bluetooth process writing data.
writeString(mBluetoothRemoteFd, "test");
writeString(mBluetoothRemoteFd, "");
writeString(mBluetoothRemoteFd, "_data");
String expectedData = "test_data";
Assert.assertEquals(readAsString(mSysproxyRemoteFd, expectedData.length()), expectedData);
expectedData = "again";
writeString(mBluetoothRemoteFd, expectedData);
Assert.assertEquals(readAsString(mSysproxyRemoteFd, expectedData.length()), expectedData);
mBluetoothRemoteFd.close();
}
@Test
public void testForwardsDataWrittenOnSysproxyFdToBluetoothFd() throws Exception {
// Simulates sysproxy process writing data.
writeString(mSysproxyRemoteFd, "test");
writeString(mSysproxyRemoteFd, "");
writeString(mSysproxyRemoteFd, "_data");
String expectedData = "test_data";
Assert.assertEquals(readAsString(mBluetoothRemoteFd, expectedData.length()), expectedData);
expectedData = "again";
writeString(mSysproxyRemoteFd, expectedData);
Assert.assertEquals(readAsString(mBluetoothRemoteFd, expectedData.length()), expectedData);
mBluetoothRemoteFd.close();
}
@Test
public void testForwardsDataSimultaneouslyOnBothDirections() throws Exception {
String request = "tcp_request1";
String response = "tcp_response1";
writeString(mSysproxyRemoteFd, request);
writeString(mBluetoothRemoteFd, response);
Assert.assertEquals(readAsString(mSysproxyRemoteFd, response.length()), response);
Assert.assertEquals(readAsString(mBluetoothRemoteFd, request.length()), request);
request = "tcp_request2";
response = "tcp_response2";
writeString(mSysproxyRemoteFd, request);
writeString(mBluetoothRemoteFd, response);
Assert.assertEquals(readAsString(mSysproxyRemoteFd, response.length()), response);
Assert.assertEquals(readAsString(mBluetoothRemoteFd, request.length()), request);
mBluetoothRemoteFd.close();
}
@Test
public void testNotifiesListenerWhenSysproxySendsDataToBluetooth() throws Exception {
writeString(mSysproxyRemoteFd, "test");
// Waits until part of the written data makes it to the Bluetooth descriptor.
readAsString(mBluetoothRemoteFd, 1);
verify(mMockListener).onBluetoothData();
mBluetoothRemoteFd.close();
}
@Test
public void testNotifiesListenerWhenSysproxyReceivesDataOnBluetooth() throws Exception {
writeString(mBluetoothRemoteFd, "test");
// Waits until part of the written data makes it to the sysproxy descriptor.
readAsString(mSysproxyRemoteFd, 1);
verify(mMockListener).onBluetoothData();
mBluetoothRemoteFd.close();
}
@Test
public void testClosesSysproxyFdWhenBluetoothFdIsClosed() throws Exception {
writeString(mBluetoothRemoteFd, "test_data");
mBluetoothRemoteFd.close();
blockUntilClosed(mSysproxyRemoteFd);
}
@Test
public void testClosesBluetoothFdWhenSysproxyFdIsClosed() throws Exception {
writeString(mSysproxyRemoteFd, "test_data");
mSysproxyRemoteFd.close();
blockUntilClosed(mBluetoothRemoteFd);
}
/**
* Writes the given string to the file descriptor using UTF-8 encdoing.
*/
private void writeString(ParcelFileDescriptor fd, String value) throws IOException {
ShadowParcelFileDescriptor shadowFd = Shadow.extract(fd);
shadowFd.getOutputStream().write(value.getBytes(StandardCharsets.UTF_8));
}
/**
* Returns the content of the given input stream as a string assuming UTF-8 encoding.
*
* <p>Blocks until either EOF is reached or {@code maxBytes} is read.
*/
private String readAsString(ParcelFileDescriptor fd, int maxBytes) throws IOException {
ShadowParcelFileDescriptor shadowFd = Shadow.extract(fd);
return new String(readFully(shadowFd.getInputStream(), maxBytes), StandardCharsets.UTF_8);
}
/**
* Blocks until the given file descriptor is closed.
*/
private void blockUntilClosed(ParcelFileDescriptor fd) throws IOException {
try {
// The tests never write 1000 bytes so this only finishes blocking if descriptor is
// closed.
readAsString(fd, 1000);
} catch (IOException e) {
// Probably already closed. Nothing to do.
}
}
/**
* Returns the content of the given input stream as a byte array.
*
* <p>Blocks until either EOF is reached or {@code maxBytes} is read.
*/
private byte[] readFully(InputStream inputStream, int maxBytes) throws IOException {
byte[] buffer = new byte[maxBytes];
int position = 0;
while (position < buffer.length) {
int readCount = inputStream.read(buffer, position, buffer.length - position);
if (readCount <= 0) {
break;
}
position += readCount;
}
return Arrays.copyOfRange(buffer, 0, position);
}
}