blob: ee18576ec0d6c16d857dc56af9402b580c1d4963 [file] [edit]
# Copyright 2013 The Emscripten Authors. All rights reserved.
# Emscripten is available under two separate licenses, the MIT license and the
# University of Illinois/NCSA Open Source License. Both these licenses can be
# found in the LICENSE file.
import os
import shutil
if __name__ == '__main__':
raise Exception('do not run this file directly; do something like: test/runner sockets_browser')
import common
from browser_common import BrowserCore
from common import create_file, read_file
from decorators import (
no_windows,
parameterized,
requires_dev_dependency,
test_file,
)
from sockets_common import (
EMTEST_SKIP_NODE_DEV_PACKAGES,
EMTEST_SKIP_PYTHON_DEV_PACKAGES,
BackgroundServerProcess,
CompiledServerHarness,
NodeJsWebSocketEchoServerProcess,
PythonTcpEchoServerProcess,
WebsockifyServerHarness,
requires_python_dev_packages,
)
from tools.utils import path_from_root
class sockets_browser(BrowserCore):
@classmethod
def setUpClass(cls):
super().setUpClass()
print()
print('Running the socket tests. Make sure the browser allows popups from localhost.')
print()
# Note: in the WebsockifyServerHarness and CompiledServerHarness tests below, explicitly use
# consecutive server listen ports, because server teardown might not occur deterministically
# (python dtor time) and is a bit racy.
# WebsockifyServerHarness uses two port numbers, x and x-1, so increment it by two.
# CompiledServerHarness only uses one. Start with 49160 & 49159 as the first server port
# addresses. If adding new tests, increment the used port addresses below.
@parameterized({
'websockify': (WebsockifyServerHarness, 49160, ['-DTEST_DGRAM=0']),
'tcp': (CompiledServerHarness, 49161, ['-DTEST_DGRAM=0']),
'udp': (CompiledServerHarness, 49162, ['-DTEST_DGRAM=1']),
# The following forces non-NULL addr and addlen parameters for the accept call
'accept_addr': (CompiledServerHarness, 49163, ['-DTEST_DGRAM=0', '-DTEST_ACCEPT_ADDR=1']),
})
def test_sockets_echo(self, harness_class, port, args):
if harness_class == WebsockifyServerHarness and common.EMTEST_LACKS_NATIVE_CLANG:
self.skipTest('requires native clang')
if harness_class == WebsockifyServerHarness and EMTEST_SKIP_PYTHON_DEV_PACKAGES:
self.skipTest('requires python websockify and EMTEST_SKIP_PYTHON_DEV_PACKAGES=1')
if harness_class == CompiledServerHarness and EMTEST_SKIP_NODE_DEV_PACKAGES:
self.skipTest('requires node ws and EMTEST_SKIP_NODE_DEV_PACKAGES=1')
with harness_class(test_file('sockets/test_sockets_echo_server.c'), args, port) as harness:
self.btest_exit('sockets/test_sockets_echo_client.c', cflags=[f'-DSOCKK={harness.listen_port}', *args])
@requires_dev_dependency('ws')
def test_sockets_echo_pthreads(self):
with CompiledServerHarness(test_file('sockets/test_sockets_echo_server.c'), [], 49161) as harness:
self.btest_exit('sockets/test_sockets_echo_client.c', cflags=['-pthread', '-sPROXY_TO_PTHREAD', f'-DSOCKK={harness.listen_port}'])
@requires_dev_dependency('ws')
def test_sdl2_sockets_echo(self):
with CompiledServerHarness('sockets/sdl2_net_server.c', ['-sUSE_SDL=2', '-sUSE_SDL_NET=2'], 49164) as harness:
self.btest_exit('sockets/sdl2_net_client.c', cflags=['-sUSE_SDL=2', '-sUSE_SDL_NET=2', f'-DSOCKK={harness.listen_port}'])
@parameterized({
'websockify': (WebsockifyServerHarness, 49166, ['-DTEST_DGRAM=0']),
'tcp': (CompiledServerHarness, 49167, ['-DTEST_DGRAM=0']),
'udp': (CompiledServerHarness, 49168, ['-DTEST_DGRAM=1']),
# The following forces non-NULL addr and addlen parameters for the accept call
'accept_addr': (CompiledServerHarness, 49169, ['-DTEST_DGRAM=0', '-DTEST_ACCEPT_ADDR=1']),
})
def test_sockets_async_echo(self, harness_class, port, args):
if harness_class == WebsockifyServerHarness and common.EMTEST_LACKS_NATIVE_CLANG:
self.skipTest('requires native clang')
if harness_class == WebsockifyServerHarness and EMTEST_SKIP_PYTHON_DEV_PACKAGES:
self.skipTest('requires python websockify and EMTEST_SKIP_PYTHON_DEV_PACKAGES=1')
if harness_class == CompiledServerHarness and EMTEST_SKIP_NODE_DEV_PACKAGES:
self.skipTest('requires node ws and EMTEST_SKIP_NODE_DEV_PACKAGES=1')
args.append('-DTEST_ASYNC=1')
with harness_class(test_file('sockets/test_sockets_echo_server.c'), args, port) as harness:
self.btest_exit('sockets/test_sockets_echo_client.c', cflags=[f'-DSOCKK={harness.listen_port}', *args])
def test_sockets_async_bad_port(self):
# Deliberately attempt a connection on a port that will fail to test the error callback and
# getsockopt
self.btest_exit('sockets/test_sockets_echo_client.c', cflags=['-DSOCKK=49169', '-DTEST_ASYNC=1'])
@parameterized({
'websockify': (WebsockifyServerHarness, 49171, ['-DTEST_DGRAM=0']),
'tcp': (CompiledServerHarness, 49172, ['-DTEST_DGRAM=0']),
'udp': (CompiledServerHarness, 49173, ['-DTEST_DGRAM=1']),
})
def test_sockets_echo_bigdata(self, harness_class, port, args):
if harness_class == WebsockifyServerHarness and common.EMTEST_LACKS_NATIVE_CLANG:
self.skipTest('requires native clang')
if harness_class == WebsockifyServerHarness and EMTEST_SKIP_PYTHON_DEV_PACKAGES:
self.skipTest('requires python websockify and EMTEST_SKIP_PYTHON_DEV_PACKAGES=1')
if harness_class == CompiledServerHarness and EMTEST_SKIP_NODE_DEV_PACKAGES:
self.skipTest('requires node ws and EMTEST_SKIP_NODE_DEV_PACKAGES=1')
sockets_include = '-I' + test_file('sockets')
# generate a large string literal to use as our message
message = ''
for i in range(256 * 256 * 2):
message += str(chr(ord('a') + (i % 26)))
# re-write the client test with this literal (it's too big to pass via command line)
src = read_file(test_file('sockets/test_sockets_echo_client.c'))
create_file('test_sockets_echo_bigdata.c', src.replace('#define MESSAGE "pingtothepong"', '#define MESSAGE "%s"' % message))
with harness_class(test_file('sockets/test_sockets_echo_server.c'), args, port) as harness:
self.btest_exit('test_sockets_echo_bigdata.c', cflags=[sockets_include, f'-DSOCKK={harness.listen_port}', *args])
@no_windows('This test is Unix-specific.')
@requires_python_dev_packages
@requires_dev_dependency('ws')
def test_sockets_partial(self):
for harness in [
WebsockifyServerHarness(test_file('sockets/test_sockets_partial_server.c'), [], 49180),
CompiledServerHarness(test_file('sockets/test_sockets_partial_server.c'), [], 49181),
]:
with harness:
self.btest_exit('sockets/test_sockets_partial_client.c', assert_returncode=165, cflags=[f'-DSOCKK={harness.listen_port}'])
@no_windows('This test is Unix-specific.')
@requires_python_dev_packages
@requires_dev_dependency('ws')
def test_sockets_select_server_down(self):
for harness in [
WebsockifyServerHarness(test_file('sockets/test_sockets_select_server_down_server.c'), [], 49190, do_server_check=False),
CompiledServerHarness(test_file('sockets/test_sockets_select_server_down_server.c'), [], 49191, do_server_check=False),
]:
with harness:
self.btest_exit('sockets/test_sockets_select_server_down_client.c', cflags=['-DSOCKK=%d' % harness.listen_port])
@no_windows('This test is Unix-specific.')
@requires_python_dev_packages
@requires_dev_dependency('ws')
def test_sockets_select_server_closes_connection_rw(self):
for harness in [
WebsockifyServerHarness(test_file('sockets/test_sockets_echo_server.c'), ['-DCLOSE_CLIENT_AFTER_ECHO'], 49200),
CompiledServerHarness(test_file('sockets/test_sockets_echo_server.c'), ['-DCLOSE_CLIENT_AFTER_ECHO'], 49201),
]:
with harness:
self.btest_exit('sockets/test_sockets_select_server_closes_connection_client_rw.c', cflags=['-DSOCKK=%d' % harness.listen_port])
@no_windows('This test uses Unix-specific build architecture.')
@requires_dev_dependency('ws')
def test_enet(self):
# this is also a good test of raw usage of emconfigure and emmake
shutil.copytree(test_file('third_party', 'enet'), 'enet')
with common.chdir('enet'):
self.run_process([common.EMCONFIGURE, './configure', '--disable-shared'])
self.run_process([common.EMMAKE, 'make'])
enet = [self.in_dir('enet', '.libs', 'libenet.a'), '-I' + self.in_dir('enet', 'include')]
with CompiledServerHarness(test_file('sockets/test_enet_server.c'), enet, 49210) as harness:
self.btest_exit('sockets/test_enet_client.c', cflags=[*enet, f'-DSOCKK={harness.listen_port}'])
# Test Emscripten WebSockets API to send and receive text and binary messages against an echo server.
# N.B. running this test requires 'npm install ws' in Emscripten root directory
# NOTE: Shared buffer is not allowed for websocket sending.
@parameterized({
'': ([],),
'shared': (['-sSHARED_MEMORY'],),
'deinitialize': (['-DTEST_EMSCRIPTEN_WEBSOCKET_DEINITIALIZE'],),
})
@requires_dev_dependency('ws')
def test_websocket_send(self, args):
with NodeJsWebSocketEchoServerProcess():
self.btest_exit('websocket/test_websocket_send.c', cflags=['-lwebsocket', '-sNO_EXIT_RUNTIME', '-sWEBSOCKET_DEBUG', *args])
def test_websocket_new(self):
self.btest_exit('websocket/test_websocket_new.c', cflags=['-lwebsocket'])
# Test that native POSIX sockets API can be used by proxying calls to an intermediate WebSockets
# -> POSIX sockets bridge server
def test_posix_proxy_sockets(self):
# Build the websocket bridge server
self.run_process(['cmake', path_from_root('tools/websocket_to_posix_proxy')])
self.run_process(['cmake', '--build', '.'])
if os.name == 'nt': # This is not quite exact, instead of "isWindows()" this should be "If CMake defaults to building with Visual Studio", but there is no good check for that, so assume Windows==VS.
proxy_server = self.in_dir('Debug', 'websocket_to_posix_proxy.exe')
else:
proxy_server = self.in_dir('websocket_to_posix_proxy')
with BackgroundServerProcess([proxy_server, '8080']):
with PythonTcpEchoServerProcess('7777'):
# Build and run the TCP echo client program with Emscripten
self.btest_exit('websocket/tcp_echo_client.c', cflags=['-lwebsocket', '-sPROXY_POSIX_SOCKETS', '-pthread', '-sPROXY_TO_PTHREAD'])
# Test that calling send() right after a socket connect() works.
@requires_dev_dependency('ws')
def test_sockets_send_while_connecting(self):
with NodeJsWebSocketEchoServerProcess():
self.btest('sockets/test_sockets_send_while_connecting.c', cflags=['-DSOCKET_DEBUG'], expected='0')
class sockets_browser64(sockets_browser):
def setUp(self):
super().setUp()
self.cflags.append('-m64')
self.require_wasm64()