Skip to content
Browse files

Added example test myapp application

  • Loading branch information...
1 parent d4658da commit 36dd6a1497d7f2efa530e41641906931378d7abd Michal Ptaszek committed Oct 16, 2009
View
7 Emakefile
@@ -49,3 +49,10 @@
debug_info,
strict_record_tests,
netload]}.
+
+{'lib/myapp-0.1/src/*', [
+ {outdir, "lib/myapp-0.1/ebin"},
+ {i, "lib/myapp-0.1/include"},
+ debug_info,
+ strict_record_tests,
+ netload]}.
View
13 bin/test.erl
@@ -32,18 +32,25 @@ run_tests(Dir) ->
start_interactive_mode_node(Dir).
start_interactive_mode_node(ReportDir) ->
- Port = open_port({spawn, "bin/start_interactive inets single_node -run ewts start_tests " ++ ReportDir},
+ Port = open_port({spawn, "bin/start_interactive inets single_node "
+ "-sasl sasl_error_logger false -pa lib/*/test "
+ "-s ewts "
+ "-run ewts start_tests "
+ ++ ReportDir},
[use_stdio, stderr_to_stdout, stream, {line, 1024}]),
print_output(Port).
print_output(Port) ->
receive
- {Port, {data, {eol, "1> EWTS: " ++ Line}}} ->
+ {Port, {data, {eol, "1> " ++ Line}}} ->
+ io:format("~s~n", [Line]),
+ print_output(Port);
+ {Port, {data, {eol, Line}}} ->
io:format("~s~n", [Line]),
print_output(Port);
{Port, {data, _Data}} ->
print_output(Port)
- after 1000 ->
+ after 100000 ->
port_close(Port),
ok
end.
View
56 lib/ewts-1.0/src/ewts.erl
@@ -1,14 +1,24 @@
-module(ewts).
+-export([start/0]).
-export([start_tests/1, fget/2]).
+-spec(start/0 :: () -> any()).
+start() ->
+ application:start(ewts).
+
-spec(start_tests/1 :: (string()) -> any()).
start_tests(Outdir) ->
error_logger:tty(false),
Modules = lists:concat(lists:map(
fun(Application) ->
- Result = cover:compile_directory(filename:join(["../../", Application, "src"]),
- [{i, filename:join(["../../", Application, "include"])}]),
+ Result = cover:compile_directory(filename:join(["lib", Application, "src"]),
+ [{i, filename:join(["lib", Application, "include"])}]),
+
+ FilesTest = filelib:wildcard(filename:join(["lib", Application, "test", "*erl"])),
+ make:files(FilesTest, [{outdir, filename:join(["lib", Application, "test"])},
+ {i, filename:join(["lib", Application, "include"])}]),
+
if
is_list(Result) ->
lists:foldl(fun({ok, M}, Acc) -> [M | Acc];
@@ -18,7 +28,7 @@ start_tests(Outdir) ->
end
end, get_apps())),
TestModules = [list_to_atom(filename:basename(F, ".erl")) ||
- F <- filelib:wildcard("../../*/test/*.erl")],
+ F <- filelib:wildcard("lib/*/test/*.erl")],
EUnitResult = eunit:test(TestModules),
if
@@ -88,8 +98,44 @@ fget0(List, Key, Dict) ->
-spec(get_apps/0 :: () -> (list(string()))).
get_apps() ->
- element(2, file:list_dir("../../")) -- ["yaws", "eptic", "eptic_fe", "wpart",
- "wparts", "ewts"].
+ filter(
+ element(2, file:list_dir("lib")), []).
+
+-spec(filter/2 :: (list(string()), list(string())) -> list(string())).
+filter(["yaws" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter(["eptic" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter(["wpart-" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter(["wparts-" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter(["ewts-" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter(["ewgi-" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter(["kernel-" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter(["stdlib-" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter(["mnesia-" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter(["inets-" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter(["mochiweb-" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter(["erlydtl-" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter(["crypto-" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter(["ssl-" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter(["sasl-" ++ _ | Rest], Acc) ->
+ filter(Rest, Acc);
+filter([App | Rest], Acc) ->
+ filter(Rest, [App | Acc]);
+filter([], Acc) ->
+ Acc.
html_report(Path, Modules) ->
Results = lists:map(fun(Module) ->
View
5 lib/myapp-0.1/Makefile
@@ -0,0 +1,5 @@
+include ../../make/apps-defs.mk
+
+%: force
+ @$(MAKE) --no-print-directory -f ../../make/apps-common.mk $@
+force: ;
View
0 lib/myapp-0.1/doc/overview.edoc
No changes.
View
11 lib/myapp-0.1/ebin/myapp.app
@@ -0,0 +1,11 @@
+%% -*- mode: erlang; -*-
+{application, myapp,
+ [
+ {description, ""},
+ {vsn, "0.1"},
+ {modules, [widget_tests, wtype_widget_test]},
+ {registered, []},
+ {applications, [kernel, stdlib, eptic, wpart, wparts, ewts]},
+ {build_dependencies, []},
+ {env, []}
+ ]}.
View
49 lib/myapp-0.1/include/widget_test.hrl
@@ -0,0 +1,49 @@
+-record(widget_test, {
+ id,
+ atom,
+ autocomplete,
+ bool,
+ csv,
+ date,
+ datetime,
+ enum,
+ float,
+ integer,
+ multilist,
+ password,
+ string,
+ text,
+ time,
+ upload}).
+
+-record(widget_test_types, {
+ id = {integer, [{primary_key},
+ {private, true}]},
+ atom = {atom, [{description, "Atom"}]},
+ autocomplete = {autocomplete, [{description, "Autocomplete"},
+ {complete, ["to","jest","test"]}]},
+ bool = {bool, [{description, "Bool"},
+ {always, ["man"]},
+ {options, "man:Man|animal:Woman"}]},
+ csv = {csv, [{description, "CSV"},
+ {type, string},
+ {delimiter, " "}]},
+ date = {date, [{description, "Date"},
+ {format, "YYYY-MM-DD"}]},
+ datetime = {datetime, [{description, "Datetime"}]},
+ enum = {enum, [{description, "Enum"},
+ {chosen, "blue"},
+ {choices, "blue:Blue|red:Red|none:I am color-blind"}]},
+ float = {float, [{description, "Float"}]},
+ integer = {integer, [{description, "Integer"}]},
+ multilist = {multilist, [{description, "Multilist"},
+ {options, "dog:Doggy|cat:Kitten|rhino:Other"},
+ {selected, ["cat", "rhino"]},
+ {html_attrs, [{multiple, "multiple"}]}]},
+ password = {password, [{description, "Password"}]},
+ string = {string, [{description, "String"},
+ {html_attrs, [{class, required}]}]},
+ text = {text, [{description, "Text"}]},
+ time = {time, [{description, "Time"}]},
+ upload = {upload, [{description, "Upload text file"},
+ {extension, ".txt"}]}}).
View
15 lib/myapp-0.1/priv/dispatch.conf
@@ -0,0 +1,15 @@
+{static, "^/$", "index.html"}.
+{dynamic, "rss", {rss, blog}}.
+{dynamic, "^/blog/post/(?<post_no>[0-9]+)/comment/(?<comment_no>[0-9]+)/?$", {blog, comment}}.
+{static, "^/some_template\\.html$", "some_site.html"}.
+
+{static, "^/tpl_top_down", "top_down.html"}.
+
+{dynamic, "^/widgets/create", {widget_tests, render}}.
+{dynamic, "^/widgets/save", {widget_tests, save}}.
+{dynamic, "^/widgets/update/(?<id>[0-9]+)", {widget_tests, update_item}}.
+{dynamic, "^/widgets/update", {widget_tests, update}}.
+
+{static, "css$", enoent}.
+{static, "js$", enoent}.
+{static, "gif$", enoent}.
View
11 lib/myapp-0.1/priv/templates/index.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+ <script type="text/javascript" src="/DateTimeShortcuts.js"> </script>
+ <script type="text/javascript" src="/calendar.js"> </script>
+</head>
+<body>
+ <h2>INDEX</h2>
+
+ <wpart:date class="vDateField" />
+</body>
+</html>
View
7 lib/myapp-0.1/priv/templates/parent.html
@@ -0,0 +1,7 @@
+<html>
+<body>
+ Parent template<br/>
+ <wtpl:include name="child" />
+ <wtpl:insert path="aaa.html" />
+</body>
+</html>
View
3 lib/myapp-0.1/priv/templates/some_slot.html
@@ -0,0 +1,3 @@
+<div>
+ Inserted template
+</div>
View
9 lib/myapp-0.1/priv/templates/top_down.html
@@ -0,0 +1,9 @@
+<wtpl:parent path="parent.html">
+ <wtpl:content name="child">
+
+ Child template
+
+ <wtpl:insert path="some_slot.html" />
+
+ </wtpl:content>
+</wtpl:parent>
View
18 lib/myapp-0.1/priv/templates/widget_test/create.html
@@ -0,0 +1,18 @@
+<html>
+ <head>
+ <link rel="stylesheet"
+ href="/autocomplete.css"
+ type="text/css" />
+ <script src="/jquery.js"
+ type="text/javascript"> </script>
+ <script src="/jquery.autocomplete.js"
+ type="text/javascript"> </script>
+ </head>
+ <body>
+ <wpart:lookup key="error_message" />
+ <wpart:form type="widget_test"
+ wpart:action="/widgets/save?form_type={[string]get:form_type}"
+ wpart:form_type="{[string]get:form_type}"
+ submit_text="test widget"/>
+ </body>
+</html>
View
18 lib/myapp-0.1/priv/templates/widget_test/update.html
@@ -0,0 +1,18 @@
+<html>
+ <head>
+ <link rel="stylesheet"
+ href="/autocomplete.css"
+ type="text/css" />
+ <script src="/jquery.js"
+ type="text/javascript"> </script>
+ <script src="/jquery.autocomplete.js"
+ type="text/javascript"> </script>
+ </head>
+ <body>
+ <wpart:lookup key="error_message" />
+ <wpart:form type="widget_test"
+ wpart:action="/widgets/update?form_type={[string]get:form_type}"
+ wpart:form_type="{[string]get:form_type}"
+ submit_text="test widget"/>
+ </body>
+</html>
View
11 lib/myapp-0.1/src/myapp.app.src
@@ -0,0 +1,11 @@
+%% -*- mode: erlang; -*-
+{application, myapp,
+ [
+ {description, ""},
+ {vsn, "@VSN@"},
+ {modules, [@MODULES@]},
+ {registered, []},
+ {applications, [kernel, stdlib]},
+ {build_dependencies, []},
+ {env, []}
+ ]}.
View
60 lib/myapp-0.1/src/widget_tests.erl
@@ -0,0 +1,60 @@
+-module(widget_tests).
+
+-export([dataflow/1, error/2]).
+-export([validate/2]).
+-export([render/1, save/1, update_item/1, update/1]).
+
+-spec(dataflow/1 :: (atom()) -> list() | tuple(list())).
+dataflow(save) -> [validate];
+dataflow(update) -> [validate];
+dataflow(_) -> [].
+
+-spec(error/2 :: (atom(), term()) -> ok).
+error(Fun, _) when Fun == save; Fun == update ->
+ Err = wpart:fget("__error"),
+ Message = "ERROR: Incomplete input or wrong type in form! Reason: " ++ Err,
+ wpart:fset("error_message", Message),
+
+ NotValidated = wtype_widget_test:prepare_invalid(),
+ wpart:fset("__edit", NotValidated),
+
+ {template, "widget_test/" ++
+ if
+ Fun == save ->
+ "create.html";
+ true ->
+ "update.html"
+ end}.
+
+validate(save, _) ->
+ validate_tool:validate_cu(widget_test, create);
+validate(update, _) ->
+ validate_tool:validate_cu(widget_test, update).
+
+render(_) ->
+ case wpart:fget("get:form_type") of
+ undefined ->
+ wpart:fset("get:form_type", "div");
+ _ ->
+ ok
+ end,
+ {template, "widget_test/create.html"}.
+
+save(Entity) ->
+ wtype_widget_test:create(Entity),
+
+ {redirect, "/"}.
+
+update_item([{id, Id}]) ->
+ Item = e_db:read(widget_test, list_to_integer(Id)),
+ Test = wpart_db:build_record_structure(widget_test, initial, Item),
+
+ wpart:fset("__edit", Test),
+ wpart:fset("__primary_key", list_to_integer(Id)),
+
+ {template, "widget_test/update.html"}.
+
+update(Entity) ->
+ wtype_widget_test:create(Entity),
+
+ {redirect, "/"}.
View
27 lib/myapp-0.1/src/wtype_widget_test.erl
@@ -0,0 +1,27 @@
+-module(wtype_widget_test).
+
+-export([get_record_info/1, install/0]).
+-export([create/1]).
+-export([prepare_invalid/0]).
+
+-include_lib("myapp/include/widget_test.hrl").
+
+-spec(get_record_info/1 :: (atom()) -> term()).
+get_record_info(widget_test) ->
+ record_info(fields, widget_test);
+get_record_info(widget_test_types) ->
+ #widget_test_types{}.
+
+-spec(create/1 :: (tuple()) -> term()).
+create(Entity) ->
+ e_db:write(widget_test, Entity).
+
+-spec(prepare_invalid/0 :: () -> tuple()).
+prepare_invalid() ->
+ wpart_db:build_record_structure(widget_test, initial,
+ wpart:fget("__not_validated")).
+
+-spec(install/0 :: () -> any()).
+install() ->
+ mnesia:create_table(widget_test, [{attributes, get_record_info(widget_test)},
+ {disc_copies, [node()]}]).
View
158 lib/myapp-0.1/test/ewts_widgets_test.erl
@@ -0,0 +1,158 @@
+-module(ewts_widgets_test).
+
+-compile(export_all).
+
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("ewts/include/request.hrl").
+
+render_test() ->
+ Response = ewts_client:request("/widgets/create", [{"key", "val"}]),
+ ?assertEqual(Response#response.code, 200),
+ ?assertEqual(ewts:fget("get:key", Response#response.req_dict), "val"),
+ ?assert(0 == string:str(Response#response.body, "<table")),
+ ?assertNot(0 == string:str(Response#response.body, "autocomplete")),
+ ?assert(0 == string:str(Response#response.body, "__primary_key")),
+
+ Response2 = ewts_client:request("/widgets/create", [{"form_type", "table"}]),
+ ?assertEqual(Response2#response.code, 200),
+ ?assertEqual(ewts:fget("get:form_type", Response2#response.req_dict), "table"),
+ ?assertNot(0 == string:str(Response2#response.body, "<table")),
+ ?assert(0 == string:str(Response2#response.body, "__primary_key")),
+ ?assertNot(0 == string:str(Response2#response.body, "autocomplete")).
+
+wrong_save_test() ->
+ Post = [{"widget_test_atom", "myapp"},
+ {"widget_test_autocomplete", "test"},
+ {"widget_test_bool", "man"},
+ {"widget_test_csv", "this is a test"},
+ {"widget_test_date", "2001-12-12"},
+ {"widget_test_datetime", "2001-12-12 21:21:21"},
+ {"widget_test_enum", "blue"},
+ {"widget_test_integer", "444"},
+ {"widget_test_multilist", "dog"},
+ {"widget_test_password", "pwd"}],
+ Response = ewts_client:request("/widgets/save", [], Post),
+
+ ?assertEqual(Response#response.code, 200),
+ ?assert(0 == string:str(Response#response.body, "__primary_key")),
+ ?assertNot(0 == string:str(Response#response.body, "this is a test")),
+ ?assertNot(undefined == ewts:fget("__not_validated", Response#response.req_dict)),
+ ?assertNot(undefined == ewts:fget("__edit", Response#response.req_dict)),
+ ?assertEqual("/widgets/save", ewts:fget("__path", Response#response.req_dict)),
+ ?assertEqual(widget_tests, ewts:fget("__controller", Response#response.req_dict)).
+
+good_save_test() ->
+ ewts_dbg:clear(),
+ ewts_dbg:add_case({dispatcher_result, {widget_tests, save, []}}),
+ ewts_dbg:add_case({function_response, {widget_tests, dataflow, 1}, [validate]}),
+ ewts_dbg:add_case({function_response, {widget_tests, save, 1}, {redirect, "/"}}),
+
+ Post = [{"widget_test_atom", "myapp"},
+ {"widget_test_autocomplete", "test"},
+ {"widget_test_bool", "man"},
+ {"widget_test_csv", "this is a test"},
+ {"widget_test_date", "2001-12-12"},
+ {"widget_test_datetime", "2001-12-12 21:21:21"},
+ {"widget_test_enum", "blue"},
+ {"widget_test_integer", "444"},
+ {"widget_test_float", "213123.213213"},
+ {"widget_test_multilist", "dog"},
+ {"widget_test_password", "pwd"},
+ {"widget_test_string", "string"},
+ {"widget_test_text", "text"},
+ {"widget_test_time", "12:21:12"}],
+ Response = ewts_client:request("/widgets/save", [], Post),
+
+ ?assertEqual(Response#response.code, 302),
+ ?assert(undefined == ewts:fget("__not_validated", Response#response.req_dict)),
+ ?assert(undefined == ewts:fget("__edit", Response#response.req_dict)),
+ ?assertEqual("/widgets/save", ewts:fget("__path", Response#response.req_dict)),
+ ?assertEqual(widget_tests, ewts:fget("__controller", Response#response.req_dict)),
+
+ ?assertDbg(ewts_dbg:results(), 3).
+%% the same as
+%% {Passed, Failed} = ewts_dbg:results(),
+%% ?assert(Failed == 0),
+%% ?assert(Passed >= 3),
+%% ewts_dbg:clear().
+
+render_update_test() ->
+%% done only to become sure we have at least one element in the db
+ Post = [{"widget_test_atom", "myapp"},
+ {"widget_test_autocomplete", "test"},
+ {"widget_test_bool", "man"},
+ {"widget_test_csv", "this is a test"},
+ {"widget_test_date", "2001-12-12"},
+ {"widget_test_datetime", "2001-12-12 21:21:21"},
+ {"widget_test_enum", "blue"},
+ {"widget_test_integer", "444"},
+ {"widget_test_float", "213123.213213"},
+ {"widget_test_multilist", "dog"},
+ {"widget_test_password", "pwd"},
+ {"widget_test_string", "string"},
+ {"widget_test_text", "text"},
+ {"widget_test_time", "12:21:12"}],
+ ewts_client:request("/widgets/save", [], Post),
+
+ Response = ewts_client:request("/widgets/update/1"),
+ ?assertEqual(Response#response.code, 200),
+ ?assertEqual(1, ewts:fget("__primary_key", Response#response.req_dict)),
+ ?assertNot(undefined == ewts:fget("__edit", Response#response.req_dict)).
+
+wrong_update_test() ->
+ PostCorrect = [{"widget_test_atom", "myapp"},
+ {"widget_test_autocomplete", "test"},
+ {"widget_test_bool", "man"},
+ {"widget_test_csv", "this is a test"},
+ {"widget_test_date", "2001-12-12"},
+ {"widget_test_datetime", "2001-12-12 21:21:21"},
+ {"widget_test_enum", "blue"},
+ {"widget_test_integer", "444"},
+ {"widget_test_float", "213123.213213"},
+ {"widget_test_multilist", "dog"},
+ {"widget_test_password", "pwd"},
+ {"widget_test_string", "string"},
+ {"widget_test_text", "text"},
+ {"widget_test_time", "12:21:12"}],
+ ewts_client:request("/widgets/save", [], PostCorrect),
+
+ Post = [{"__primary_key", "1"},
+ {"widget_test_time", "wrong time format"}],
+ Response = ewts_client:request("/widgets/update", [], Post),
+ ?assertEqual(Response#response.code, 200),
+ ?assertNot(undefined == ewts:fget("__edit", Response#response.req_dict)).
+
+correct_update_test() ->
+ PostCorrect = [{"widget_test_atom", "myapp"},
+ {"widget_test_autocomplete", "test"},
+ {"widget_test_bool", "man"},
+ {"widget_test_csv", "this is a test"},
+ {"widget_test_date", "2001-12-12"},
+ {"widget_test_datetime", "2001-12-12 21:21:21"},
+ {"widget_test_enum", "blue"},
+ {"widget_test_integer", "444"},
+ {"widget_test_float", "213123.213213"},
+ {"widget_test_multilist", "dog"},
+ {"widget_test_password", "pwd"},
+ {"widget_test_string", "string"},
+ {"widget_test_text", "text"},
+ {"widget_test_time", "12:21:12"}],
+ ewts_client:request("/widgets/save", [], PostCorrect),
+
+ Post = [{"__primary_key", "1"},
+ {"widget_test_atom", "myapp"},
+ {"widget_test_autocomplete", "correct"},
+ {"widget_test_bool", "man"},
+ {"widget_test_csv", "this is a test"},
+ {"widget_test_date", "2001-12-12"},
+ {"widget_test_datetime", "2001-12-12 21:21:21"},
+ {"widget_test_enum", "blue"},
+ {"widget_test_integer", "444"},
+ {"widget_test_float", "213123.213213"},
+ {"widget_test_multilist", "dog"},
+ {"widget_test_password", "pwd"},
+ {"widget_test_string", "string"},
+ {"widget_test_text", "text"},
+ {"widget_test_time", "12:21:12"}],
+ Response = ewts_client:request("/widgets/update", [], Post),
+ ?assertEqual(Response#response.code, 302).
View
29 lib/myapp-0.1/test/myapp_tests.erl
@@ -0,0 +1,29 @@
+-module(myapp_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("ewts/include/request.hrl").
+
+-compile(export_all).
+
+'404_test'() ->
+ Response = ewts_client:request("/404_error"),
+ ?assertEqual(Response#response.code, 404).
+
+'501_test'() ->
+ Response = ewts_client:request("/blog/post/12/comment/231"),
+ ?assertEqual(Response#response.code, 501),
+
+ Response2 = ewts_client:request("/some_template.html"),
+ ?assertEqual(Response2#response.code, 501).
+
+cookie_test() ->
+ Response = ewts_client:request("/widgets/create"),
+ Cookie = hd(Response#response.cookies),
+ Response2 = ewts_client:request("/widgets/create"),
+ Cookie2 = hd(Response2#response.cookies),
+ ?assertEqual(Cookie, Cookie2),
+
+ ewts_client:clear(),
+ Response3 = ewts_client:request("/widgets/create"),
+ Cookie3 = hd(Response3#response.cookies),
+ ?assertNot(Cookie == Cookie3).
View
7 lib/myapp-0.1/test/primary_suite.conf
@@ -0,0 +1,7 @@
+%% dispatcher tests
+{dispatcher, "/", {main, home, ""}, []}.
+{dispatcher, "/blog/rss", {rss, blog, ""}, []}.
+{dispatcher, "/blog/post/12/comment/231", {blog, comment, ""},
+ [{dispatcher_params, [{post_no, "12"}, {comment_no, "231"}]}]}.
+{dispatcher, "/not_found", {error, 404, "/not_found"}, []}.
+{dispatcher, "/some_template.html", {view, "some_site.html"}, []}.
View
12 lib/myapp-0.1/test/tpl_tests.erl
@@ -0,0 +1,12 @@
+-module(tpl_tests).
+
+-include_lib("eunit/include/eunit.hrl").
+-include_lib("ewts/include/request.hrl").
+
+-compile(export_all).
+
+tpl_test() ->
+ Response = ewts_client:request("/tpl_top_down"),
+
+ ?assertEqual(Response#response.code, 200),
+ ?assertNot(string:str(Response#response.body, "Inserted template") == 0).
View
1 lib/myapp-0.1/vsn.mk
@@ -0,0 +1 @@
+VSN=1

0 comments on commit 36dd6a1

Please sign in to comment.
Something went wrong with that request. Please try again.