From 88088251732e36f72e3edf806a5561b569e62c19 Mon Sep 17 00:00:00 2001 From: Magnus Klaar Date: Sat, 24 Dec 2011 00:58:03 +0100 Subject: Add Autobahn test suite for websockets We're using the existing test suite for websocket servers from the Autobahn project to verify that out websockets implementation is sane. A CT test suite and python module wrapping the test suite has been added. The test suite is run when the 'make inttests' target is executed. --- test/autobahn_SUITE.erl | 97 ++++++++++++++++++++++++++++++++++++++++ test/autobahn_SUITE_data/test.py | 76 +++++++++++++++++++++++++++++++ test/websocket_echo_handler.erl | 34 ++++++++++++++ 3 files changed, 207 insertions(+) create mode 100644 test/autobahn_SUITE.erl create mode 100755 test/autobahn_SUITE_data/test.py create mode 100644 test/websocket_echo_handler.erl (limited to 'test') diff --git a/test/autobahn_SUITE.erl b/test/autobahn_SUITE.erl new file mode 100644 index 0000000..85647d3 --- /dev/null +++ b/test/autobahn_SUITE.erl @@ -0,0 +1,97 @@ +%% Copyright (c) 2011, Magnus Klaar +%% +%% Permission to use, copy, modify, and/or distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-module(autobahn_SUITE). + +%% This CT suite reuses the websocket server test suite from the Autobahn +%% project. The Autobahn project is a websocket implementation for Python. +%% Given that we don't expect to find the packages and tools to properly +%% set up and run such a test on a system used primarily for Erlang devlopment +%% this test suite is not included in the default 'ct' target in the makefile. + +-include_lib("common_test/include/ct.hrl"). + +-export([all/0, groups/0, init_per_suite/1, end_per_suite/1, + init_per_group/2, end_per_group/2]). %% ct. +-export([run_tests/1]). %% autobahn. + +%% ct. + +all() -> + [{group, autobahn}]. + +groups() -> + BaseTests = [run_tests], + [{autobahn, [], BaseTests}]. + +init_per_suite(Config) -> + application:start(inets), + application:start(cowboy), + %% /tmp must be used as the parent directory for the virtualenv because + %% the directory names used in CT are so long that the interpreter path + %% in the scripts generated by virtualenv get so long that the system + %% refuses to execute them. + EnvPath = "/tmp/cowboy_autobahn_env", + os:putenv("AB_TESTS_ENV", EnvPath), + os:putenv("AB_TESTS_PRIV", ?config(priv_dir, Config)), + BinPath = filename:join(?config(data_dir, Config), "test.py"), + Stdout = os:cmd(BinPath ++ " setup"), + ct:log("~s~n", [Stdout]), + case string:str(Stdout, "AB-TESTS-SETUP-OK") of + 0 -> erlang:error(failed); + _ -> [{env_path, EnvPath},{bin_path,BinPath}|Config] + end. + +end_per_suite(_Config) -> + os:cmd("deactivate"), + application:stop(cowboy), + application:stop(inets), + ok. + +init_per_group(autobahn, Config) -> + Port = 33080, + cowboy:start_listener(autobahn, 100, + cowboy_tcp_transport, [{port, Port}], + cowboy_http_protocol, [{dispatch, init_dispatch()}] + ), + [{port, Port}|Config]. + +end_per_group(Listener, _Config) -> + cowboy:stop_listener(Listener), + ok. + +%% Dispatch configuration. + +init_dispatch() -> + [{[<<"localhost">>], [ + {[<<"echo">>], websocket_echo_handler, []}]}]. + +%% autobahn cases + +run_tests(Config) -> + PrivDir = ?config(priv_dir, Config), + IndexFile = filename:join([PrivDir, "reports", "servers", "index.html"]), + ct:log("

Full Test Results Report

~n", [IndexFile]), + BinPath = ?config(bin_path, Config), + Stdout = os:cmd(BinPath ++ " test"), + ct:log("~s~n", [Stdout]), + case string:str(Stdout, "AB-TESTS-TEST-OK") of + 0 -> erlang:error(failed); + _ -> ok + end, + {ok, IndexHTML} = file:read_file(IndexFile), + case binary:match(IndexHTML, <<"Fail">>) of + {_, _} -> erlang:error(failed); + nomatch -> ok + end. diff --git a/test/autobahn_SUITE_data/test.py b/test/autobahn_SUITE_data/test.py new file mode 100755 index 0000000..422cb41 --- /dev/null +++ b/test/autobahn_SUITE_data/test.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python +import os +import os.path +import sys +import subprocess + + +AB_TESTS_ENV = os.getenv("AB_TESTS_ENV") +AB_TESTS_PRIV = os.getenv("AB_TESTS_PRIV") + +VIRTUALENV_URL = 'https://raw.github.com/pypa/virtualenv/master/virtualenv.py' +VIRTUALENV_BIN = os.path.join(AB_TESTS_ENV, "virtualenv.py") +PIP_BIN = os.path.join(AB_TESTS_ENV, "bin", "pip") + + +def activate_env(env): + """ + See 'Using Virtualenv without bin/python' at http://www.virtualenv.org + """ + activate_this = os.path.join(env, 'bin', 'activate_this.py') + exec(compile(open(activate_this).read(), activate_this, 'exec'), + dict(__file__=activate_this)) + +def install_env(env): + """ + Install a new virtualenv at a path and also install the Autobahn package. + """ + os.makedirs(env) if not os.path.isdir(env) else None + subprocess.check_call(["curl", "-sS", VIRTUALENV_URL, "-o", VIRTUALENV_BIN]) + subprocess.check_call(["python", VIRTUALENV_BIN, env]) + activate_env(env) + subprocess.check_call([PIP_BIN, "install", "Autobahn"]) + +def client_config(): + """ + See comment on SUPPORTED_SPEC_VERSIONS in Autobahn/.../websocket.py + """ + base = { + 'options': {'failByDrop': False}, + 'enable-ssl': False, + 'servers': [{ + 'agent': 'Cowboy/10', + 'url': 'ws://localhost:33080/echo', + 'options': {'version': 10}}, # hybi-10 + {'agent': 'Cowboy/18', + 'url': 'ws://localhost:33080/echo', + 'options': {'version': 18}} # RFC6455 + ], + 'cases': ['*'], + 'exclude-cases': [] } + return base + +def run_test(env, config): + activate_env(env) + from twisted.python import log + from twisted.internet import reactor + from autobahn.fuzzing import FuzzingClientFactory + os.chdir(AB_TESTS_PRIV) + log.startLogging(sys.stdout) + fuzzer = FuzzingClientFactory(config) + return reactor.run() + + +def main(): + cmd = sys.argv[1] + if cmd == 'setup': + install_env(AB_TESTS_ENV) + print('AB-TESTS-SETUP-OK') + elif cmd == 'test': + run_test(AB_TESTS_ENV, client_config()) + print('AB-TESTS-TEST-OK') + else: + return 1 + +if __name__ == '__main__': + main() diff --git a/test/websocket_echo_handler.erl b/test/websocket_echo_handler.erl new file mode 100644 index 0000000..b06c1e7 --- /dev/null +++ b/test/websocket_echo_handler.erl @@ -0,0 +1,34 @@ +%% Feel free to use, reuse and abuse the code in this file. + +-module(websocket_echo_handler). +-behaviour(cowboy_http_handler). +-behaviour(cowboy_http_websocket_handler). +-export([init/3, handle/2, terminate/2]). +-export([websocket_init/3, websocket_handle/3, + websocket_info/3, websocket_terminate/3]). + +init(_Any, _Req, _Opts) -> + {upgrade, protocol, cowboy_http_websocket}. + +handle(_Req, _State) -> + exit(badarg). + +terminate(_Req, _State) -> + exit(badarg). + +websocket_init(_TransportName, Req, _Opts) -> + Req2 = cowboy_http_req:compact(Req), + {ok, Req2, undefined}. + +websocket_handle({text, Data}, Req, State) -> + {reply, {text, Data}, Req, State}; +websocket_handle({binary, Data}, Req, State) -> + {reply, {binary, Data}, Req, State}; +websocket_handle(_Frame, Req, State) -> + {ok, Req, State}. + +websocket_info(_Info, Req, State) -> + {ok, Req, State}. + +websocket_terminate(_Reason, _Req, _State) -> + ok. -- cgit v1.2.3