Skip to content

Commit

Permalink
Add an integration test for WebSocket
Browse files Browse the repository at this point in the history
Summary:
**Motivation**

See if we can safely run a WebSocket test in Travis CI
Closes #11433

Differential Revision: D4342024

Pulled By: ericvicenti

fbshipit-source-id: 137fb0c39ed7ea3726e2778d5c0bdac4cef6ab89
  • Loading branch information
douglowder authored and facebook-github-bot committed Jan 16, 2017
1 parent e0c3d56 commit a531efe
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 1 deletion.
Expand Up @@ -18,6 +18,14 @@ - (void)test##name \
[_runner runTest:_cmd module:@#name]; \
}

#define RCT_TEST_ONLY_WITH_PACKAGER(name) \
- (void)test##name \
{ \
if (getenv("CI_USE_PACKAGER")) { \
[_runner runTest:_cmd module:@#name]; \
} \
}

@interface UIExplorerIntegrationTests : XCTestCase

@end
Expand Down Expand Up @@ -63,6 +71,7 @@ - (void)testTheTester_ExpectError
//RCT_TEST(LayoutEventsTest) // Disabled due to flakiness: #8686784
RCT_TEST(SimpleSnapshotTest)
RCT_TEST(PromiseTest)
RCT_TEST_ONLY_WITH_PACKAGER(WebSocketTest)


@end
1 change: 1 addition & 0 deletions IntegrationTests/IntegrationTestsApp.js
Expand Up @@ -31,6 +31,7 @@ var TESTS = [
require('./SimpleSnapshotTest'),
require('./ImageSnapshotTest'),
require('./PromiseTest'),
require('./WebSocketTest'),
];

TESTS.forEach(
Expand Down
167 changes: 167 additions & 0 deletions IntegrationTests/WebSocketTest.js
@@ -0,0 +1,167 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/
'use strict';

var React = require('react');
var ReactNative = require('react-native');
var { View } = ReactNative;
var { TestModule } = ReactNative.NativeModules;

const DEFAULT_WS_URL = 'ws://localhost:5555/';

const WS_EVENTS = [
'close',
'error',
'message',
'open',
];
const WS_STATES = [
/* 0 */ 'CONNECTING',
/* 1 */ 'OPEN',
/* 2 */ 'CLOSING',
/* 3 */ 'CLOSED',
];

type State = {
url: string;
fetchStatus: ?string;
socket: ?WebSocket;
socketState: ?number;
lastSocketEvent: ?string;
lastMessage: ?string | ?ArrayBuffer;
testMessage: string;
testExpectedResponse: string;
};

class WebSocketTest extends React.Component {
state: State = {
url: DEFAULT_WS_URL,
fetchStatus: null,
socket: null,
socketState: null,
lastSocketEvent: null,
lastMessage: null,
testMessage: 'testMessage',
testExpectedResponse: 'testMessage_response'
};

_waitFor = (condition: any, timeout: any, callback: any) => {
var remaining = timeout;
var t;
var timeoutFunction = function() {
if (condition()) {
callback(true);
return;
}
remaining--;
if (remaining === 0) {
callback(false);
} else {
t = setTimeout(timeoutFunction,1000);
}
};
t = setTimeout(timeoutFunction,1000);
}

_connect = () => {
const socket = new WebSocket(this.state.url);
WS_EVENTS.forEach(ev => socket.addEventListener(ev, this._onSocketEvent));
this.setState({
socket,
socketState: socket.readyState,
});
};

_socketIsConnected = () => {
return this.state.socketState === 1; //'OPEN'
}

_socketIsDisconnected = () => {
return this.state.socketState === 3; //'CLOSED'
}

_disconnect = () => {
if (!this.state.socket) {
return;
}
this.state.socket.close();
};

_onSocketEvent = (event: any) => {
const state: any = {
socketState: event.target.readyState,
lastSocketEvent: event.type,
};
if (event.type === 'message') {
state.lastMessage = event.data;
}
this.setState(state);
};

_sendText = (text: string) => {
if (!this.state.socket) {
return;
}
this.state.socket.send(text);
};

_sendTestMessage = () => {
this._sendText(this.state.testMessage);
};

_receivedTestExpectedResponse = () => {
return (this.state.lastMessage === this.state.testExpectedResponse);
};

componentDidMount() {
this.testConnect();
}

testConnect = () => {
var component = this;
component._connect();
component._waitFor(component._socketIsConnected, 5, function(connectSucceeded) {
if (!connectSucceeded) {
TestModule.markTestPassed(false);
return;
}
component.testSendAndReceive();
});
}

testSendAndReceive = () => {
var component = this;
component._sendTestMessage();
component._waitFor(component._receivedTestExpectedResponse, 5, function(messageReceived) {
if (!messageReceived) {
TestModule.markTestPassed(false);
return;
}
component.testDisconnect();
});
}

testDisconnect = () => {
var component = this;
component._disconnect();
component._waitFor(component._socketIsDisconnected, 5, function(disconnectSucceeded) {
TestModule.markTestPassed(disconnectSucceeded);
});
}

render(): React.Element<any> {
return <View />;
}
}

WebSocketTest.displayName = 'WebSocketTest';

module.exports = WebSocketTest;
20 changes: 20 additions & 0 deletions IntegrationTests/launchWebSocketServer.command
@@ -0,0 +1,20 @@
#!/usr/bin/env bash

# Copyright (c) 2015-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.

# Set terminal title
echo -en "\033]0;Web Socket Test Server\a"
clear

THIS_DIR=$(dirname "$0")
pushd "$THIS_DIR"
./websocket_integration_test_server.js
popd

echo "Process terminated. Press <enter> to close the window"
read
52 changes: 52 additions & 0 deletions IntegrationTests/websocket_integration_test_server.js
@@ -0,0 +1,52 @@
#!/usr/bin/env node

/**
* Copyright (c) 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* The examples provided by Facebook are for non-commercial testing and
* evaluation purposes only.
*
* Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*
* @flow
*/
'use strict';

/* eslint-env node */

const WebSocket = require('ws');

console.log(`\
WebSocket integration test server
This will send each incoming message back, with the string '_response' appended.
An incoming message of 'exit' will shut down the server.
`);

const server = new WebSocket.Server({port: 5555});
server.on('connection', (ws) => {
ws.on('message', (message) => {
console.log('Received message:', message);
if (message === 'exit') {
console.log('WebSocket integration test server exit');
process.exit(0);
}
console.log('Cookie:', ws.upgradeReq.headers.cookie);
ws.send(message + '_response');
});

ws.send('hello');
});
5 changes: 4 additions & 1 deletion scripts/objc-test.sh
Expand Up @@ -4,6 +4,7 @@

# Start the packager and preload the UIExplorerApp bundle for better performance in integration tests
open "./packager/launchPackager.command" || echo "Can't start packager automatically"
open "./IntegrationTests/launchWebSocketServer.command" || echo "Can't start web socket server automatically"
sleep 20
curl 'http://localhost:8081/Examples/UIExplorer/js/UIExplorerApp.ios.bundle?platform=ios&dev=true' -o temp.bundle
rm temp.bundle
Expand All @@ -23,8 +24,10 @@ function cleanup {
WATCHMAN_LOGS=/usr/local/Cellar/watchman/3.1/var/run/watchman/$USER.log
[ -f $WATCHMAN_LOGS ] && cat $WATCHMAN_LOGS
fi
# kill whatever is occupying port 8081
# kill whatever is occupying port 8081 (packager)
lsof -i tcp:8081 | awk 'NR!=1 {print $2}' | xargs kill
# kill whatever is occupying port 5555 (web socket server)
lsof -i tcp:5555 | awk 'NR!=1 {print $2}' | xargs kill
}
trap cleanup EXIT

Expand Down

0 comments on commit a531efe

Please sign in to comment.