0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 12:20:24 +00:00

Add rfc7230 test suite and update others to recent Gun

This is a large commit.

The rfc7230 test suite adds many tests from the RFC7230 document.

Gun has been updated quite a bit recently, which broke the Cowboy
suites. This is now fixed with this commit.

A new hook onfirstrequest has been added. It was very useful during
debugging of the test suites.

The initial process code has changed a little; more changes are
expected with the switch to maps for options.
This commit is contained in:
Loïc Hoguin 2015-05-05 19:59:37 +03:00
parent 90ae31998e
commit 228cebaf04
21 changed files with 2137 additions and 440 deletions

View file

@ -5,7 +5,7 @@ PROJECT = cowboy
# Options. # Options.
COMPILE_FIRST = cowboy_middleware cowboy_sub_protocol COMPILE_FIRST = cowboy_middleware cowboy_sub_protocol
CT_OPTS += -pa test -ct_hooks cowboy_ct_hook [] CT_OPTS += -pa test -ct_hooks cowboy_ct_hook [] # -boot start_sasl
PLT_APPS = crypto public_key ssl PLT_APPS = crypto public_key ssl
# Dependencies. # Dependencies.
@ -21,6 +21,7 @@ dep_ct_helper = git https://github.com/extend/ct_helper.git master
include erlang.mk include erlang.mk
ERLC_OPTS += +warn_export_all +warn_missing_spec +warn_untyped_record ERLC_OPTS += +warn_export_all +warn_missing_spec +warn_untyped_record
TEST_ERLC_OPTS += +'{parse_transform, eunit_autoexport}'
# Also dialyze the tests. # Also dialyze the tests.

View file

@ -21,12 +21,12 @@ No parameterized module. No process dictionary. **Clean** Erlang code.
Sponsors Sponsors
-------- --------
The SPDY implementation was sponsored by
[LeoFS Cloud Storage](http://www.leofs.org).
The project is currently sponsored by The project is currently sponsored by
[Kato.im](https://kato.im). [Kato.im](https://kato.im).
The SPDY implementation was sponsored by
[LeoFS Cloud Storage](http://www.leofs.org).
Online documentation Online documentation
-------------------- --------------------

View file

@ -48,7 +48,7 @@ outcome of the processing.
The time the request (request line and headers) takes to be The time the request (request line and headers) takes to be
received by the server must be limited and subject to configuration. received by the server must be limited and subject to configuration.
A server must wait at least 5 seconds before dropping the connection. A server must wait at least 5 seconds before dropping the connection.
A 418 status code must be sent if the request line was received A 408 status code must be sent if the request line was received
fully when the timeout is triggered. fully when the timeout is triggered.
An HTTP/1.1 server must understand any valid HTTP/1.0 request, An HTTP/1.1 server must understand any valid HTTP/1.0 request,
@ -105,7 +105,7 @@ forms are specific to the CONNECT and site-wide OPTIONS method,
respectively. (RFC7230 5.3.2) respectively. (RFC7230 5.3.2)
The fragment part of the target URI is not sent. It must be The fragment part of the target URI is not sent. It must be
ignrored by a server receiving it. (RFC7230 5.1) ignored by a server receiving it. (RFC7230 5.1)
``` ```
request-target = origin-form / absolute-form / authority-form / asterisk-form request-target = origin-form / absolute-form / authority-form / asterisk-form
@ -362,10 +362,6 @@ version = "HTTP/1.0" / "HTTP/1.1"
Any version number other than HTTP/1.0 or HTTP/1.1 must be Any version number other than HTTP/1.0 or HTTP/1.1 must be
rejected by a server or intermediary with a 505 status code. (RFC7230 2.6, RFC7230 A.2) rejected by a server or intermediary with a 505 status code. (RFC7230 2.6, RFC7230 A.2)
A request that has whitespace different than CRLF following the
version must be rejected with a 400 status code and the closing
of the connection. (RFC7230 3.1.1)
A request that has any whitespace or characters different than A request that has any whitespace or characters different than
CRLF following the version must be rejected with a 400 status CRLF following the version must be rejected with a 400 status
code and the closing of the connection. (RFC7230 3.1.1) code and the closing of the connection. (RFC7230 3.1.1)

651
erlang.mk vendored
View file

@ -1,4 +1,4 @@
# Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu> # Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# #
# Permission to use, copy, modify, and/or distribute this software for any # Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above # purpose with or without fee is hereby granted, provided that the above
@ -28,12 +28,30 @@ V ?= 0
gen_verbose_0 = @echo " GEN " $@; gen_verbose_0 = @echo " GEN " $@;
gen_verbose = $(gen_verbose_$(V)) gen_verbose = $(gen_verbose_$(V))
# "erl" command.
ERL = erl +A0 -noinput -boot start_clean
# Core targets. # Core targets.
all:: deps app rel ifneq ($(words $(MAKECMDGOALS)),1)
.NOTPARALLEL:
endif
clean:: all:: deps
@$(MAKE) --no-print-directory app
@$(MAKE) --no-print-directory rel
# Noop to avoid a Make warning when there's nothing to do.
rel::
@echo -n
clean:: clean-crashdump
clean-crashdump:
ifneq ($(wildcard erl_crash.dump),)
$(gen_verbose) rm -f erl_crash.dump $(gen_verbose) rm -f erl_crash.dump
endif
distclean:: clean distclean:: clean
@ -42,7 +60,7 @@ help::
"erlang.mk (version $(ERLANG_MK_VERSION)) is distributed under the terms of the ISC License." \ "erlang.mk (version $(ERLANG_MK_VERSION)) is distributed under the terms of the ISC License." \
"Copyright (c) 2013-2014 Loïc Hoguin <essen@ninenines.eu>" \ "Copyright (c) 2013-2014 Loïc Hoguin <essen@ninenines.eu>" \
"" \ "" \
"Usage: [V=1] make [target]" \ "Usage: [V=1] make [-jNUM] [target]" \
"" \ "" \
"Core targets:" \ "Core targets:" \
" all Run deps, app and rel targets in that order" \ " all Run deps, app and rel targets in that order" \
@ -58,7 +76,8 @@ help::
"The target clean only removes files that are commonly removed." \ "The target clean only removes files that are commonly removed." \
"Dependencies and releases are left untouched." \ "Dependencies and releases are left untouched." \
"" \ "" \
"Setting V=1 when calling make enables verbose mode." "Setting V=1 when calling make enables verbose mode." \
"Parallel execution is supported through the -j Make flag."
# Core functions. # Core functions.
@ -68,7 +87,7 @@ define core_http_get
endef endef
else else
define core_http_get define core_http_get
erl -noshell -eval 'ssl:start(), inets:start(), case httpc:request(get, {"$(2)", []}, [{autoredirect, true}], []) of {ok, {{_, 200, _}, _, Body}} -> case file:write_file("$(1)", Body) of ok -> ok; {error, R1} -> halt(R1) end; {error, R2} -> halt(R2) end, halt(0).' $(ERL) -eval 'ssl:start(), inets:start(), case httpc:request(get, {"$(2)", []}, [{autoredirect, true}], []) of {ok, {{_, 200, _}, _, Body}} -> case file:write_file("$(1)", Body) of ok -> ok; {error, R1} -> halt(R1) end; {error, R2} -> halt(R2) end, halt(0).'
endef endef
endif endif
@ -84,13 +103,16 @@ erlang-mk:
cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk
rm -rf $(ERLANG_MK_BUILD_DIR) rm -rf $(ERLANG_MK_BUILD_DIR)
# Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu> # Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License. # This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: distclean-deps distclean-pkg pkg-list pkg-search .PHONY: distclean-deps distclean-pkg pkg-list pkg-search
# Configuration. # Configuration.
AUTOPATCH ?= edown gen_leader gproc
export AUTOPATCH
DEPS_DIR ?= $(CURDIR)/deps DEPS_DIR ?= $(CURDIR)/deps
export DEPS_DIR export DEPS_DIR
@ -128,6 +150,41 @@ distclean:: distclean-deps distclean-pkg
# Deps related targets. # Deps related targets.
define dep_autopatch
$(ERL) -eval " \
DepDir = \"$(DEPS_DIR)/$(1)/\", \
fun() -> \
{ok, Conf} = file:consult(DepDir ++ \"rebar.config\"), \
File = case lists:keyfind(deps, 1, Conf) of false -> []; {_, Deps} -> \
[begin {Method, Repo, Commit} = case Repos of \
{git, R} -> {git, R, master}; \
{M, R, {branch, C}} -> {M, R, C}; \
{M, R, {tag, C}} -> {M, R, C}; \
{M, R, C} -> {M, R, C} \
end, \
io_lib:format(\"DEPS += ~s\ndep_~s = ~s ~s ~s~n\", [Name, Name, Method, Repo, Commit]) \
end || {Name, _, Repos} <- Deps] \
end, \
ok = file:write_file(\"$(DEPS_DIR)/$(1)/Makefile\", [\"ERLC_OPTS = +debug_info\n\n\", File, \"\ninclude erlang.mk\"]) \
end(), \
AppSrcOut = \"$(DEPS_DIR)/$(1)/src/$(1).app.src\", \
AppSrcIn = case filelib:is_regular(AppSrcOut) of false -> \"$(DEPS_DIR)/$(1)/ebin/$(1).app\"; true -> AppSrcOut end, \
fun() -> \
{ok, [{application, $(1), L}]} = file:consult(AppSrcIn), \
L2 = case lists:keyfind(modules, 1, L) of {_, _} -> L; false -> [{modules, []}|L] end, \
L3 = case lists:keyfind(vsn, 1, L2) of {vsn, git} -> lists:keyreplace(vsn, 1, L2, {vsn, \"git\"}); _ -> L2 end, \
ok = file:write_file(AppSrcOut, io_lib:format(\"~p.~n\", [{application, $(1), L3}])) \
end(), \
case AppSrcOut of AppSrcIn -> ok; _ -> ok = file:delete(AppSrcIn) end, \
halt()."
endef
ifeq ($(V),0)
define dep_autopatch_verbose
@echo " PATCH " $(1);
endef
endif
define dep_fetch define dep_fetch
if [ "$$$$VS" = "git" ]; then \ if [ "$$$$VS" = "git" ]; then \
git clone -n -- $$$$REPO $(DEPS_DIR)/$(1); \ git clone -n -- $$$$REPO $(DEPS_DIR)/$(1); \
@ -135,6 +192,8 @@ define dep_fetch
elif [ "$$$$VS" = "hg" ]; then \ elif [ "$$$$VS" = "hg" ]; then \
hg clone -U $$$$REPO $(DEPS_DIR)/$(1); \ hg clone -U $$$$REPO $(DEPS_DIR)/$(1); \
cd $(DEPS_DIR)/$(1) && hg update -q $$$$COMMIT; \ cd $(DEPS_DIR)/$(1) && hg update -q $$$$COMMIT; \
elif [ "$$$$VS" = "svn" ]; then \
svn checkout $$$$REPO $(DEPS_DIR)/$(1); \
else \ else \
echo "Unknown or invalid dependency: $(1). Please consult the erlang.mk README for instructions." >&2; \ echo "Unknown or invalid dependency: $(1). Please consult the erlang.mk README for instructions." >&2; \
exit 78; \ exit 78; \
@ -157,6 +216,15 @@ else
COMMIT=$(word 3,$(dep_$(1))); \ COMMIT=$(word 3,$(dep_$(1))); \
$(call dep_fetch,$(1)) $(call dep_fetch,$(1))
endif endif
ifneq ($(filter $(1),$(AUTOPATCH)),)
$(call dep_autopatch_verbose,$(1)) if [ -f $(DEPS_DIR)/$(1)/rebar.config ]; then \
$(call dep_autopatch,$(1)); \
cd $(DEPS_DIR)/$(1)/ && ln -s ../../erlang.mk; \
elif [ ! -f $(DEPS_DIR)/$(1)/Makefile ]; then \
echo "ERLC_OPTS = +debug_info\ninclude erlang.mk" > $(DEPS_DIR)/$(1)/Makefile; \
cd $(DEPS_DIR)/$(1)/ && ln -s ../../erlang.mk; \
fi
endif
endef endef
$(foreach dep,$(DEPS),$(eval $(call dep_target,$(dep)))) $(foreach dep,$(DEPS),$(eval $(call dep_target,$(dep))))
@ -199,32 +267,48 @@ help::
" pkg-list List all known packages" \ " pkg-list List all known packages" \
" pkg-search q=STRING Search for STRING in the package index" " pkg-search q=STRING Search for STRING in the package index"
# Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu> # Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License. # This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: clean-app .PHONY: clean-app
# Configuration. # Configuration.
ERLC_OPTS ?= -Werror +debug_info +warn_export_all +warn_export_vars \ ERLC_OPTS ?= -Werror +debug_info +warn_export_vars +warn_shadow_vars \
+warn_shadow_vars +warn_obsolete_guard # +bin_opt_info +warn_missing_spec +warn_obsolete_guard # +bin_opt_info +warn_export_all +warn_missing_spec
COMPILE_FIRST ?= COMPILE_FIRST ?=
COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST))) COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST)))
ERLC_EXCLUDE ?=
ERLC_EXCLUDE_PATHS = $(addprefix src/,$(addsuffix .erl,$(ERLC_EXCLUDE)))
ERLC_MIB_OPTS ?=
COMPILE_MIB_FIRST ?=
COMPILE_MIB_FIRST_PATHS = $(addprefix mibs/,$(addsuffix .mib,$(COMPILE_MIB_FIRST)))
# Verbosity. # Verbosity.
appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src; appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src;
appsrc_verbose = $(appsrc_verbose_$(V)) appsrc_verbose = $(appsrc_verbose_$(V))
erlc_verbose_0 = @echo " ERLC " $(filter %.erl %.core,$(?F)); erlc_verbose_0 = @echo " ERLC " $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\
$(filter %.erl %.core,$(?F)));
erlc_verbose = $(erlc_verbose_$(V)) erlc_verbose = $(erlc_verbose_$(V))
xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F)); xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F));
xyrl_verbose = $(xyrl_verbose_$(V)) xyrl_verbose = $(xyrl_verbose_$(V))
# Core targets. mib_verbose_0 = @echo " MIB " $(filter %.bin %.mib,$(?F));
mib_verbose = $(mib_verbose_$(V))
app:: erlc-include ebin/$(PROJECT).app # Targets.
ifeq ($(wildcard ebin/test),)
app:: app-build
else
app:: clean app-build
endif
app-build: erlc-include ebin/$(PROJECT).app
$(eval MODULES := $(shell find ebin -type f -name \*.beam \ $(eval MODULES := $(shell find ebin -type f -name \*.beam \
| sed "s/ebin\//'/;s/\.beam/',/" | sed '$$s/.$$//')) | sed "s/ebin\//'/;s/\.beam/',/" | sed '$$s/.$$//'))
@if [ -z "$$(grep -E '^[^%]*{modules,' src/$(PROJECT).app.src)" ]; then \ @if [ -z "$$(grep -E '^[^%]*{modules,' src/$(PROJECT).app.src)" ]; then \
@ -237,9 +321,15 @@ app:: erlc-include ebin/$(PROJECT).app
| sed "s/{id,[[:space:]]*\"git\"}/{id, \"$(GITDESCRIBE)\"}/" \ | sed "s/{id,[[:space:]]*\"git\"}/{id, \"$(GITDESCRIBE)\"}/" \
> ebin/$(PROJECT).app > ebin/$(PROJECT).app
erlc-include:
-@if [ -d ebin/ ]; then \
find include/ src/ -type f -name \*.hrl -newer ebin -exec touch $(shell find src/ -type f -name "*.erl") \; 2>/dev/null || printf ''; \
fi
define compile_erl define compile_erl
$(erlc_verbose) erlc -v $(ERLC_OPTS) -o ebin/ \ $(erlc_verbose) erlc -v $(ERLC_OPTS) -o ebin/ \
-pa ebin/ -I include/ $(COMPILE_FIRST_PATHS) $(1) -pa ebin/ -I include/ $(filter-out $(ERLC_EXCLUDE_PATHS),\
$(COMPILE_FIRST_PATHS) $(1))
endef endef
define compile_xyrl define compile_xyrl
@ -248,10 +338,22 @@ define compile_xyrl
@rm ebin/*.erl @rm ebin/*.erl
endef endef
define compile_mib
$(mib_verbose) erlc -v $(ERLC_MIB_OPTS) -o priv/mibs/ \
-I priv/mibs/ $(COMPILE_MIB_FIRST_PATHS) $(1)
$(mib_verbose) erlc -o include/ -- priv/mibs/*.bin
endef
ifneq ($(wildcard src/),) ifneq ($(wildcard src/),)
ebin/$(PROJECT).app:: ebin/$(PROJECT).app::
@mkdir -p ebin/ @mkdir -p ebin/
ifneq ($(wildcard mibs/),)
ebin/$(PROJECT).app:: $(shell find mibs -type f -name \*.mib)
@mkdir -p priv/mibs/ include
$(if $(strip $?),$(call compile_mib,$?))
endif
ebin/$(PROJECT).app:: $(shell find src -type f -name \*.erl) \ ebin/$(PROJECT).app:: $(shell find src -type f -name \*.erl) \
$(shell find src -type f -name \*.core) $(shell find src -type f -name \*.core)
$(if $(strip $?),$(call compile_erl,$?)) $(if $(strip $?),$(call compile_erl,$?))
@ -263,17 +365,56 @@ endif
clean:: clean-app clean:: clean-app
# Extra targets.
erlc-include:
-@if [ -d ebin/ ]; then \
find include/ src/ -type f -name \*.hrl -newer ebin -exec touch $(shell find src/ -type f -name "*.erl") \; 2>/dev/null || printf ''; \
fi
clean-app: clean-app:
$(gen_verbose) rm -rf ebin/ $(gen_verbose) rm -rf ebin/ priv/mibs/ \
$(addprefix include/,$(addsuffix .hrl,$(notdir $(basename $(wildcard mibs/*.mib)))))
# Copyright (c) 2014, Loïc Hoguin <essen@ninenines.eu> # Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: test-deps test-dir test-build clean-test-dir
# Configuration.
TEST_DIR ?= test
ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS))
TEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard
TEST_ERLC_OPTS += -DTEST=1
# Targets.
$(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep))))
test-deps: $(ALL_TEST_DEPS_DIRS)
@for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep; done
ifneq ($(strip $(TEST_DIR)),)
test-dir:
$(gen_verbose) erlc -v $(TEST_ERLC_OPTS) -I include/ -o $(TEST_DIR) \
$(wildcard $(TEST_DIR)/*.erl $(TEST_DIR)/*/*.erl) -pa ebin/
endif
ifeq ($(wildcard ebin/test),)
test-build: ERLC_OPTS=$(TEST_ERLC_OPTS)
test-build: clean deps test-deps
@$(MAKE) --no-print-directory app-build test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)"
$(gen_verbose) touch ebin/test
else
test-build: ERLC_OPTS=$(TEST_ERLC_OPTS)
test-build: deps test-deps
@$(MAKE) --no-print-directory app-build test-dir ERLC_OPTS="$(TEST_ERLC_OPTS)"
endif
clean:: clean-test-dir
clean-test-dir:
ifneq ($(wildcard $(TEST_DIR)/*.beam),)
$(gen_verbose) rm -f $(TEST_DIR)/*.beam
endif
# Copyright (c) 2014-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License. # This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates .PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates
@ -392,6 +533,56 @@ tpl_gen_server = "-module($(n))." \
"" \ "" \
"code_change(_OldVsn, State, _Extra) ->" \ "code_change(_OldVsn, State, _Extra) ->" \
" {ok, State}." " {ok, State}."
tpl_gen_fsm = "-module($(n))." \
"-behaviour(gen_fsm)." \
"" \
"%% API." \
"-export([start_link/0])." \
"" \
"%% gen_fsm." \
"-export([init/1])." \
"-export([state_name/2])." \
"-export([handle_event/3])." \
"-export([state_name/3])." \
"-export([handle_sync_event/4])." \
"-export([handle_info/3])." \
"-export([terminate/3])." \
"-export([code_change/4])." \
"" \
"-record(state, {" \
"})." \
"" \
"%% API." \
"" \
"-spec start_link() -> {ok, pid()}." \
"start_link() ->" \
" gen_fsm:start_link(?MODULE, [], [])." \
"" \
"%% gen_fsm." \
"" \
"init([]) ->" \
" {ok, state_name, \#state{}}." \
"" \
"state_name(_Event, StateData) ->" \
" {next_state, state_name, StateData}." \
"" \
"handle_event(_Event, StateName, StateData) ->" \
" {next_state, StateName, StateData}." \
"" \
"state_name(_Event, _From, StateData) ->" \
" {reply, ignored, state_name, StateData}." \
"" \
"handle_sync_event(_Event, _From, StateName, StateData) ->" \
" {reply, ignored, StateName, StateData}." \
"" \
"handle_info(_Info, StateName, StateData) ->" \
" {next_state, StateName, StateData}." \
"" \
"terminate(_Reason, _StateName, _StateData) ->" \
" ok." \
"" \
"code_change(_OldVsn, StateName, StateData, _Extra) ->" \
" {ok, StateName, StateData}."
tpl_cowboy_http = "-module($(n))." \ tpl_cowboy_http = "-module($(n))." \
"-behaviour(cowboy_http_handler)." \ "-behaviour(cowboy_http_handler)." \
"" \ "" \
@ -551,85 +742,169 @@ endif
list-templates: list-templates:
@echo Available templates: $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES)))) @echo Available templates: $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES))))
# Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu> # Copyright (c) 2014-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License. # This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: build-ct-deps build-ct-suites tests-ct clean-ct distclean-ct .PHONY: clean-c_src distclean-c_src-env
# todo
# Configuration.
C_SRC_DIR = $(CURDIR)/c_src
C_SRC_ENV ?= $(C_SRC_DIR)/env.mk
C_SRC_OUTPUT ?= $(CURDIR)/priv/$(PROJECT).so
# System type and C compiler/flags.
UNAME_SYS := $(shell uname -s)
ifeq ($(UNAME_SYS), Darwin)
CC ?= cc
CFLAGS ?= -O3 -std=c99 -arch x86_64 -finline-functions -Wall -Wmissing-prototypes
CXXFLAGS ?= -O3 -arch x86_64 -finline-functions -Wall
LDFLAGS ?= -arch x86_64 -flat_namespace -undefined suppress
else ifeq ($(UNAME_SYS), FreeBSD)
CC ?= cc
CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
CXXFLAGS ?= -O3 -finline-functions -Wall
else ifeq ($(UNAME_SYS), Linux)
CC ?= gcc
CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
CXXFLAGS ?= -O3 -finline-functions -Wall
endif
CFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR)
CXXFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR)
LDLIBS += -L $(ERL_INTERFACE_LIB_DIR) -lerl_interface -lei
LDFLAGS += -shared
# Verbosity.
c_verbose_0 = @echo " C " $(?F);
c_verbose = $(c_verbose_$(V))
cpp_verbose_0 = @echo " CPP " $(?F);
cpp_verbose = $(cpp_verbose_$(V))
link_verbose_0 = @echo " LD " $(@F);
link_verbose = $(link_verbose_$(V))
# Targets.
ifeq ($(wildcard $(C_SRC_DIR)),)
else ifneq ($(wildcard $(C_SRC_DIR)/Makefile),)
app::
$(MAKE) -C $(C_SRC_DIR)
clean::
$(MAKE) -C $(C_SRC_DIR) clean
else
SOURCES := $(shell find $(C_SRC_DIR) -type f \( -name "*.c" -o -name "*.C" -o -name "*.cc" -o -name "*.cpp" \))
OBJECTS = $(addsuffix .o, $(basename $(SOURCES)))
COMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c
COMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c
app:: $(C_SRC_ENV) $(C_SRC_OUTPUT)
$(C_SRC_OUTPUT): $(OBJECTS)
@mkdir -p priv/
$(link_verbose) $(CC) $(OBJECTS) $(LDFLAGS) $(LDLIBS) -o $(C_SRC_OUTPUT)
%.o: %.c
$(COMPILE_C) $(OUTPUT_OPTION) $<
%.o: %.cc
$(COMPILE_CPP) $(OUTPUT_OPTION) $<
%.o: %.C
$(COMPILE_CPP) $(OUTPUT_OPTION) $<
%.o: %.cpp
$(COMPILE_CPP) $(OUTPUT_OPTION) $<
$(C_SRC_ENV):
@$(ERL) -eval "file:write_file(\"$(C_SRC_ENV)\", \
io_lib:format( \
\"ERTS_INCLUDE_DIR ?= ~s/erts-~s/include/~n\" \
\"ERL_INTERFACE_INCLUDE_DIR ?= ~s~n\" \
\"ERL_INTERFACE_LIB_DIR ?= ~s~n\", \
[code:root_dir(), erlang:system_info(version), \
code:lib_dir(erl_interface, include), \
code:lib_dir(erl_interface, lib)])), \
halt()."
clean:: clean-c_src
clean-c_src:
$(gen_verbose) rm -f $(C_SRC_OUTPUT) $(OBJECTS)
distclean:: distclean-c_src-env
distclean-c_src-env:
$(gen_verbose) rm -f $(C_SRC_ENV)
-include $(C_SRC_ENV)
endif
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: ct distclean-ct
# Configuration. # Configuration.
CT_OPTS ?= CT_OPTS ?=
ifneq ($(wildcard test/),) ifneq ($(wildcard $(TEST_DIR)),)
CT_SUITES ?= $(sort $(subst _SUITE.erl,,$(shell find test -type f -name \*_SUITE.erl -exec basename {} \;))) CT_SUITES ?= $(sort $(subst _SUITE.erl,,$(shell find $(TEST_DIR) -type f -name \*_SUITE.erl -exec basename {} \;)))
else else
CT_SUITES ?= CT_SUITES ?=
endif endif
TEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard
TEST_ERLC_OPTS += -DTEST=1 -DEXTRA=1 +'{parse_transform, eunit_autoexport}'
# Core targets. # Core targets.
tests:: tests-ct tests:: ct
clean:: clean-ct
distclean:: distclean-ct distclean:: distclean-ct
help:: help::
@printf "%s\n" "" \ @printf "%s\n" "" \
"Common_test targets:" \
" ct Run all the common_test suites for this project" \
"" \
"All your common_test suites have their associated targets." \ "All your common_test suites have their associated targets." \
"A suite named http_SUITE can be ran using the ct-http target." "A suite named http_SUITE can be ran using the ct-http target."
# Plugin-specific targets. # Plugin-specific targets.
ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS))
CT_RUN = ct_run \ CT_RUN = ct_run \
-no_auto_compile \ -no_auto_compile \
-noshell \ -noinput \
-pa $(realpath ebin) $(DEPS_DIR)/*/ebin \ -pa ebin $(DEPS_DIR)/*/ebin \
-dir test \ -dir $(TEST_DIR) \
-logdir logs -logdir logs
$(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep)))) ifeq ($(CT_SUITES),)
ct:
build-ct-deps: $(ALL_TEST_DEPS_DIRS) else
@for dep in $(ALL_TEST_DEPS_DIRS) ; do $(MAKE) -C $$dep; done ct: test-build
@mkdir -p logs/
build-ct-suites: build-ct-deps $(gen_verbose) $(CT_RUN) -suite $(addsuffix _SUITE,$(CT_SUITES)) $(CT_OPTS)
$(gen_verbose) erlc -v $(TEST_ERLC_OPTS) -I include/ -o test/ \ endif
$(wildcard test/*.erl test/*/*.erl) -pa ebin/
tests-ct: ERLC_OPTS = $(TEST_ERLC_OPTS)
tests-ct: clean deps app build-ct-suites
@if [ -d "test" ] ; \
then \
mkdir -p logs/ ; \
$(CT_RUN) -suite $(addsuffix _SUITE,$(CT_SUITES)) $(CT_OPTS) ; \
fi
$(gen_verbose) rm -f test/*.beam
define ct_suite_target define ct_suite_target
ct-$(1): ERLC_OPTS = $(TEST_ERLC_OPTS) ct-$(1): test-build
ct-$(1): clean deps app build-ct-suites @mkdir -p logs/
@if [ -d "test" ] ; \ $(gen_verbose) $(CT_RUN) -suite $(addsuffix _SUITE,$(1)) $(CT_OPTS)
then \
mkdir -p logs/ ; \
$(CT_RUN) -suite $(addsuffix _SUITE,$(1)) $(CT_OPTS) ; \
fi
$(gen_verbose) rm -f test/*.beam
endef endef
$(foreach test,$(CT_SUITES),$(eval $(call ct_suite_target,$(test)))) $(foreach test,$(CT_SUITES),$(eval $(call ct_suite_target,$(test))))
clean-ct:
$(gen_verbose) rm -rf test/*.beam
distclean-ct: distclean-ct:
$(gen_verbose) rm -rf logs/ $(gen_verbose) rm -rf logs/
# Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu> # Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License. # This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: plt distclean-plt dialyze .PHONY: plt distclean-plt dialyze
@ -671,9 +946,82 @@ dialyze: $(DIALYZER_PLT)
endif endif
@dialyzer --no_native $(DIALYZER_DIRS) $(DIALYZER_OPTS) @dialyzer --no_native $(DIALYZER_DIRS) $(DIALYZER_OPTS)
# Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu> # Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# Copyright (c) 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
# This file is part of erlang.mk and subject to the terms of the ISC License. # This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: distclean-edoc build-doc-deps
# Configuration.
EDOC_OPTS ?=
# Core targets.
docs:: distclean-edoc build-doc-deps
$(gen_verbose) $(ERL) -eval 'edoc:application($(PROJECT), ".", [$(EDOC_OPTS)]), halt().'
distclean:: distclean-edoc
# Plugin-specific targets.
DOC_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DOC_DEPS))
$(foreach dep,$(DOC_DEPS),$(eval $(call dep_target,$(dep))))
build-doc-deps: $(DOC_DEPS_DIRS)
@for dep in $(DOC_DEPS_DIRS) ; do $(MAKE) -C $$dep; done
distclean-edoc:
$(gen_verbose) rm -f doc/*.css doc/*.html doc/*.png doc/edoc-info
# Copyright (c) 2014, Juan Facorro <juan@inaka.net>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: elvis distclean-elvis
# Configuration.
ELVIS_CONFIG ?= $(CURDIR)/elvis.config
ELVIS ?= $(CURDIR)/elvis
export ELVIS
ELVIS_URL ?= https://github.com/inaka/elvis/releases/download/0.2.3/elvis
ELVIS_CONFIG_URL ?= https://github.com/inaka/elvis/releases/download/0.2.3/elvis.config
ELVIS_OPTS ?=
# Core targets.
help::
@printf "%s\n" "" \
"Elvis targets:" \
" elvis Run Elvis using the local elvis.config or download the default otherwise"
distclean:: distclean-elvis
# Plugin-specific targets.
$(ELVIS):
@$(call core_http_get,$(ELVIS),$(ELVIS_URL))
@chmod +x $(ELVIS)
$(ELVIS_CONFIG):
@$(call core_http_get,$(ELVIS_CONFIG),$(ELVIS_CONFIG_URL))
elvis: $(ELVIS) $(ELVIS_CONFIG)
@$(ELVIS) rock -c $(ELVIS_CONFIG) $(ELVIS_OPTS)
distclean-elvis:
$(gen_verbose) rm -rf $(ELVIS)
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
# Configuration.
DTL_FULL_PATH ?= 0
# Verbosity. # Verbosity.
dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F)); dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F));
@ -682,14 +1030,16 @@ dtl_verbose = $(dtl_verbose_$(V))
# Core targets. # Core targets.
define compile_erlydtl define compile_erlydtl
$(dtl_verbose) erl -noshell -pa ebin/ $(DEPS_DIR)/erlydtl/ebin/ -eval ' \ $(dtl_verbose) $(ERL) -pa ebin/ $(DEPS_DIR)/erlydtl/ebin/ -eval ' \
Compile = fun(F) -> \ Compile = fun(F) -> \
Module = list_to_atom( \ S = fun (1) -> re:replace(filename:rootname(string:sub_string(F, 11), ".dtl"), "/", "_", [{return, list}, global]); \
string:to_lower(filename:basename(F, ".dtl")) ++ "_dtl"), \ (0) -> filename:basename(F, ".dtl") \
erlydtl:compile(F, Module, [{out_dir, "ebin/"}]) \ end, \
Module = list_to_atom(string:to_lower(S($(DTL_FULL_PATH))) ++ "_dtl"), \
{ok, _} = erlydtl:compile(F, Module, [{out_dir, "ebin/"}, return_errors, {doc_root, "templates"}]) \
end, \ end, \
_ = [Compile(F) || F <- string:tokens("$(1)", " ")], \ _ = [Compile(F) || F <- string:tokens("$(1)", " ")], \
init:stop()' halt().'
endef endef
ifneq ($(wildcard src/),) ifneq ($(wildcard src/),)
@ -697,7 +1047,124 @@ ebin/$(PROJECT).app:: $(shell find templates -type f -name \*.dtl 2>/dev/null)
$(if $(strip $?),$(call compile_erlydtl,$?)) $(if $(strip $?),$(call compile_erlydtl,$?))
endif endif
# Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu> # Copyright (c) 2014 Dave Cottlehuber <dch@skunkwerks.at>
# This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: distclean-escript escript
# Configuration.
ESCRIPT_NAME ?= $(PROJECT)
ESCRIPT_COMMENT ?= This is an -*- erlang -*- file
ESCRIPT_BEAMS ?= "ebin/*", "deps/*/ebin/*"
ESCRIPT_SYS_CONFIG ?= "rel/sys.config"
ESCRIPT_EMU_ARGS ?= -pa . \
-sasl errlog_type error \
-escript main $(ESCRIPT_NAME)
ESCRIPT_SHEBANG ?= /usr/bin/env escript
ESCRIPT_STATIC ?= "deps/*/priv/**", "priv/**"
# Core targets.
distclean:: distclean-escript
help::
@printf "%s\n" "" \
"Escript targets:" \
" escript Build an executable escript archive" \
# Plugin-specific targets.
# Based on https://github.com/synrc/mad/blob/master/src/mad_bundle.erl
# Copyright (c) 2013 Maxim Sokhatsky, Synrc Research Center
# Modified MIT License, https://github.com/synrc/mad/blob/master/LICENSE :
# Software may only be used for the great good and the true happiness of all
# sentient beings.
define ESCRIPT_RAW
'Read = fun(F) -> {ok, B} = file:read_file(filename:absname(F)), B end,'\
'Files = fun(L) -> A = lists:concat([filelib:wildcard(X)||X<- L ]),'\
' [F || F <- A, not filelib:is_dir(F) ] end,'\
'Squash = fun(L) -> [{filename:basename(F), Read(F) } || F <- L ] end,'\
'Zip = fun(A, L) -> {ok,{_,Z}} = zip:create(A, L, [{compress,all},memory]), Z end,'\
'Ez = fun(Escript) ->'\
' Static = Files([$(ESCRIPT_STATIC)]),'\
' Beams = Squash(Files([$(ESCRIPT_BEAMS), $(ESCRIPT_SYS_CONFIG)])),'\
' Archive = Beams ++ [{ "static.gz", Zip("static.gz", Static)}],'\
' escript:create(Escript, [ $(ESCRIPT_OPTIONS)'\
' {archive, Archive, [memory]},'\
' {shebang, "$(ESCRIPT_SHEBANG)"},'\
' {comment, "$(ESCRIPT_COMMENT)"},'\
' {emu_args, " $(ESCRIPT_EMU_ARGS)"}'\
' ]),'\
' file:change_mode(Escript, 8#755)'\
'end,'\
'Ez("$(ESCRIPT_NAME)"),'\
'halt().'
endef
ESCRIPT_COMMAND = $(subst ' ',,$(ESCRIPT_RAW))
escript:: distclean-escript deps app
$(gen_verbose) $(ERL) -eval $(ESCRIPT_COMMAND)
distclean-escript:
$(gen_verbose) rm -f $(ESCRIPT_NAME)
# Copyright (c) 2014, Enrique Fernandez <enrique.fernandez@erlang-solutions.com>
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is contributed to erlang.mk and subject to the terms of the ISC License.
.PHONY: eunit
# Configuration
ifeq ($(strip $(TEST_DIR)),)
TAGGED_EUNIT_TESTS = {dir,"ebin"}
else
ifeq ($(wildcard $(TEST_DIR)),)
TAGGED_EUNIT_TESTS = {dir,"ebin"}
else
# All modules in TEST_DIR
TEST_DIR_MODS = $(notdir $(basename $(shell find $(TEST_DIR) -type f -name *.beam)))
# All modules in 'ebin'
EUNIT_EBIN_MODS = $(notdir $(basename $(shell find ebin -type f -name *.beam)))
# Only those modules in TEST_DIR with no matching module in 'ebin'.
# This is done to avoid some tests being executed twice.
EUNIT_MODS = $(filter-out $(patsubst %,%_tests,$(EUNIT_EBIN_MODS)),$(TEST_DIR_MODS))
TAGGED_EUNIT_TESTS = {dir,"ebin"} $(foreach mod,$(EUNIT_MODS),$(shell echo $(mod) | sed -e 's/\(.*\)/{module,\1}/g'))
endif
endif
EUNIT_OPTS ?=
# Utility functions
define str-join
$(shell echo '$(strip $(1))' | sed -e "s/ /,/g")
endef
# Core targets.
tests:: eunit
help::
@printf "%s\n" "" \
"EUnit targets:" \
" eunit Run all the EUnit tests for this project"
# Plugin-specific targets.
EUNIT_RUN = $(ERL) \
-pa $(TEST_DIR) $(DEPS_DIR)/*/ebin \
-pz ebin \
-eval 'case eunit:test([$(call str-join,$(TAGGED_EUNIT_TESTS))], [$(EUNIT_OPTS)]) of ok -> halt(0); error -> halt(1) end.'
eunit: test-build
$(gen_verbose) $(EUNIT_RUN)
# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License. # This file is part of erlang.mk and subject to the terms of the ISC License.
.PHONY: relx-rel distclean-relx-rel distclean-relx .PHONY: relx-rel distclean-relx-rel distclean-relx
@ -709,7 +1176,7 @@ RELX_CONFIG ?= $(CURDIR)/relx.config
RELX ?= $(CURDIR)/relx RELX ?= $(CURDIR)/relx
export RELX export RELX
RELX_URL ?= https://github.com/erlware/relx/releases/download/v1.0.2/relx RELX_URL ?= https://github.com/erlware/relx/releases/download/v1.2.0/relx
RELX_OPTS ?= RELX_OPTS ?=
RELX_OUTPUT_DIR ?= _rel RELX_OUTPUT_DIR ?= _rel
@ -753,7 +1220,7 @@ distclean-relx:
# Configuration. # Configuration.
SHELL_PATH ?= -pa ../$(PROJECT)/ebin $(DEPS_DIR)/*/ebin SHELL_PATH ?= -pa $(CURDIR)/ebin $(DEPS_DIR)/*/ebin
SHELL_OPTS ?= SHELL_OPTS ?=
ALL_SHELL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(SHELL_DEPS)) ALL_SHELL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(SHELL_DEPS))
@ -774,3 +1241,35 @@ build-shell-deps: $(ALL_SHELL_DEPS_DIRS)
shell: build-shell-deps shell: build-shell-deps
$(gen_verbose) erl $(SHELL_PATH) $(SHELL_OPTS) $(gen_verbose) erl $(SHELL_PATH) $(SHELL_OPTS)
# Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
# This file is part of erlang.mk and subject to the terms of the ISC License.
ifneq ($(wildcard $(DEPS_DIR)/triq),)
.PHONY: triq
# Targets.
tests:: triq
define triq_run
$(ERL) -pa $(CURDIR)/ebin $(DEPS_DIR)/*/ebin \
-eval "try $(1) of true -> halt(0); _ -> halt(1) catch error:undef -> io:format(\"Undefined property or module~n\"), halt() end."
endef
ifdef t
ifeq (,$(findstring :,$(t)))
triq: test-build
@$(call triq_run,triq:check($(t)))
else
triq: test-build
@echo Testing $(t)/0
@$(call triq_run,triq:check($(t)()))
endif
else
triq: test-build
$(eval MODULES := $(shell find ebin -type f -name \*.beam \
| sed "s/ebin\//'/;s/\.beam/',/" | sed '$$s/.$$//'))
$(gen_verbose) $(call triq_run,[true] =:= lists:usort([triq:check(M) || M <- [$(MODULES)]]))
endif
endif

View file

@ -75,6 +75,16 @@ get_value(Key, Opts, Default) ->
-spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok. -spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok.
init(Ref, Socket, Transport, Opts) -> init(Ref, Socket, Transport, Opts) ->
ok = ranch:accept_ack(Ref),
Timeout = get_value(timeout, Opts, 5000),
Until = until(Timeout),
case recv(Socket, Transport, Until) of
{ok, Data} ->
OnFirstRequest = get_value(onfirstrequest, Opts, undefined),
case OnFirstRequest of
undefined -> ok;
_ -> OnFirstRequest(Ref, Socket, Transport, Opts)
end,
Compress = get_value(compress, Opts, false), Compress = get_value(compress, Opts, false),
MaxEmptyLines = get_value(max_empty_lines, Opts, 5), MaxEmptyLines = get_value(max_empty_lines, Opts, 5),
MaxHeaderNameLength = get_value(max_header_name_length, Opts, 64), MaxHeaderNameLength = get_value(max_header_name_length, Opts, 64),
@ -85,15 +95,16 @@ init(Ref, Socket, Transport, Opts) ->
Middlewares = get_value(middlewares, Opts, [cowboy_router, cowboy_handler]), Middlewares = get_value(middlewares, Opts, [cowboy_router, cowboy_handler]),
Env = [{listener, Ref}|get_value(env, Opts, [])], Env = [{listener, Ref}|get_value(env, Opts, [])],
OnResponse = get_value(onresponse, Opts, undefined), OnResponse = get_value(onresponse, Opts, undefined),
Timeout = get_value(timeout, Opts, 5000), parse_request(Data, #state{socket=Socket, transport=Transport,
ok = ranch:accept_ack(Ref),
wait_request(<<>>, #state{socket=Socket, transport=Transport,
middlewares=Middlewares, compress=Compress, env=Env, middlewares=Middlewares, compress=Compress, env=Env,
max_empty_lines=MaxEmptyLines, max_keepalive=MaxKeepalive, max_empty_lines=MaxEmptyLines, max_keepalive=MaxKeepalive,
max_request_line_length=MaxRequestLineLength, max_request_line_length=MaxRequestLineLength,
max_header_name_length=MaxHeaderNameLength, max_header_name_length=MaxHeaderNameLength,
max_header_value_length=MaxHeaderValueLength, max_headers=MaxHeaders, max_header_value_length=MaxHeaderValueLength, max_headers=MaxHeaders,
onresponse=OnResponse, timeout=Timeout, until=until(Timeout)}, 0). onresponse=OnResponse, timeout=Timeout, until=Until}, 0);
{error, _} ->
terminate(#state{socket=Socket, transport=Transport}) %% @todo ridiculous
end.
-spec until(timeout()) -> non_neg_integer() | infinity. -spec until(timeout()) -> non_neg_integer() | infinity.
until(infinity) -> until(infinity) ->

View file

@ -17,7 +17,7 @@
-export([init/2]). -export([init/2]).
init(_, _) -> init(_, _) ->
cowboy_test:start([cowboy, gun]), ct_helper:start([cowboy, gun]),
cowboy_test:make_certs(), ct_helper:make_certs_in_ets(),
error_logger:add_report_handler(cowboy_error_h), error_logger:add_report_handler(ct_helper_error_h),
{ok, undefined}. {ok, undefined}.

View file

@ -1,145 +0,0 @@
%% Copyright (c) 2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% 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(cowboy_error_h).
-behaviour(gen_event).
%% Public interface.
-export([ignore/3]).
%% gen_event.
-export([init/1]).
-export([handle_event/2]).
-export([handle_call/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).
%% Public interface.
%% Ignore crashes from Pid occuring in M:F/A.
ignore(M, F, A) ->
gen_event:call(error_logger, ?MODULE, {expect, {self(), M, F, A}}).
%% gen_event.
init(_) ->
spawn(fun() -> error_logger:tty(false) end),
{ok, []}.
%% Ignore supervisor and progress reports.
handle_event({info_report, _, {_, progress, _}}, State) ->
{ok, State};
handle_event({info_report, _, {_, std_info, _}}, State) ->
{ok, State};
handle_event({error_report, _, {_, supervisor_report, _}}, State) ->
{ok, State};
%% Ignore gun retry failures.
handle_event({error_report, _, {_, crash_report,
[[{initial_call, {gun, init, _}}, _, _,
{error_info, {error, gone, _}}|_]|_]}},
State) ->
{ok, State};
%% Ignore emulator reports that are a duplicate of what Ranch gives us.
%%
%% The emulator always sends strings for errors, which makes it very
%% difficult to extract the information we need, hence the regexps.
handle_event(Event = {error, GL, {emulator, _, Msg}}, State)
when node(GL) =:= node() ->
Result = re:run(Msg,
"Error in process ([^\s]+).+? with exit value: "
".+?{stacktrace,\\[{([^,]+),([^,]+),(.+)",
[{capture, all_but_first, list}]),
case Result of
nomatch ->
write_event(Event),
{ok, State};
{match, [PidStr, MStr, FStr, Rest]} ->
A = case Rest of
"[]" ++ _ ->
0;
"[" ++ Rest2 ->
count_args(Rest2, 1, 0);
_ ->
{match, [AStr]} = re:run(Rest, "([^,]+).+",
[{capture, all_but_first, list}]),
list_to_integer(AStr)
end,
Crash = {list_to_pid(PidStr), list_to_existing_atom(MStr),
list_to_existing_atom(FStr), A},
case lists:member(Crash, State) of
true ->
{ok, lists:delete(Crash, State)};
false ->
write_event(Event),
{ok, State}
end
end;
handle_event(Event = {error, GL,
{_, "Ranch listener" ++ _, [_, _, Pid, {[_, _,
{stacktrace, [{M, F, A, _}|_]}|_], _}]}},
State) when node(GL) =:= node() ->
A2 = if is_list(A) -> length(A); true -> A end,
Crash = {Pid, M, F, A2},
case lists:member(Crash, State) of
true ->
{ok, lists:delete(Crash, State)};
false ->
write_event(Event),
{ok, State}
end;
handle_event(Event = {_, GL, _}, State) when node(GL) =:= node() ->
write_event(Event),
{ok, State};
handle_event(_, State) ->
{ok, State}.
handle_call({expect, Crash}, State) ->
{ok, ok, [Crash, Crash|State]};
handle_call(_, State) ->
{ok, {error, bad_query}, State}.
handle_info(_, State) ->
{ok, State}.
terminate(_, _) ->
spawn(fun() -> error_logger:tty(true) end),
ok.
code_change(_, State, _) ->
{ok, State}.
%% Internal.
write_event(Event) ->
error_logger_tty_h:write_event(
{erlang:universaltime(), Event},
io).
count_args("]" ++ _, N, 0) ->
N;
count_args("]" ++ Tail, N, Levels) ->
count_args(Tail, N, Levels - 1);
count_args("[" ++ Tail, N, Levels) ->
count_args(Tail, N, Levels + 1);
count_args("}" ++ Tail, N, Levels) ->
count_args(Tail, N, Levels - 1);
count_args("{" ++ Tail, N, Levels) ->
count_args(Tail, N, Levels + 1);
count_args("," ++ Tail, N, Levels = 0) ->
count_args(Tail, N + 1, Levels);
count_args("," ++ Tail, N, Levels) ->
count_args(Tail, N, Levels);
count_args([_|Tail], N, Levels) ->
count_args(Tail, N, Levels).

View file

@ -15,83 +15,24 @@
-module(cowboy_test). -module(cowboy_test).
-compile(export_all). -compile(export_all).
%% Start and stop applications and their dependencies. -import(ct_helper, [config/2]).
start(Apps) ->
_ = [do_start(App) || App <- Apps],
ok.
do_start(App) ->
case application:start(App) of
ok ->
ok;
{error, {not_started, Dep}} ->
do_start(Dep),
do_start(App)
end.
%% SSL certificate creation and safekeeping.
make_certs() ->
{_, Cert, Key} = ct_helper:make_certs(),
CertOpts = [{cert, Cert}, {key, Key}],
Pid = spawn(fun() -> receive after infinity -> ok end end),
?MODULE = ets:new(?MODULE, [ordered_set, public, named_table,
{heir, Pid, undefined}]),
ets:insert(?MODULE, {cert_opts, CertOpts}),
ok.
get_certs() ->
ets:lookup_element(?MODULE, cert_opts, 2).
%% Quick configuration value retrieval.
config(Key, Config) ->
{_, Value} = lists:keyfind(Key, 1, Config),
Value.
%% Test case description.
doc(String) ->
ct:comment(String),
ct:log(String).
%% List of all test cases in the suite.
all(Suite) ->
lists:usort([F || {F, 1} <- Suite:module_info(exports),
F =/= module_info,
F =/= test, %% This is leftover from the eunit parse_transform...
F =/= all,
F =/= groups,
string:substr(atom_to_list(F), 1, 5) =/= "init_",
string:substr(atom_to_list(F), 1, 4) =/= "end_",
string:substr(atom_to_list(F), 1, 3) =/= "do_"
]).
%% Listeners initialization. %% Listeners initialization.
init_http(Ref, ProtoOpts, Config) -> init_http(Ref, ProtoOpts, Config) ->
{ok, _} = cowboy:start_http(Ref, 100, [{port, 0}], [ {ok, _} = cowboy:start_http(Ref, 100, [{port, 0}], ProtoOpts),
{max_keepalive, 50},
{timeout, 500}
|ProtoOpts]),
Port = ranch:get_port(Ref), Port = ranch:get_port(Ref),
[{type, tcp}, {port, Port}, {opts, []}|Config]. [{type, tcp}, {port, Port}, {opts, []}|Config].
init_https(Ref, ProtoOpts, Config) -> init_https(Ref, ProtoOpts, Config) ->
Opts = get_certs(), Opts = ct_helper:get_certs_from_ets(),
{ok, _} = cowboy:start_https(Ref, 100, Opts ++ [{port, 0}], [ {ok, _} = cowboy:start_https(Ref, 100, Opts ++ [{port, 0}], ProtoOpts),
{max_keepalive, 50},
{timeout, 500}
|ProtoOpts]),
Port = ranch:get_port(Ref), Port = ranch:get_port(Ref),
[{type, ssl}, {port, Port}, {opts, Opts}|Config]. [{type, ssl}, {port, Port}, {opts, Opts}|Config].
init_spdy(Ref, ProtoOpts, Config) -> init_spdy(Ref, ProtoOpts, Config) ->
Opts = get_certs(), Opts = ct_helper:get_certs_from_ets(),
{ok, _} = cowboy:start_spdy(Ref, 100, Opts ++ [{port, 0}], {ok, _} = cowboy:start_spdy(Ref, 100, Opts ++ [{port, 0}], ProtoOpts),
ProtoOpts),
Port = ranch:get_port(Ref), Port = ranch:get_port(Ref),
[{type, ssl}, {port, Port}, {opts, Opts}|Config]. [{type, ssl}, {port, Port}, {opts, Opts}|Config].
@ -148,22 +89,17 @@ init_common_groups(Name = spdy_compress, Config, Mod) ->
%% Support functions for testing using Gun. %% Support functions for testing using Gun.
gun_open(Config) -> gun_open(Config) ->
gun_open(Config, []). gun_open(Config, #{}).
gun_open(Config, Opts) -> gun_open(Config, Opts) ->
{ok, ConnPid} = gun:open("localhost", config(port, Config), {ok, ConnPid} = gun:open("localhost", config(port, Config), Opts#{
[{retry, 0}, {type, config(type, Config)}|Opts]), retry => 0,
transport => config(type, Config)
}),
ConnPid. ConnPid.
gun_monitor_open(Config) -> gun_down(ConnPid) ->
gun_monitor_open(Config, []). receive {gun_down, ConnPid, _, _, _, _} -> ok
gun_monitor_open(Config, Opts) ->
ConnPid = gun_open(Config, Opts),
{ConnPid, monitor(process, ConnPid)}.
gun_is_gone(ConnPid, MRef) ->
receive {'DOWN', MRef, process, ConnPid, gone} -> ok
after 500 -> error(timeout) end. after 500 -> error(timeout) end.
%% Support functions for testing using a raw socket. %% Support functions for testing using a raw socket.
@ -183,18 +119,21 @@ raw_send({raw_client, Socket, Transport}, Data) ->
Transport:send(Socket, Data). Transport:send(Socket, Data).
raw_recv_head({raw_client, Socket, Transport}) -> raw_recv_head({raw_client, Socket, Transport}) ->
{ok, Data} = Transport:recv(Socket, 0, 5000), {ok, Data} = Transport:recv(Socket, 0, 10000),
raw_recv_head(Socket, Transport, Data). raw_recv_head(Socket, Transport, Data).
raw_recv_head(Socket, Transport, Buffer) -> raw_recv_head(Socket, Transport, Buffer) ->
case binary:match(Buffer, <<"\r\n\r\n">>) of case binary:match(Buffer, <<"\r\n\r\n">>) of
nomatch -> nomatch ->
{ok, Data} = Transport:recv(Socket, 0, 5000), {ok, Data} = Transport:recv(Socket, 0, 10000),
raw_recv_head(Socket, Transport, << Buffer/binary, Data/binary >>); raw_recv_head(Socket, Transport, << Buffer/binary, Data/binary >>);
{_, _} -> {_, _} ->
Buffer Buffer
end. end.
raw_recv({raw_client, Socket, Transport}, Length, Timeout) ->
Transport:recv(Socket, Length, Timeout).
raw_expect_recv({raw_client, Socket, Transport}, Expect) -> raw_expect_recv({raw_client, Socket, Transport}, Expect) ->
{ok, Expect} = Transport:recv(Socket, iolist_size(Expect), 5000), {ok, Expect} = Transport:recv(Socket, iolist_size(Expect), 10000),
ok. ok.

View file

@ -1,22 +0,0 @@
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
%%
%% 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(eunit_SUITE).
-compile(export_all).
all() ->
[eunit].
eunit(_) ->
ok = eunit:test({application, cowboy}).

View file

@ -0,0 +1,16 @@
%% This module echoes back the value the test is interested in.
-module(asterisk_h).
-export([init/2]).
init(Req, Opts) ->
echo(cowboy_req:header(<<"x-echo">>, Req), Req, Opts).
echo(What, Req, Opts) ->
F = binary_to_atom(What, latin1),
Value = case cowboy_req:F(Req) of
V when is_integer(V) -> integer_to_binary(V);
V -> V
end,
{ok, cowboy_req:reply(200, [], Value, Req), Opts}.

16
test/handlers/echo_h.erl Normal file
View file

@ -0,0 +1,16 @@
%% This module echoes back the value the test is interested in.
-module(echo_h).
-export([init/2]).
init(Req, Opts) ->
echo(cowboy_req:binding(key, Req), Req, Opts).
echo(What, Req, Opts) ->
F = binary_to_atom(What, latin1),
Value = case cowboy_req:F(Req) of
V when is_integer(V) -> integer_to_binary(V);
V -> V
end,
{ok, cowboy_req:reply(200, [], Value, Req), Opts}.

View file

@ -0,0 +1,8 @@
%% This module sends a hello world response.
-module(hello_h).
-export([init/2]).
init(Req, Opts) ->
{ok, cowboy_req:reply(200, [], <<"Hello world!">>, Req), Opts}.

View file

@ -6,5 +6,5 @@
-export([init/2]). -export([init/2]).
init(Req, content_length) -> init(Req, content_length) ->
cowboy_error_h:ignore(erlang, binary_to_integer, 1), ct_helper_error_h:ignore(erlang, binary_to_integer, 1),
cowboy_req:parse_header(<<"content-length">>, Req). cowboy_req:parse_header(<<"content-length">>, Req).

View file

@ -16,11 +16,10 @@
-module(http_SUITE). -module(http_SUITE).
-compile(export_all). -compile(export_all).
-import(cowboy_test, [config/2]). -import(ct_helper, [config/2]).
-import(cowboy_test, [gun_open/1]). -import(cowboy_test, [gun_open/1]).
-import(cowboy_test, [gun_monitor_open/1]). -import(cowboy_test, [gun_open/2]).
-import(cowboy_test, [gun_monitor_open/2]). -import(cowboy_test, [gun_down/1]).
-import(cowboy_test, [gun_is_gone/2]).
-import(cowboy_test, [raw_open/1]). -import(cowboy_test, [raw_open/1]).
-import(cowboy_test, [raw_send/2]). -import(cowboy_test, [raw_send/2]).
-import(cowboy_test, [raw_recv_head/1]). -import(cowboy_test, [raw_recv_head/1]).
@ -41,7 +40,7 @@ all() ->
]. ].
groups() -> groups() ->
Tests = cowboy_test:all(?MODULE) -- [ Tests = ct_helper:all(?MODULE) -- [
onresponse_crash, onresponse_reply, onresponse_capitalize, onresponse_crash, onresponse_reply, onresponse_capitalize,
parse_host, set_env_dispatch parse_host, set_env_dispatch
], ],
@ -95,18 +94,14 @@ init_per_group(Name = https_compress, Config) ->
init_per_group(onresponse, Config) -> init_per_group(onresponse, Config) ->
{ok, _} = cowboy:start_http(onresponse, 100, [{port, 0}], [ {ok, _} = cowboy:start_http(onresponse, 100, [{port, 0}], [
{env, [{dispatch, init_dispatch(Config)}]}, {env, [{dispatch, init_dispatch(Config)}]},
{max_keepalive, 50}, {onresponse, fun do_onresponse_hook/4}
{onresponse, fun do_onresponse_hook/4},
{timeout, 500}
]), ]),
Port = ranch:get_port(onresponse), Port = ranch:get_port(onresponse),
[{type, tcp}, {port, Port}, {opts, []}|Config]; [{type, tcp}, {port, Port}, {opts, []}|Config];
init_per_group(onresponse_capitalize, Config) -> init_per_group(onresponse_capitalize, Config) ->
{ok, _} = cowboy:start_http(onresponse_capitalize, 100, [{port, 0}], [ {ok, _} = cowboy:start_http(onresponse_capitalize, 100, [{port, 0}], [
{env, [{dispatch, init_dispatch(Config)}]}, {env, [{dispatch, init_dispatch(Config)}]},
{max_keepalive, 50}, {onresponse, fun do_onresponse_capitalize_hook/4}
{onresponse, fun do_onresponse_capitalize_hook/4},
{timeout, 500}
]), ]),
Port = ranch:get_port(onresponse_capitalize), Port = ranch:get_port(onresponse_capitalize),
[{type, tcp}, {port, Port}, {opts, []}|Config]; [{type, tcp}, {port, Port}, {opts, []}|Config];
@ -117,17 +112,13 @@ init_per_group(parse_host, Config) ->
]} ]}
]), ]),
{ok, _} = cowboy:start_http(parse_host, 100, [{port, 0}], [ {ok, _} = cowboy:start_http(parse_host, 100, [{port, 0}], [
{env, [{dispatch, Dispatch}]}, {env, [{dispatch, Dispatch}]}
{max_keepalive, 50},
{timeout, 500}
]), ]),
Port = ranch:get_port(parse_host), Port = ranch:get_port(parse_host),
[{type, tcp}, {port, Port}, {opts, []}|Config]; [{type, tcp}, {port, Port}, {opts, []}|Config];
init_per_group(set_env, Config) -> init_per_group(set_env, Config) ->
{ok, _} = cowboy:start_http(set_env, 100, [{port, 0}], [ {ok, _} = cowboy:start_http(set_env, 100, [{port, 0}], [
{env, [{dispatch, []}]}, {env, [{dispatch, []}]}
{max_keepalive, 50},
{timeout, 500}
]), ]),
Port = ranch:get_port(set_env), Port = ranch:get_port(set_env),
[{type, tcp}, {port, Port}, {opts, []}|Config]. [{type, tcp}, {port, Port}, {opts, []}|Config].
@ -342,26 +333,26 @@ echo_body_qs_max_length(Config) ->
ok. ok.
error_init_after_reply(Config) -> error_init_after_reply(Config) ->
{ConnPid, MRef} = gun_monitor_open(Config), ConnPid = gun_open(Config),
Ref = gun:get(ConnPid, "/handler_errors?case=init_after_reply"), Ref = gun:get(ConnPid, "/handler_errors?case=init_after_reply"),
{response, nofin, 200, _} = gun:await(ConnPid, Ref, MRef), {response, nofin, 200, _} = gun:await(ConnPid, Ref),
gun_is_gone(ConnPid, MRef). gun_down(ConnPid).
headers_dupe(Config) -> headers_dupe(Config) ->
{ConnPid, MRef} = gun_monitor_open(Config), ConnPid = gun_open(Config),
Ref = gun:get(ConnPid, "/headers/dupe"), Ref = gun:get(ConnPid, "/headers/dupe"),
{response, nofin, 200, Headers} = gun:await(ConnPid, Ref, MRef), {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
%% Ensure that only one connection header was received. %% Ensure that only one connection header was received.
[<<"close">>] = [V || {Name, V} <- Headers, Name =:= <<"connection">>], [<<"close">>] = [V || {Name, V} <- Headers, Name =:= <<"connection">>],
gun_is_gone(ConnPid, MRef). gun_down(ConnPid).
http10_chunkless(Config) -> http10_chunkless(Config) ->
{ConnPid, MRef} = gun_monitor_open(Config, [{http, [{version, 'HTTP/1.0'}]}]), ConnPid = gun_open(Config, #{http_opts => #{version => 'HTTP/1.0'}}),
Ref = gun:get(ConnPid, "/chunked_response"), Ref = gun:get(ConnPid, "/chunked_response"),
{response, nofin, 200, Headers} = gun:await(ConnPid, Ref, MRef), {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
false = lists:keyfind(<<"transfer-encoding">>, 1, Headers), false = lists:keyfind(<<"transfer-encoding">>, 1, Headers),
{ok, <<"chunked_handler\r\nworks fine!">>} = gun:await_body(ConnPid, Ref, MRef), {ok, <<"chunked_handler\r\nworks fine!">>} = gun:await_body(ConnPid, Ref),
gun_is_gone(ConnPid, MRef). gun_down(ConnPid).
http10_hostless(Config) -> http10_hostless(Config) ->
Name = http10_hostless, Name = http10_hostless,
@ -411,17 +402,17 @@ http10_keepalive_forced(Config) ->
end. end.
keepalive_max(Config) -> keepalive_max(Config) ->
{ConnPid, MRef} = gun_monitor_open(Config), ConnPid = gun_open(Config),
Refs = [gun:get(ConnPid, "/", [{<<"connection">>, <<"keep-alive">>}]) Refs = [gun:get(ConnPid, "/", [{<<"connection">>, <<"keep-alive">>}])
|| _ <- lists:seq(1, 49)], || _ <- lists:seq(1, 99)],
CloseRef = gun:get(ConnPid, "/", [{<<"connection">>, <<"keep-alive">>}]), CloseRef = gun:get(ConnPid, "/", [{<<"connection">>, <<"keep-alive">>}]),
_ = [begin _ = [begin
{response, nofin, 200, Headers} = gun:await(ConnPid, Ref, MRef), {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
{_, <<"keep-alive">>} = lists:keyfind(<<"connection">>, 1, Headers) {_, <<"keep-alive">>} = lists:keyfind(<<"connection">>, 1, Headers)
end || Ref <- Refs], end || Ref <- Refs],
{response, nofin, 200, Headers} = gun:await(ConnPid, CloseRef, MRef), {response, nofin, 200, Headers} = gun:await(ConnPid, CloseRef),
{_, <<"close">>} = lists:keyfind(<<"connection">>, 1, Headers), {_, <<"close">>} = lists:keyfind(<<"connection">>, 1, Headers),
gun_is_gone(ConnPid, MRef). gun_down(ConnPid).
keepalive_nl(Config) -> keepalive_nl(Config) ->
ConnPid = gun_open(Config), ConnPid = gun_open(Config),
@ -440,7 +431,7 @@ keepalive_stream_loop(Config) ->
ConnPid = gun_open(Config), ConnPid = gun_open(Config),
Refs = [begin Refs = [begin
Ref = gun:post(ConnPid, "/loop_stream_recv", Ref = gun:post(ConnPid, "/loop_stream_recv",
[{<<"transfer-encoding">>, <<"chunked">>}]), [{<<"content-type">>, <<"application/octet-stream">>}]),
_ = [gun:data(ConnPid, Ref, nofin, << ID:32 >>) _ = [gun:data(ConnPid, Ref, nofin, << ID:32 >>)
|| ID <- lists:seq(1, 250)], || ID <- lists:seq(1, 250)],
gun:data(ConnPid, Ref, fin, <<>>), gun:data(ConnPid, Ref, fin, <<>>),
@ -481,9 +472,8 @@ multipart_chunked(Config) ->
"\r\n--OHai--\r\n" "\r\n--OHai--\r\n"
"This is an epilogue." "This is an epilogue."
>>, >>,
Ref = gun:post(ConnPid, "/multipart", [ Ref = gun:post(ConnPid, "/multipart",
{<<"content-type">>, <<"multipart/x-makes-no-sense; boundary=OHai">>}, [{<<"content-type">>, <<"multipart/x-makes-no-sense; boundary=OHai">>}]),
{<<"transfer-encoding">>, <<"chunked">>}]),
gun:data(ConnPid, Ref, fin, Body), gun:data(ConnPid, Ref, fin, Body),
{response, nofin, 200, _} = gun:await(ConnPid, Ref), {response, nofin, 200, _} = gun:await(ConnPid, Ref),
{ok, RespBody} = gun:await_body(ConnPid, Ref), {ok, RespBody} = gun:await_body(ConnPid, Ref),
@ -614,10 +604,12 @@ rest_param_all(Config) ->
%% Content-Type without param. %% Content-Type without param.
Ref6 = gun:put(ConnPid, "/param_all", Ref6 = gun:put(ConnPid, "/param_all",
[{<<"content-type">>, <<"text/plain">>}]), [{<<"content-type">>, <<"text/plain">>}]),
gun:data(ConnPid, Ref6, fin, "Hello world!"),
{response, fin, 204, _} = gun:await(ConnPid, Ref6), {response, fin, 204, _} = gun:await(ConnPid, Ref6),
%% Content-Type with param. %% Content-Type with param.
Ref7 = gun:put(ConnPid, "/param_all", Ref7 = gun:put(ConnPid, "/param_all",
[{<<"content-type">>, <<"text/plain; charset=utf-8">>}]), [{<<"content-type">>, <<"text/plain; charset=utf-8">>}]),
gun:data(ConnPid, Ref7, fin, "Hello world!"),
{response, fin, 204, _} = gun:await(ConnPid, Ref7), {response, fin, 204, _} = gun:await(ConnPid, Ref7),
ok. ok.
@ -662,12 +654,13 @@ rest_keepalive(Config) ->
rest_keepalive_post(Config) -> rest_keepalive_post(Config) ->
ConnPid = gun_open(Config), ConnPid = gun_open(Config),
Refs = [{ Refs = [begin
gun:post(ConnPid, "/forbidden_post", Ref1 = gun:post(ConnPid, "/forbidden_post", [{<<"content-type">>, <<"text/plain">>}]),
[{<<"content-type">>, <<"text/plain">>}]), gun:data(ConnPid, Ref1, fin, "Hello world!"),
gun:post(ConnPid, "/simple_post", Ref2 = gun:post(ConnPid, "/simple_post", [{<<"content-type">>, <<"text/plain">>}]),
[{<<"content-type">>, <<"text/plain">>}]) gun:data(ConnPid, Ref2, fin, "Hello world!"),
} || _ <- lists:seq(1, 5)], {Ref1, Ref2}
end || _ <- lists:seq(1, 5)],
_ = [begin _ = [begin
{response, fin, 403, Headers1} = gun:await(ConnPid, Ref1), {response, fin, 403, Headers1} = gun:await(ConnPid, Ref1),
{_, <<"keep-alive">>} = lists:keyfind(<<"connection">>, 1, Headers1), {_, <<"keep-alive">>} = lists:keyfind(<<"connection">>, 1, Headers1),
@ -808,7 +801,7 @@ slowloris(Config) ->
try try
[begin [begin
ok = raw_send(Client, [C]), ok = raw_send(Client, [C]),
receive after 25 -> ok end receive after 250 -> ok end
end || C <- "GET / HTTP/1.1\r\nHost: localhost\r\n" end || C <- "GET / HTTP/1.1\r\nHost: localhost\r\n"
"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US)\r\n" "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US)\r\n"
"Cookie: name=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n\r\n"], "Cookie: name=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n\r\n"],
@ -894,11 +887,11 @@ stream_body_set_resp(Config) ->
ok. ok.
stream_body_set_resp_close(Config) -> stream_body_set_resp_close(Config) ->
{ConnPid, MRef} = gun_monitor_open(Config), ConnPid = gun_open(Config),
Ref = gun:get(ConnPid, "/stream_body/set_resp_close"), Ref = gun:get(ConnPid, "/stream_body/set_resp_close"),
{response, nofin, 200, _} = gun:await(ConnPid, Ref, MRef), {response, nofin, 200, _} = gun:await(ConnPid, Ref),
{ok, <<"stream_body_set_resp_close">>} = gun:await_body(ConnPid, Ref, MRef), {ok, <<"stream_body_set_resp_close">>} = gun:await_body(ConnPid, Ref),
gun_is_gone(ConnPid, MRef). gun_down(ConnPid).
stream_body_set_resp_chunked(Config) -> stream_body_set_resp_chunked(Config) ->
ConnPid = gun_open(Config), ConnPid = gun_open(Config),
@ -909,12 +902,12 @@ stream_body_set_resp_chunked(Config) ->
ok. ok.
stream_body_set_resp_chunked10(Config) -> stream_body_set_resp_chunked10(Config) ->
{ConnPid, MRef} = gun_monitor_open(Config, [{http, [{version, 'HTTP/1.0'}]}]), ConnPid = gun_open(Config, #{http_opts => #{version => 'HTTP/1.0'}}),
Ref = gun:get(ConnPid, "/stream_body/set_resp_chunked"), Ref = gun:get(ConnPid, "/stream_body/set_resp_chunked"),
{response, nofin, 200, Headers} = gun:await(ConnPid, Ref, MRef), {response, nofin, 200, Headers} = gun:await(ConnPid, Ref),
false = lists:keyfind(<<"transfer-encoding">>, 1, Headers), false = lists:keyfind(<<"transfer-encoding">>, 1, Headers),
{ok, <<"stream_body_set_resp_chunked">>} = gun:await_body(ConnPid, Ref, MRef), {ok, <<"stream_body_set_resp_chunked">>} = gun:await_body(ConnPid, Ref),
gun_is_gone(ConnPid, MRef). gun_down(ConnPid).
%% Undocumented hack: force chunked response to be streamed as HTTP/1.1. %% Undocumented hack: force chunked response to be streamed as HTTP/1.1.
streamed_response(Config) -> streamed_response(Config) ->
@ -933,8 +926,8 @@ streamed_response(Config) ->
te_chunked(Config) -> te_chunked(Config) ->
Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])), Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])),
ConnPid = gun_open(Config), ConnPid = gun_open(Config),
Ref = gun:post(ConnPid, "/echo/body", Ref = gun:post(ConnPid, "/echo/body", [{<<"content-type">>, <<"text/plain">>}]),
[{<<"transfer-encoding">>, <<"chunked">>}], Body), gun:data(ConnPid, Ref, fin, Body),
{response, nofin, 200, _} = gun:await(ConnPid, Ref), {response, nofin, 200, _} = gun:await(ConnPid, Ref),
{ok, Body} = gun:await_body(ConnPid, Ref), {ok, Body} = gun:await_body(ConnPid, Ref),
ok. ok.
@ -957,7 +950,7 @@ te_chunked_chopped(Config) ->
Body2 = iolist_to_binary(do_body_to_chunks(50, Body, [])), Body2 = iolist_to_binary(do_body_to_chunks(50, Body, [])),
ConnPid = gun_open(Config), ConnPid = gun_open(Config),
Ref = gun:post(ConnPid, "/echo/body", Ref = gun:post(ConnPid, "/echo/body",
[{<<"transfer-encoding">>, <<"chunked">>}]), [{<<"content-type">>, <<"text/plain">>}]),
_ = [begin _ = [begin
ok = gun:dbg_send_raw(ConnPid, << C >>), ok = gun:dbg_send_raw(ConnPid, << C >>),
receive after 10 -> ok end receive after 10 -> ok end
@ -971,7 +964,7 @@ te_chunked_delayed(Config) ->
Chunks = do_body_to_chunks(50, Body, []), Chunks = do_body_to_chunks(50, Body, []),
ConnPid = gun_open(Config), ConnPid = gun_open(Config),
Ref = gun:post(ConnPid, "/echo/body", Ref = gun:post(ConnPid, "/echo/body",
[{<<"transfer-encoding">>, <<"chunked">>}]), [{<<"content-type">>, <<"text/plain">>}]),
_ = [begin _ = [begin
ok = gun:dbg_send_raw(ConnPid, Chunk), ok = gun:dbg_send_raw(ConnPid, Chunk),
receive after 10 -> ok end receive after 10 -> ok end
@ -985,7 +978,7 @@ te_chunked_split_body(Config) ->
Chunks = do_body_to_chunks(50, Body, []), Chunks = do_body_to_chunks(50, Body, []),
ConnPid = gun_open(Config), ConnPid = gun_open(Config),
Ref = gun:post(ConnPid, "/echo/body", Ref = gun:post(ConnPid, "/echo/body",
[{<<"transfer-encoding">>, <<"chunked">>}]), [{<<"content-type">>, <<"text/plain">>}]),
_ = [begin _ = [begin
case Chunk of case Chunk of
<<"0\r\n\r\n">> -> <<"0\r\n\r\n">> ->
@ -1009,7 +1002,7 @@ te_chunked_split_crlf(Config) ->
Chunks = do_body_to_chunks(50, Body, []), Chunks = do_body_to_chunks(50, Body, []),
ConnPid = gun_open(Config), ConnPid = gun_open(Config),
Ref = gun:post(ConnPid, "/echo/body", Ref = gun:post(ConnPid, "/echo/body",
[{<<"transfer-encoding">>, <<"chunked">>}]), [{<<"content-type">>, <<"text/plain">>}]),
_ = [begin _ = [begin
%% Split in the newline just before the end of the chunk. %% Split in the newline just before the end of the chunk.
Len = byte_size(Chunk) - (random:uniform(2) - 1), Len = byte_size(Chunk) - (random:uniform(2) - 1),

View file

@ -9,9 +9,9 @@ init(Req, _Opts) ->
case_init(Case, Req). case_init(Case, Req).
case_init(<<"init_before_reply">> = Case, _Req) -> case_init(<<"init_before_reply">> = Case, _Req) ->
cowboy_error_h:ignore(?MODULE, case_init, 2), ct_helper_error_h:ignore(?MODULE, case_init, 2),
error(Case); error(Case);
case_init(<<"init_after_reply">> = Case, Req) -> case_init(<<"init_after_reply">> = Case, Req) ->
cowboy_error_h:ignore(?MODULE, case_init, 2), ct_helper_error_h:ignore(?MODULE, case_init, 2),
_ = cowboy_req:reply(200, [], "http_handler_crashes", Req), _ = cowboy_req:reply(200, [], "http_handler_crashes", Req),
error(Case). error(Case).

View file

@ -12,13 +12,13 @@ allowed_methods(Req, State) ->
{[<<"GET">>, <<"PUT">>], Req, State}. {[<<"GET">>, <<"PUT">>], Req, State}.
content_types_accepted(Req, State) -> content_types_accepted(Req, State) ->
cowboy_error_h:ignore(cowboy_rest, process_content_type, 3), ct_helper_error_h:ignore(cowboy_rest, process_content_type, 3),
{[ {[
{<<"application/json">>, put_application_json} {<<"application/json">>, put_application_json}
], Req, State}. ], Req, State}.
content_types_provided(Req, State) -> content_types_provided(Req, State) ->
cowboy_error_h:ignore(cowboy_rest, set_resp_body, 2), ct_helper_error_h:ignore(cowboy_rest, set_resp_body, 2),
{[ {[
{<<"text/plain">>, get_text_plain} {<<"text/plain">>, get_text_plain}
], Req, State}. ], Req, State}.

View file

@ -23,10 +23,10 @@ generate_etag(Req, State) ->
{<<"\"etag-header-value\"">>, Req, State}; {<<"\"etag-header-value\"">>, Req, State};
%% Invalid return values from generate_etag/2. %% Invalid return values from generate_etag/2.
<<"binary-strong-unquoted">> -> <<"binary-strong-unquoted">> ->
cowboy_error_h:ignore(cow_http_hd, parse_etag, 1), ct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),
{<<"etag-header-value">>, Req, State}; {<<"etag-header-value">>, Req, State};
<<"binary-weak-unquoted">> -> <<"binary-weak-unquoted">> ->
cowboy_error_h:ignore(cow_http_hd, parse_etag, 1), ct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),
{<<"W/etag-header-value">>, Req, State} {<<"W/etag-header-value">>, Req, State}
end. end.

View file

@ -15,8 +15,8 @@
-module(loop_handler_SUITE). -module(loop_handler_SUITE).
-compile(export_all). -compile(export_all).
-import(cowboy_test, [config/2]). -import(ct_helper, [config/2]).
-import(cowboy_test, [doc/1]). -import(ct_helper, [doc/1]).
-import(cowboy_test, [gun_open/1]). -import(cowboy_test, [gun_open/1]).
%% ct. %% ct.
@ -25,7 +25,7 @@ all() ->
cowboy_test:common_all(). cowboy_test:common_all().
groups() -> groups() ->
cowboy_test:common_groups(cowboy_test:all(?MODULE)). cowboy_test:common_groups(ct_helper:all(?MODULE)).
init_per_group(Name, Config) -> init_per_group(Name, Config) ->
cowboy_test:init_common_groups(Name, Config, ?MODULE). cowboy_test:init_common_groups(Name, Config, ?MODULE).

1388
test/rfc7230_SUITE.erl Normal file

File diff suppressed because it is too large Load diff

View file

@ -15,8 +15,8 @@
-module(spdy_SUITE). -module(spdy_SUITE).
-compile(export_all). -compile(export_all).
-import(cowboy_test, [config/2]). -import(ct_helper, [config/2]).
-import(cowboy_test, [gun_monitor_open/1]). -import(cowboy_test, [gun_open/1]).
-import(cowboy_test, [raw_open/1]). -import(cowboy_test, [raw_open/1]).
-import(cowboy_test, [raw_send/2]). -import(cowboy_test, [raw_send/2]).
@ -26,7 +26,7 @@ all() ->
[{group, spdy}]. [{group, spdy}].
groups() -> groups() ->
[{spdy, [], cowboy_test:all(?MODULE)}]. [{spdy, [], ct_helper:all(?MODULE)}].
init_per_suite(Config) -> init_per_suite(Config) ->
case proplists:get_value(ssl_app, ssl:versions()) of case proplists:get_value(ssl_app, ssl:versions()) of
@ -64,9 +64,9 @@ init_dispatch(Config) ->
%% Convenience functions. %% Convenience functions.
do_get(ConnPid, MRef, Host, Path) -> do_get(ConnPid, Host, Path) ->
StreamRef = gun:get(ConnPid, Path, [{":host", Host}]), StreamRef = gun:get(ConnPid, Path, [{<<"host">>, Host}]),
{response, IsFin, Status, _} = gun:await(ConnPid, StreamRef, MRef), {response, IsFin, Status, _} = gun:await(ConnPid, StreamRef),
{IsFin, Status}. {IsFin, Status}.
%% Tests. %% Tests.
@ -80,25 +80,25 @@ check_status(Config) ->
{400, fin, "localhost", "bad-path"}, {400, fin, "localhost", "bad-path"},
{404, fin, "localhost", "/this/path/does/not/exist"} {404, fin, "localhost", "/this/path/does/not/exist"}
], ],
{ConnPid, MRef} = gun_monitor_open(Config), ConnPid = gun_open(Config),
_ = [{Status, Fin, Host, Path} = begin _ = [{Status, Fin, Host, Path} = begin
{IsFin, Ret} = do_get(ConnPid, MRef, Host, Path), {IsFin, Ret} = do_get(ConnPid, Host, Path),
{Ret, IsFin, Host, Path} {Ret, IsFin, Host, Path}
end || {Status, Fin, Host, Path} <- Tests], end || {Status, Fin, Host, Path} <- Tests],
gun:close(ConnPid). gun:close(ConnPid).
echo_body(Config) -> echo_body(Config) ->
{ConnPid, MRef} = gun_monitor_open(Config), ConnPid = gun_open(Config),
Body = << 0:800000 >>, Body = << 0:800000 >>,
StreamRef = gun:post(ConnPid, "/echo/body", [ StreamRef = gun:post(ConnPid, "/echo/body", [
{<<"content-type">>, "application/octet-stream"} {<<"content-type">>, "application/octet-stream"}
], Body), ], Body),
{response, nofin, 200, _} = gun:await(ConnPid, StreamRef, MRef), {response, nofin, 200, _} = gun:await(ConnPid, StreamRef),
{ok, Body} = gun:await_body(ConnPid, StreamRef, MRef), {ok, Body} = gun:await_body(ConnPid, StreamRef),
gun:close(ConnPid). gun:close(ConnPid).
echo_body_multi(Config) -> echo_body_multi(Config) ->
{ConnPid, MRef} = gun_monitor_open(Config), ConnPid = gun_open(Config),
BodyChunk = << 0:80000 >>, BodyChunk = << 0:80000 >>,
StreamRef = gun:post(ConnPid, "/echo/body", [ StreamRef = gun:post(ConnPid, "/echo/body", [
%% @todo I'm still unhappy with this. It shouldn't be required... %% @todo I'm still unhappy with this. It shouldn't be required...
@ -107,8 +107,8 @@ echo_body_multi(Config) ->
]), ]),
_ = [gun:data(ConnPid, StreamRef, nofin, BodyChunk) || _ <- lists:seq(1, 9)], _ = [gun:data(ConnPid, StreamRef, nofin, BodyChunk) || _ <- lists:seq(1, 9)],
gun:data(ConnPid, StreamRef, fin, BodyChunk), gun:data(ConnPid, StreamRef, fin, BodyChunk),
{response, nofin, 200, _} = gun:await(ConnPid, StreamRef, MRef), {response, nofin, 200, _} = gun:await(ConnPid, StreamRef),
{ok, << 0:800000 >>} = gun:await_body(ConnPid, StreamRef, MRef), {ok, << 0:800000 >>} = gun:await_body(ConnPid, StreamRef),
gun:close(ConnPid). gun:close(ConnPid).
two_frames_one_packet(Config) -> two_frames_one_packet(Config) ->

View file

@ -15,7 +15,7 @@
-module(ws_SUITE). -module(ws_SUITE).
-compile(export_all). -compile(export_all).
-import(cowboy_test, [config/2]). -import(ct_helper, [config/2]).
%% ct. %% ct.
@ -23,12 +23,9 @@ all() ->
[{group, autobahn}, {group, ws}]. [{group, autobahn}, {group, ws}].
groups() -> groups() ->
BaseTests = cowboy_test:all(?MODULE) -- [autobahn_fuzzingclient], BaseTests = ct_helper:all(?MODULE) -- [autobahn_fuzzingclient],
[{autobahn, [], [autobahn_fuzzingclient]}, {ws, [parallel], BaseTests}]. [{autobahn, [], [autobahn_fuzzingclient]}, {ws, [parallel], BaseTests}].
init_per_suite(Config) ->
Config.
init_per_group(Name = autobahn, Config) -> init_per_group(Name = autobahn, Config) ->
%% Some systems have it named pip2. %% Some systems have it named pip2.
Out = os:cmd("pip show autobahntestsuite ; pip2 show autobahntestsuite"), Out = os:cmd("pip show autobahntestsuite ; pip2 show autobahntestsuite"),