diff --git a/.gitignore b/.gitignore index c009caec..c1a25b81 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,41 @@ -.rebar3 -_build -.DS_Store -generated.config -generated.conf -ebin/* -.eunit -deps -cuttlefish +# rebar and tools +*.crashdump +.cache/ +.rebar3/ +_build/ +_checkouts +rebar.lock +log/ + +# work environments +*.bak +*.dump +*.iml +*.plt *.sublime-project *.sublime-workspace -log -.local_dialyzer_plt +*.tmp +*.txt +*_plt +*~ +.DS_Store +.idea/ +.project +.settings/ +.tm_properties erln8.config +tmp/ + +# Erlang artifacts +*.app +*.beam +/doc/ +/ebin/ + +# build/test artifacts +/cuttlefish +generated.conf +generated.config +src/conf_parse.erl +src/cuttlefish_duration_parse.erl test_fixtures/escript_prune_test/generated.config/* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..4b70972d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,8 @@ +language: erlang +otp_release: + - 20.3.8 + - 21.3 + - 22.3 +script: + - chmod u+x rebar3 + - ./rebar3 do upgrade, compile, xref, dialyzer, eunit diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..b6e9f546 --- /dev/null +++ b/Makefile @@ -0,0 +1,111 @@ +# ------------------------------------------------------------------- +# +# Copyright (c) 2013-2017 Basho Technologies, Inc. +# +# This file is provided to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file +# except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# +# ------------------------------------------------------------------- + +prj_dir := $(CURDIR) +cache := $(prj_dir)/.cache + +dl_tgts := +# +# tools +# +ifeq ($(REBAR3),) +REBAR3 := $(cache)/rebar3 +dl_tgts += $(REBAR3) +endif +export REBAR3 +CP := /bin/cp -p +RM := /bin/rm + +.PHONY : check clean clean-deps clean-docs clean-dist compile dialyzer \ + default docs escript prereqs test validate veryclean xref + +default : compile + +prereqs :: + +compile :: prereqs + $(REBAR3) as prod compile + +check :: prereqs + $(REBAR3) as check do brt-deps --check, dialyzer, xref + +clean :: prereqs + $(REBAR3) clean --all + +clean-deps :: clean + $(RM) -rf $(prj_dir)/_build + +clean-docs :: + $(REBAR3) as docs clean + +clean-dist :: + $(RM) -f $(prj_dir)/cuttlefish + +docs :: prereqs + $(REBAR3) edoc + +dialyzer :: prereqs + $(REBAR3) as check dialyzer + +escript :: prereqs + $(REBAR3) as prod escriptize + $(CP) _build/prod/bin/cuttlefish $(prj_dir)/cuttlefish + +test :: prereqs + $(REBAR3) eunit + +validate :: prereqs + $(REBAR3) as validate compile + +veryclean :: clean clean-docs clean-deps clean-dist + $(RM) -rf $(cache) + +xref :: prereqs + $(REBAR3) as check xref + +# +# how to download files if we need to +# +ifneq ($(dl_tgts),) + +dlcmd := $(shell which wget 2>/dev/null || true) +ifneq ($(wildcard $(dlcmd)),) +dlcmd += -O +else +dlcmd := $(shell which curl 2>/dev/null || true) +ifneq ($(wildcard $(dlcmd)),) +dlcmd += -o +else +$(error Need wget or curl to download files) +endif +endif + +prereqs :: $(dl_tgts) + +veryclean :: + $(RM) -rf $(dl_tgts) + +$(cache)/rebar3 : + @test -d $(@D) || /bin/mkdir -p $(@D) + @echo Downloading $@ ... + @$(dlcmd) $@ https://s3.amazonaws.com/rebar3/rebar3 + @/bin/chmod +x $@ + +endif # dl_tgts diff --git a/README.md b/README.md index b8469715..d6894634 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,21 @@ # Cuttlefish +[![Build Status](https://travis-ci.org/Kyorai/cuttlefish.svg?branch=develop)](https://travis-ci.org/Kyorai/cuttlefish) +[![Build status (windows)](https://ci.appveyor.com/api/projects/status/lo8ssrixiqmufc3j?svg=true)](https://ci.appveyor.com/project/Licenser/cuttlefish) +[![Coverage Status](https://coveralls.io/repos/github/Kyorai/cuttlefish/badge.svg?branch=master)](https://coveralls.io/github/Kyorai/cuttlefish) +[![Hex version](https://img.shields.io/hexpm/v/cuttlefish.svg "Hex version")](https://hex.pm/packages/cuttlefish) + Cuttlefish is a library for Erlang applications that wish to walk the fine line between Erlang `app.config`s and a sysctl-like syntax. The name is a pun on the pronunciation of 'sysctl' and jokes are better explained. +This repository retains full history of the original repository, [basho/cuttlefish/](https://github.com/basho/cuttlefish/), +but intentionally cut ties with that repo to avoid confusion as to +where is the most up-to-date, maintained version is. + +This is the repository used to produce [Hex.pm releases](https://hex.pm/packages/cuttlefish) of the project. + ## Riak Disclaimer While this readme and test suite is Riak-heavy, the fact is that this @@ -88,7 +99,14 @@ https://github.com/basho/cuttlefish/wiki/Cuttlefish-for-Application-Users * [node_package](https://github.com/basho/cuttlefish/wiki/Cuttlefish-for-node_package-users) * [non node_package](https://github.com/basho/cuttlefish/wiki/Cuttlefish-for-non-node_package-users) - ## Current Status Cuttlefish is ready for production deployments. + +## Re-generating parser + +``` +rebar3 as dev neotoma +``` + +Please see the *NOTE* in `src/conf_parse.peg` as well. diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 00000000..1fd2b092 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,9 @@ +build: off + +install: + - curl -OL https://s3.amazonaws.com/rebar3/rebar3 + +test_script: + - escript ./rebar3 do compile,eunit,dialyzer + +deploy: false diff --git a/rebar.config b/rebar.config index c7435bad..9468bfb9 100644 --- a/rebar.config +++ b/rebar.config @@ -1,20 +1,35 @@ -{minimum_otp_vsn, "R16"}. +{minimum_otp_vsn, "20"}. {erl_opts, [warnings_as_errors, {parse_transform, lager_transform}, debug_info, warn_untyped_record]}. -{deps, [getopt, {lager, "~>3.6.0"}]}. +{deps, [ + {lager, {git, "git://github.com/erlang-lager/lager.git", {tag, "3.8.0"}}}, + {getopt, {git, "git://github.com/jcomellas/getopt.git", {tag, "v1.0.1"}}}]}. {escript_emu_args, "%%! -escript main cuttlefish_escript +S 1 +A 0\n"}. -{escript_incl_apps, [goldrush, getopt, lager, cuttlefish]}. +{escript_incl_apps, [getopt, lager, cuttlefish]}. {escript_main_app, cuttlefish}. -{provider_hooks, [{post, [{compile, {default, escriptize}}]}]}. +% {provider_hooks, [{post, [{compile, {default, escriptize}}]}]}. + +{xref_checks, [ + undefined_function_calls, + undefined_functions, + locals_not_used, + deprecated_function_calls, + deprecated_functions +]}. {eunit_opts, [verbose]}. {cover_enabled, true}. +{cover_print_enabled, true}. +{cover_export_enabled, true}. {profiles, [{dev, [{deps, [neotoma]}, - {plugins, [rebar3_neotoma_plugin]}]}]}. + {plugins, [rebar3_neotoma_plugin]}]}, + {test, [{deps, [bbmustache]}]}]}. + + diff --git a/rebar.config.script b/rebar.config.script new file mode 100644 index 00000000..20a57a8b --- /dev/null +++ b/rebar.config.script @@ -0,0 +1,15 @@ +%% vim:ft=erlang: + +case os:getenv("TRAVIS_JOB_ID") of + false -> CONFIG; + JobId -> + %% coveralls.io. + [{plugins, [{coveralls, + {git, "https://github.com/markusn/coveralls-erl", + {branch, "master"}}}]} + ,{coveralls_coverdata, "_build/test/cover/eunit.coverdata"} + ,{coveralls_service_name, "travis-ci"} + ,{coveralls_service_job_id, JobId} + |CONFIG + ] +end. diff --git a/rebar.lock b/rebar.lock deleted file mode 100644 index c6b3b3f0..00000000 --- a/rebar.lock +++ /dev/null @@ -1,10 +0,0 @@ -{"1.1.0", -[{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}, - {<<"goldrush">>,{pkg,<<"goldrush">>,<<"0.1.9">>},1}, - {<<"lager">>,{pkg,<<"lager">>,<<"3.6.3">>},0}]}. -[ -{pkg_hash,[ - {<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}, - {<<"goldrush">>, <<"F06E5D5F1277DA5C413E84D5A2924174182FB108DABB39D5EC548B27424CD106">>}, - {<<"lager">>, <<"FE78951D174616273F87F0DBC3374D1430B1952E5EFC4E1C995592D30A207294">>}]} -]. diff --git a/rebar3 b/rebar3 new file mode 100755 index 00000000..e550663a Binary files /dev/null and b/rebar3 differ diff --git a/src/conf_parse.erl b/src/conf_parse.erl index c6e2a286..7066d496 100644 --- a/src/conf_parse.erl +++ b/src/conf_parse.erl @@ -17,9 +17,7 @@ %% ------------------------------------------------------------------- %% -%% conf_parse: for all your .conf parsing needs. -%% -%% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved. +%% Copyright (c) 2013-2017 Basho Technologies, Inc. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file @@ -36,10 +34,13 @@ %% under the License. %% %% ------------------------------------------------------------------- +%% +%% This is a generated file, changes should be made to conf_parse.peg. +%% -%% This module implements the parser for a sysctl-style -%% configuration format. Example: +%% This module implements the parser for a sysctl-style configuration format. %% +%% Example: %% ``` %% riak.local.node = riak@127.0.0.1 %% riak.local.http = 127.0.0.1:8098 @@ -56,17 +57,18 @@ %% %% Other modules in this application interpret and validate the %% result of a successful parse. -%% @end +%% + -define(line, true). -define(FMT(F,A), lists:flatten(io_lib:format(F,A))). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. %% @doc Only let through lines that are not comments or whitespace. is_setting(ws) -> false; +is_setting([ws]) -> false; is_setting(comment) -> false; is_setting(_) -> true. @@ -79,7 +81,7 @@ unescape_dots([C|Rest]) -> -ifdef(TEST). file_test() -> - Conf = conf_parse:file("../test/riak.conf"), + Conf = conf_parse:file(cuttlefish_test_util:test_file("riak.conf")), ?assertEqual([ {["ring_size"],"32"}, {["anti_entropy"],"debug"}, @@ -92,18 +94,35 @@ file_test() -> ok. utf8_test() -> - Conf = conf_parse:parse("setting = thingÅ’\n"), + Conf = conf_parse:parse("setting = thing" ++ [338] ++ "\n"), ?assertEqual([{["setting"], {error, {conf_to_latin1, 1}} }], Conf), ok. + +gh_1_two_tab_test() -> + Conf = conf_parse:parse("setting0 = thing0\n\t\t\nsetting1 = thing1\n"), + ?assertEqual([ + {["setting0"],"thing0"}, + {["setting1"],"thing1"} + ], Conf), + ok. + +gh_1_three_tab_test() -> + Conf = conf_parse:parse("setting0 = thing0\n\t\t\t\nsetting1 = thing1\n"), + ?assertEqual([ + {["setting0"],"thing0"}, + {["setting1"],"thing1"} + ], Conf), + ok. + -endif. -spec file(file:name()) -> any(). file(Filename) -> case file:read_file(Filename) of {ok,Bin} -> parse(Bin); Err -> Err end. -spec parse(binary() | list()) -> any(). -parse(List) when is_list(List) -> parse(list_to_binary(List)); +parse(List) when is_list(List) -> parse(unicode:characters_to_binary(List)); parse(Input) when is_binary(Input) -> _ = setup_memo(), Result = case 'config'(Input,{{line,1},{column,1}}) of @@ -172,7 +191,7 @@ parse(Input) when is_binary(Input) -> -spec 'ws'(input(), index()) -> parse_result(). 'ws'(Input, Index) -> - p(Input, Index, 'ws', fun(I,D) -> (p_charclass(<<"[\s\t]">>))(I,D) end, fun(_Node, _Idx) ->ws end). + p(Input, Index, 'ws', fun(I,D) -> (p_one_or_more(p_charclass(<<"[\s\t]">>)))(I,D) end, fun(_Node, _Idx) ->ws end). diff --git a/src/conf_parse.peg b/src/conf_parse.peg index f23d2ec3..c933dff8 100644 --- a/src/conf_parse.peg +++ b/src/conf_parse.peg @@ -3,6 +3,7 @@ %% conf_parse: for all your .conf parsing needs. %% %% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved. +%% Copyright (c) 2019 Pivotal Software, Inc. All rights reserved. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file @@ -20,6 +21,24 @@ %% %% ------------------------------------------------------------------- +% ------------------------------------------------------------------- +% NOTE: IMPORTANT +% +% After using neotoma to re-generate conf_parse.erl, you MUST +% edit that file to change the exported file/1 function to this code: +% +% -spec file(file:name()) -> any(). +% file(Filename) -> +% AbsFilename = filename:absname(Filename), +% case erl_prim_loader:get_file(AbsFilename) of +% {ok, Bin, _} -> parse(Bin); +% error -> {error, undefined} +% end. +% +% The reason is that the above code allows for reading cuttlefish +% schemas from .ez archives +% ------------------------------------------------------------------- + %% A configuration file may have zero-or-more lines. config <- line* %{ [ L || L <- Node, is_setting(L) ] @@ -75,16 +94,14 @@ crlf <- "\r"? "\n" `ws`; eof <- !. `ws`; %% Whitespace is either spaces or tabs. -ws <- [ \t] `ws`; +ws <- [ \t]+ `ws`; % Erlang code %{ %% ------------------------------------------------------------------- %% -%% conf_parse: for all your .conf parsing needs. -%% -%% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved. +%% Copyright (c) 2013-2017 Basho Technologies, Inc. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file @@ -101,10 +118,13 @@ ws <- [ \t] `ws`; %% under the License. %% %% ------------------------------------------------------------------- +%% +%% This is a generated file, changes should be made to conf_parse.peg. +%% -%% This module implements the parser for a sysctl-style -%% configuration format. Example: +%% This module implements the parser for a sysctl-style configuration format. %% +%% Example: %% ``` %% riak.local.node = riak@127.0.0.1 %% riak.local.http = 127.0.0.1:8098 @@ -121,17 +141,18 @@ ws <- [ \t] `ws`; %% %% Other modules in this application interpret and validate the %% result of a successful parse. -%% @end +%% + -define(line, true). -define(FMT(F,A), lists:flatten(io_lib:format(F,A))). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. %% @doc Only let through lines that are not comments or whitespace. is_setting(ws) -> false; +is_setting([ws]) -> false; is_setting(comment) -> false; is_setting(_) -> true. @@ -144,7 +165,7 @@ unescape_dots([C|Rest]) -> -ifdef(TEST). file_test() -> - Conf = conf_parse:file("../test/riak.conf"), + Conf = conf_parse:file(cuttlefish_test_util:test_file("riak.conf")), ?assertEqual([ {["ring_size"],"32"}, {["anti_entropy"],"debug"}, @@ -162,5 +183,22 @@ utf8_test() -> {error, {conf_to_latin1, 1}} }], Conf), ok. + +gh_1_two_tab_test() -> + Conf = conf_parse:parse("setting0 = thing0\n\t\t\nsetting1 = thing1\n"), + ?assertEqual([ + {["setting0"],"thing0"}, + {["setting1"],"thing1"} + ], Conf), + ok. + +gh_1_three_tab_test() -> + Conf = conf_parse:parse("setting0 = thing0\n\t\t\t\nsetting1 = thing1\n"), + ?assertEqual([ + {["setting0"],"thing0"}, + {["setting1"],"thing1"} + ], Conf), + ok. + -endif. %} diff --git a/src/cuttlefish.app.src b/src/cuttlefish.app.src index 2f58f75d..92bbaaa0 100644 --- a/src/cuttlefish.app.src +++ b/src/cuttlefish.app.src @@ -1,15 +1,10 @@ -{application, cuttlefish, - [ - {description, "cuttlefish configuration abstraction"}, - {vsn, git}, - {registered, []}, - {applications, [ - kernel, - stdlib - ]}, - {env, []}, - - {maintainers,[]}, - {licenses,["Apache"]}, - {links,[{"Github","https://github.com/tsloughter/cuttlefish"}]} - ]}. +{application, cuttlefish, [ + {description, "cuttlefish configuration abstraction"}, + {vsn, git}, + {registered, []}, + {applications, [kernel, sasl, stdlib, syntax_tools, getopt, lager]}, + {env, []}, + {modules, []}, + {licenses, ["Apache 2.0"]}, + {links, [{"Github", "https://github.com/basho/cuttlefish"}]} +]}. diff --git a/src/cuttlefish.erl b/src/cuttlefish.erl index 90caa6ae..f7e78ca8 100644 --- a/src/cuttlefish.erl +++ b/src/cuttlefish.erl @@ -24,7 +24,6 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. -export([ diff --git a/src/cuttlefish_advanced.erl b/src/cuttlefish_advanced.erl index d089daa5..05044d63 100644 --- a/src/cuttlefish_advanced.erl +++ b/src/cuttlefish_advanced.erl @@ -26,7 +26,6 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. %% @doc this function overlays the values in proplist 'AdvancedConfig' diff --git a/src/cuttlefish_bytesize.erl b/src/cuttlefish_bytesize.erl index 5262a526..254a75f0 100644 --- a/src/cuttlefish_bytesize.erl +++ b/src/cuttlefish_bytesize.erl @@ -28,7 +28,6 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. -export([parse/1, to_string/1]). diff --git a/src/cuttlefish_conf.erl b/src/cuttlefish_conf.erl index 616f7a5a..49f6a9e2 100644 --- a/src/cuttlefish_conf.erl +++ b/src/cuttlefish_conf.erl @@ -1,8 +1,6 @@ %% ------------------------------------------------------------------- %% -%% cuttlefish_conf: handles the reading and generation of .conf files -%% -%% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved. +%% Copyright (c) 2013-2017 Basho Technologies, Inc. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file @@ -19,6 +17,10 @@ %% under the License. %% %% ------------------------------------------------------------------- + +%% +%% @doc Handles the reading and generation of .conf files. +%% -module(cuttlefish_conf). -export([ @@ -206,7 +208,6 @@ remove_duplicates(Conf) -> -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. -ifdef(TEST). @@ -298,27 +299,31 @@ generate_comments_test() -> ?assertEqual(["## Hi!", "## Bye!", "## ", "## Acceptable values:", "## - text"], Comments). duplicates_test() -> - Conf = file("../test/multi1.conf"), + Conf = file(cuttlefish_test_util:test_file("multi1.conf")), ?assertEqual(2, length(Conf)), ?assertEqual("3", proplists:get_value(["a","b","c"], Conf)), ?assertEqual("1", proplists:get_value(["a","b","d"], Conf)), ok. duplicates_multi_test() -> - Conf = files(["../test/multi1.conf", "../test/multi2.conf"]), + Conf = files([ + cuttlefish_test_util:test_file("multi1.conf"), + cuttlefish_test_util:test_file("multi2.conf") ]), ?assertEqual(2, length(Conf)), ?assertEqual("4", proplists:get_value(["a","b","c"], Conf)), ?assertEqual("1", proplists:get_value(["a","b","d"], Conf)), ok. files_one_nonent_test() -> - Conf = files(["../test/multi1.conf", "../test/nonent.conf"]), - ?assertEqual({errorlist,[{error, {file_open, {"../test/nonent.conf", enoent}}}]}, Conf), + NonEnt = cuttlefish_test_util:test_file("nonent.conf"), + Conf = files([cuttlefish_test_util:test_file("multi1.conf"), NonEnt]), + ?assertEqual({errorlist,[{error, {file_open, {NonEnt, enoent}}}]}, Conf), ok. files_incomplete_parse_test() -> - Conf = file("../test/incomplete.conf"), - ?assertEqual({errorlist, [{error, {conf_syntax, {"../test/incomplete.conf", {3, 1}}}}]}, Conf), + File = cuttlefish_test_util:test_file("incomplete.conf"), + Conf = file(File), + ?assertEqual({errorlist, [{error, {conf_syntax, {File, {3, 1}}}}]}, Conf), ok. generate_element_level_advanced_test() -> diff --git a/src/cuttlefish_datatypes.erl b/src/cuttlefish_datatypes.erl index f79779ea..71a99052 100644 --- a/src/cuttlefish_datatypes.erl +++ b/src/cuttlefish_datatypes.erl @@ -23,7 +23,6 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. -type datatype() :: integer | diff --git a/src/cuttlefish_duration.erl b/src/cuttlefish_duration.erl index 06ec15d9..1cdc48b3 100644 --- a/src/cuttlefish_duration.erl +++ b/src/cuttlefish_duration.erl @@ -28,7 +28,6 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. -export([parse/1, parse/2, to_string/2]). diff --git a/src/cuttlefish_duration_parse.erl b/src/cuttlefish_duration_parse.erl index 2f26ae95..58117c71 100644 --- a/src/cuttlefish_duration_parse.erl +++ b/src/cuttlefish_duration_parse.erl @@ -38,7 +38,12 @@ -define(FLATTEN(S), binary_to_list(iolist_to_binary(S))). -spec file(file:name()) -> any(). -file(Filename) -> case file:read_file(Filename) of {ok,Bin} -> parse(Bin); Err -> Err end. +file(Filename) -> + AbsFilename = filename:absname(Filename), + case erl_prim_loader:get_file(AbsFilename) of + {ok, Bin, _} -> parse(Bin); + error -> {error, undefined} + end. -spec parse(binary() | list()) -> any(). parse(List) when is_list(List) -> parse(list_to_binary(List)); diff --git a/src/cuttlefish_effective.erl b/src/cuttlefish_effective.erl index c992734f..f94722da 100644 --- a/src/cuttlefish_effective.erl +++ b/src/cuttlefish_effective.erl @@ -27,7 +27,6 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. -spec build(cuttlefish_conf:conf(), cuttlefish_schema:schema(), [proplists:property()]) -> [string()]. diff --git a/src/cuttlefish_enum.erl b/src/cuttlefish_enum.erl index 0b5eb18c..75ba20b5 100644 --- a/src/cuttlefish_enum.erl +++ b/src/cuttlefish_enum.erl @@ -30,7 +30,6 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. -export([ diff --git a/src/cuttlefish_error.erl b/src/cuttlefish_error.erl index 3a760101..ca890cf9 100644 --- a/src/cuttlefish_error.erl +++ b/src/cuttlefish_error.erl @@ -37,7 +37,6 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. %% We'll be calling this a lot from `xlate' diff --git a/src/cuttlefish_escript.erl b/src/cuttlefish_escript.erl index 81c932c6..079ab11f 100644 --- a/src/cuttlefish_escript.erl +++ b/src/cuttlefish_escript.erl @@ -27,7 +27,6 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. cli_options() -> @@ -38,9 +37,12 @@ cli_options() -> {dest_dir, $d, "dest_dir", string, "specifies the directory to write the config file to"}, {dest_file, $f, "dest_file", {string, "app"}, "the file name to write"}, {schema_dir, $s, "schema_dir", string, "a directory containing .schema files"}, + %% one or more schema file paths {schema_file, $i, "schema_file", string, "individual schema file, will be processed in command line order, after -s"}, - {conf_file, $c, "conf_file", string, "a cuttlefish conf file, multiple files allowed"}, - {app_config, $a, "app_config", string, "the advanced erlangy app.config"}, + %% one or more sysctl-style configuration file paths + {conf_file, $c, "conf_file", string, "a cuttlefish conf file path, multiple files allowed"}, + %% overrides advanced.config file path + {advanced_conf_file, $a, "advanced_conf_file", string, "the advanced config file path"}, {log_level, $l, "log_level", {string, "notice"}, "log level for cuttlefish output"}, {print_schema, $p, "print", undefined, "prints schema mappings on stderr"}, {max_history, $m, "max_history", {integer, 3}, "the maximum number of generated config files to keep"} @@ -83,6 +85,8 @@ main(Args) -> lager:start(), lager:debug("Cuttlefish set to debug level logging"), + lager:debug("Parsed arguments: ~p", [ParsedArgs]), + case Command of help -> print_help(); @@ -107,10 +111,11 @@ effective(ParsedArgs) -> case {AppConfigExists, VMArgsExists} of {false, false} -> - AdvancedConfigFile = filename:join(EtcDir, "advanced.config"), + AdvancedConfigFile = proplists:get_value(advanced_conf_file, ParsedArgs, filename:join(EtcDir, "advanced.config")), + lager:debug("Will look for advanced.config at '~s'", [AdvancedConfigFile]), AdvConfig = case filelib:is_file(AdvancedConfigFile) of true -> - lager:debug("~s/advanced.config detected, overlaying proplists", [EtcDir]), + lager:debug("~s detected, overlaying proplists", [AdvancedConfigFile]), case file:consult(AdvancedConfigFile) of {ok, [AdvancedConfig]} -> AdvancedConfig; @@ -244,7 +249,7 @@ generate(ParsedArgs) -> {true, true} -> lager:info("~s and ~s exists, disabling cuttlefish.", [ExistingAppConfigName, ExistingVMArgsName]), lager:info("If you'd like to know more about cuttlefish, check your local library!", []), - lager:info(" or see http://github.com/basho/cuttlefish", []), + lager:info(" or see http://github.com/Kyorai/cuttlefish", []), {ExistingAppConfigName, ExistingVMArgsName}; {true, false} -> lager:info("~s exists, generating vm.args", [ExistingAppConfigName]), @@ -357,7 +362,6 @@ engage_cuttlefish(ParsedArgs) -> lager:debug("Generating config in: ~p", [Destination]), Schema = load_schema(ParsedArgs), - Conf = load_conf(ParsedArgs), NewConfig = case cuttlefish_generator:map(Schema, Conf) of {error, Phase, {errorlist, Errors}} -> @@ -367,10 +371,11 @@ engage_cuttlefish(ParsedArgs) -> ValidConfig -> ValidConfig end, - AdvancedConfigFile = filename:join(EtcDir, "advanced.config"), + AdvancedConfigFile = proplists:get_value(advanced_conf_file, ParsedArgs, filename:join(EtcDir, "advanced.config")), + lager:debug("AdvancedConfigFile: ~p", [AdvancedConfigFile]), FinalConfig = case filelib:is_file(AdvancedConfigFile) of true -> - lager:info("~s/advanced.config detected, overlaying proplists", [EtcDir]), + lager:info("advanced config file is detected at ~s, overlaying proplists", [AdvancedConfigFile]), case file:consult(AdvancedConfigFile) of {ok, [AdvancedConfig]} -> cuttlefish_advanced:overlay(NewConfig, AdvancedConfig); diff --git a/src/cuttlefish_flag.erl b/src/cuttlefish_flag.erl index b0f60450..c37ce153 100644 --- a/src/cuttlefish_flag.erl +++ b/src/cuttlefish_flag.erl @@ -25,7 +25,6 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. -export([ diff --git a/src/cuttlefish_generator.erl b/src/cuttlefish_generator.erl index dc7c02fd..6d8dd189 100644 --- a/src/cuttlefish_generator.erl +++ b/src/cuttlefish_generator.erl @@ -1,8 +1,6 @@ %% ------------------------------------------------------------------- %% -%% cuttlefish_generator: this is where the action is -%% -%% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved. +%% Copyright (c) 2013-2017 Basho Technologies, Inc. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file @@ -19,11 +17,14 @@ %% under the License. %% %% ------------------------------------------------------------------- + +%% +%% @doc This is where the action is. +%% -module(cuttlefish_generator). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. -define(FMT(F,A), lists:flatten(io_lib:format(F,A))). @@ -722,9 +723,9 @@ add_defaults_test() -> map_test() -> lager:start(), - Schema = cuttlefish_schema:file("../test/riak.schema"), + Schema = cuttlefish_schema:file(cuttlefish_test_util:test_file("riak.schema")), - Conf = conf_parse:file("../test/riak.conf"), + Conf = conf_parse:file(cuttlefish_test_util:test_file("riak.conf")), NewConfig = map(Schema, Conf), @@ -749,7 +750,7 @@ map_test() -> minimal_map_test() -> lager:start(), - Schema = cuttlefish_schema:file("../test/riak.schema"), + Schema = cuttlefish_schema:file(cuttlefish_test_util:test_file("riak.schema")), Conf = [{["ring_size"], "32"}, {["anti_entropy"], "debug"}], NewConfig = minimal_map(Schema, Conf), diff --git a/src/cuttlefish_mapping.erl b/src/cuttlefish_mapping.erl index 3207c25d..669fb28a 100644 --- a/src/cuttlefish_mapping.erl +++ b/src/cuttlefish_mapping.erl @@ -23,7 +23,6 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. -record(mapping, { diff --git a/src/cuttlefish_rebar_plugin.erl b/src/cuttlefish_rebar_plugin.erl index e262303c..fdaeaea8 100644 --- a/src/cuttlefish_rebar_plugin.erl +++ b/src/cuttlefish_rebar_plugin.erl @@ -1,9 +1,6 @@ %% ------------------------------------------------------------------- %% -%% cuttlefish_rebar_plugin: generates an application's default .conf -%% as part of the build -%% -%% Copyright (c) 2013 Basho Technologies, Inc. +%% Copyright (c) 2013-2017 Basho Technologies, Inc. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file @@ -21,75 +18,120 @@ %% %% ------------------------------------------------------------------- +%% +%% @doc Rebar cuttlefish plugin to generate an application's default .conf +%% file as part of the build. +%% +%% For now, this is specific to Rebar2, but it's been refactored to allow it +%% to be extended to work with Rebar3 as well. +%% When we decide how we want to use it, we should do that. +%% -module(cuttlefish_rebar_plugin). -export([ - generate/2 + generate/2, + init/1 ]). %% =================================================================== -%% Public API +%% Rebar2 Plugin API %% =================================================================== -generate(Config0, ReltoolFile) -> - case should_i_run(Config0, ReltoolFile) of - {ok, Config, ReltoolConfig} -> - TargetDir = rebar_rel_utils:get_target_dir(Config, ReltoolConfig), - %% Finally, overlay the files specified by the overlay section - case lists:keyfind(overlay, 1, ReltoolConfig) of - {overlay, Overlays} when is_list(Overlays) -> - SchemaOverlays = lists:filter(fun(Overlay) -> - element(1, Overlay) =:= template - andalso filename:extension(element(3, Overlay)) =:= ".schema" - end, - Overlays), +%% +%% @doc Rebar2 plugin. +%% +generate(ConfigIn, ReltoolFile) -> + case rebar_info() of + {2, _, RelMod} = Inf -> + case RelMod:is_rel_dir() of + {true, _} -> + %% Load the reltool configuration from the file + {RebarConfig, ReltoolConfig} = + RelMod:load_config(ConfigIn, ReltoolFile), + rebar2run(Inf, RebarConfig, ReltoolConfig); + _ -> + ok + end; + _ -> + ok + end. - Schemas = lists:sort([ - lists:flatten(filename:join(TargetDir, element(3, Schema))) - || Schema <- SchemaOverlays]), +%% =================================================================== +%% Rebar3 Plugin API +%% =================================================================== - io:format("Schema: ~p~n", [Schemas]), +%% +%% @doc Rebar3 provider initialization. +%% +init(State) -> + {ok, State}. - case cuttlefish_schema:files(Schemas) of - {errorlist, _Es} -> - %% These errors were already printed - error; - {_Translations, Mappings, _Validators} -> - make_default_file(Config, TargetDir, Mappings) - end; +%% =================================================================== +%% Internal +%% =================================================================== - false -> - %%io:format("No {overlay, [...]} found in reltool.config.\n", []); - ok; - _ -> - io:format("{overlay, [...]} entry in reltool.config " - "must be a list.\n", []) - end, - ok; - no -> - ok - end, - ok. +rebar2run({_Vsn, CfgMod, RelMod}, RebarConfig, ReltoolConfig) -> + TargetDir = RelMod:get_target_dir(RebarConfig, ReltoolConfig), + %% Overlay the files specified by the overlay section + case lists:keyfind(overlay, 1, ReltoolConfig) of + {overlay, Overlays} when is_list(Overlays) -> + SchemaOverlays = lists:filter( + fun(Overlay) -> + element(1, Overlay) =:= template andalso + filename:extension(element(3, Overlay)) =:= ".schema" + end, Overlays), -make_default_file(Config, TargetDir, Mappings) -> - %% I really wanted this to default to the application name. The problem - %% is that the type of application that uses cuttlefish is also the kind - %% that doesn't have an .app.src file, so rebar doesn't get it. - %% I could have done something with cwd, but I didn't like that because you - %% could be building anywhere. So, cuttlefish it is. he's pretty cool anyway. - File = rebar_config:get_local(Config, cuttlefish_filename, "cuttlefish.conf"), - Filename = filename:join([TargetDir, "etc", File]), + Schemas = lists:sort([ + lists:flatten(filename:join(TargetDir, element(3, Schema))) + || Schema <- SchemaOverlays]), - cuttlefish_conf:generate_file(Mappings, Filename), - ok. + io:format("Schema: ~p~n", [Schemas]), -%% Only run for rel directory -should_i_run(Config0, ReltoolFile) -> - case rebar_rel_utils:is_rel_dir() of - {true, _} -> - %% Load the reltool configuration from the file - {Config, ReltoolConfig} = rebar_rel_utils:load_config(Config0, ReltoolFile), - {ok, Config, ReltoolConfig}; + case cuttlefish_schema:files(Schemas) of + {errorlist, _Es} -> + %% These errors were already printed + error; + {_Translations, Mappings, _Validators} -> + %% I really wanted this to default to the application name. + %% The problem is that the type of application that uses + %% cuttlefish is also the kind that doesn't have an + %% .app.src file, so rebar doesn't get it. + %% I could have done something with cwd, but I didn't like + %% that because you could be building anywhere. + %% So, cuttlefish it is. he's pretty cool anyway. + File = CfgMod:get_local( + RebarConfig, cuttlefish_filename, "cuttlefish.conf"), + Filename = filename:join([TargetDir, "etc", File]), + cuttlefish_conf:generate_file(Mappings, Filename), + ok + end; false -> - no + io:put_chars("No {overlay, [...]} found in reltool.config.\n"); + _ -> + io:put_chars( + "{overlay, [...]} entry in reltool.config must be a list.\n") + end. + +-spec rebar_info() -> non_neg_integer() | tuple(). +% +% Returns the version and, if the version's supported, the modules needed to +% access Rebar. ALL Rebar modules are returned as variables, as we don't want +% any dependencies on Rebar, nor do we want to listen to xref and dialyzer +% whining about unknown functions. +% +rebar_info() -> + Vsn = case application:get_key(rebar, vsn) of + {ok, [Ch | _] = VsnStr} when Ch >= $0 andalso Ch =< $9 -> + {N, _} = string:to_integer(VsnStr), + N; + _ -> + 0 + end, + case Vsn of + 2 -> + {Vsn, rebar_config, rebar_rel_utils}; + 3 -> + {Vsn, renar_api, rebar_state, rebar_app_info}; + _ -> + Vsn end. diff --git a/src/cuttlefish_schema.erl b/src/cuttlefish_schema.erl index 2185fa97..eff4d375 100644 --- a/src/cuttlefish_schema.erl +++ b/src/cuttlefish_schema.erl @@ -1,8 +1,6 @@ %% ------------------------------------------------------------------- %% -%% cuttlefish_schema: slurps schema files -%% -%% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved. +%% Copyright (c) 2013-2017 Basho Technologies, Inc. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file @@ -20,6 +18,9 @@ %% %% ------------------------------------------------------------------- +%% +%% @doc Slurps schema files. +%% -module(cuttlefish_schema). -export([files/1, strings/1]). @@ -29,9 +30,7 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). - - +-export([file/1]). -endif. -type schema() :: { @@ -120,7 +119,7 @@ count_mappings(Mappings) -> -spec file(string(), schema()) -> schema() | cuttlefish_error:errorlist(). file(Filename, Schema) -> - {ok, B} = file:read_file(Filename), + {ok, B, _} = erl_prim_loader:get_file(filename:absname(Filename)), %% latin-1 is easier to support generically. We'll revisit utf-8 %% support in the future. S = unicode:characters_to_list(B, latin1), @@ -324,13 +323,14 @@ comment_parser_test() -> bad_file_test() -> cuttlefish_lager_test_backend:bounce(), - {errorlist, ErrorList} = file("../test/bad_erlang.schema"), + BadSch = cuttlefish_test_util:test_file("bad_erlang.schema"), + {errorlist, ErrorList} = file(BadSch), Logs = cuttlefish_lager_test_backend:get_logs(), [L1|Tail] = Logs, [L2|[]] = Tail, ?assertMatch({match, _}, re:run(L1, "Error scanning erlang near line 10")), - ?assertMatch({match, _}, re:run(L2, "Error parsing schema: ../test/bad_erlang.schema")), + ?assertMatch({match, _}, re:run(L2, "Error parsing schema: " ++ BadSch)), ?assertEqual([ {error, {erl_scan, 10}} @@ -377,9 +377,9 @@ files_test() -> %% Loads them in reverse order, as things are overridden {Translations, Mappings, Validators} = files( [ - "../test/multi1.schema", - "../test/multi2.schema", - "../test/multi3.schema" + cuttlefish_test_util:test_file("multi1.schema"), + cuttlefish_test_util:test_file("multi2.schema"), + cuttlefish_test_util:test_file("multi3.schema") ]), ?assertEqual(6, length(Mappings)), diff --git a/src/cuttlefish_translation.erl b/src/cuttlefish_translation.erl index 5ce558d7..53a11179 100644 --- a/src/cuttlefish_translation.erl +++ b/src/cuttlefish_translation.erl @@ -1,8 +1,6 @@ %% ------------------------------------------------------------------- %% -%% cuttlefish_translation: models a cuttlefish translation -%% -%% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved. +%% Copyright (c) 2013-2017 Basho Technologies, Inc. %% %% This file is provided to you under the Apache License, %% Version 2.0 (the "License"); you may not use this file @@ -19,17 +17,18 @@ %% under the License. %% %% ------------------------------------------------------------------- + +%% models a cuttlefish translation -module(cuttlefish_translation). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. -record(translation, { - mapping::string(), - func::fun() - }). + mapping :: string(), + func = undefined :: translation_fun() | undefined +}). -type translation() :: #translation{}. -type translation_fun() :: fun(([proplists:property()]) -> any()). -type raw_translation() :: {translation, string(), translation_fun()} | {translation, string()}. @@ -41,7 +40,8 @@ is_translation/1, mapping/1, func/1, - replace/2]). + replace/2 +]). -spec parse(raw_translation()) -> translation() | cuttlefish_error:error(). parse({translation, Mapping}) -> diff --git a/src/cuttlefish_unit.erl b/src/cuttlefish_unit.erl index debaad1d..e67826f2 100644 --- a/src/cuttlefish_unit.erl +++ b/src/cuttlefish_unit.erl @@ -1,7 +1,87 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2013-2017 Basho Technologies, Inc. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% +%% @doc Support for unit testing. +%% @end -module(cuttlefish_unit). +%% Documented and/or Active API +-export([ + assert_config/3, + assert_error/1, + assert_error/3, + assert_error_in_phase/2, + assert_error_message/2, + assert_errors/2, + assert_errors/3, + assert_not_configured/2, + assert_valid_config/1, + generate_config/2, + generate_templated_config/3, + generate_templated_config/4, + lib_priv_dir/1, + lib_test_dir/1 +]). + +%% Historically Exported by -compile(export_all). +%% Commented out unless/until somebody screams. +%% None of these are used anywhere in Riak, nor are they mentioned in any +%% documentation. +%% +%% chase_message/3, +%% dump_to_file/2, +%% generate_config/3, +%% key_no_match/1, +%% path/2, +%% render_template/2 + +% I'm pretty sure these should be defined somewhere else, but where? +% These types are a rough guess, there doesn't seem to be an explicit +% statement on their specifications. +-export_type([ + mustache_ctx/0, + mustache_def/0, + mustache_key/0, + mustache_val/0 +]). +% Various mustache modules [claim to] accept the mustache_key() types. +% The type of mustache_val() could certainly be narrower, but to what? +-type mustache_ctx() :: [mustache_def()]. +-type mustache_def() :: {mustache_key(), mustache_val()}. +-type mustache_key() :: atom() | binary() | string(). +-type mustache_val() :: term(). + +% This file uses eunit's assert macros so inclusion is unconditional. +% However, eunit.hrl defines TEST by default, and we don't want that, so +% override the default behavior if not actually running eunit (or ct). +-ifndef(TEST). +-ifndef(NOTEST). +-define(NOTEST, true). +-endif. +-endif. -include_lib("eunit/include/eunit.hrl"). --compile([nowarn_export_all, export_all]). + +%% =================================================================== +%% Public API +%% =================================================================== generate_templated_config(FileName, Conf, Context) -> generate_templated_config(FileName, Conf, Context, {[], [], []}). @@ -16,37 +96,6 @@ generate_templated_config(FileName, Conf, Context, PreexistingSchema) -> Schema = cuttlefish_schema:merger(RenderedSchemas ++ [ { fun(_, _) -> PreexistingSchema end, ""} ]), cuttlefish_generator:map(Schema, Conf). -render_template(FileName, Context) -> - {ok, Bin} = file:read_file(FileName), - %% Stolen from rebar_templater:render/2 - %% Be sure to escape any double-quotes before rendering... - ReOpts = [global, {return, list}], - Str0 = re:replace(Bin, "\\\\", "\\\\\\", ReOpts), - Str1 = re:replace(Str0, "\"", "\\\\\"", ReOpts), - - %% the mustache module is only available in the context of a rebar run. - case {code:ensure_loaded(mustache), code:ensure_loaded(rebar_mustache)} of - {{module, mustache}, _} -> - mustache:render(Str1, dict:from_list(Context)); - {_, {module, rebar_mustache}} -> - rebar_mustache:render(Str1, dict:from_list(Context)); - _ -> - io:format("mustache and/or rebar_mustache module not loaded. " - "This test can only be run in a rebar context.~n") - end. - --spec generate_config(atom(), [string()]|string(), list()) -> list(). -generate_config(strings, SchemaStrings, Conf) -> - Schema = cuttlefish_schema:strings(SchemaStrings), - cuttlefish_generator:map(Schema, Conf); - -generate_config(string, SchemaString, Conf) -> - Schema = cuttlefish_schema:strings([SchemaString]), - cuttlefish_generator:map(Schema, Conf); - -generate_config(file, SchemaFile, Conf) -> - generate_config(SchemaFile, Conf). - -spec generate_config(string(), list()) -> list(). generate_config(SchemaFile, Conf) -> Schema = cuttlefish_schema:files([SchemaFile]), @@ -72,7 +121,8 @@ assert_config(Config, Path, Value) -> ?assertEqual({Path, Value}, {Path, nesting_error}); notset -> ?assertEqual({Path, Value}, {Path, notset}); - {ok, X} -> X + {ok, X} -> + X end, ?assertEqual({Path, Value}, {Path, ActualValue}). @@ -125,10 +175,66 @@ assert_error_message(Config, Message) -> {errorlist, Errors} = element(3, Config), chase_message(Message, Errors, Errors). +-spec lib_priv_dir(Module :: module()) -> string() | false. +%% +%% @doc Returns the path to an application's working "priv" directory. +%% +%% Module MUST be compiled from a file in one of the application's main source +%% directories, generally the project's "src" directory. +%% Module's code IS NOT explicitly loaded by this operation. +%% +%% The returned path is to the "priv" directory in the working instance of the +%% application, which may be in a number of places in different Rebar versions. +%% +%% `false' is returned if the path cannot be determined, or does not exist, +%% or is not a directory. +%% +%% Logically, this is analogous to +%% ``` +%% filename:join( +%% filename:dirname(filename:dirname(code:which(Module))), +%% "priv" ) +%% ''' +%% +lib_priv_dir(Module) -> + lib_sub_dir(Module, "priv"). + +-spec lib_test_dir(Module :: module()) -> string() | false. +%% +%% @doc Returns the path to an application's working "test" directory. +%% +%% Module MUST be compiled from a file in one of the application's main source +%% directories, generally the project's "src" directory. +%% Module's code IS NOT explicitly loaded by this operation. +%% +%% The returned path is to the "test" directory in the working instance of the +%% application, which may be in a number of places in different Rebar versions. +%% +%% `false' is returned if the path cannot be determined, or does not exist, +%% or is not a directory. +%% +%% Logically, this is analogous to +%% ``` +%% filename:join( +%% filename:dirname(filename:dirname(code:which(Module))), +%% "test" ) +%% ''' +%% +lib_test_dir(Module) -> + lib_sub_dir(Module, "test"). + +%% =================================================================== +%% Historically Exported +%% +%% Everything in this section should be moved to either the Public API +%% or Internal section or, in the cases of commented-out dead code, +%% deleted. +%% =================================================================== + chase_message(Message, [], Errors) -> erlang:exit({assert_error_message_failed, - [{expected, Message}, - {actual, Errors}]}); + [{expected, Message}, {actual, Errors}] }); + chase_message(Message, [{error, ErrorTerm}|T], Errors) -> case lists:flatten(cuttlefish_error:xlate(ErrorTerm)) of Message -> @@ -137,9 +243,37 @@ chase_message(Message, [{error, ErrorTerm}|T], Errors) -> chase_message(Message, T, Errors) end. --spec path(cuttlefish_variable:variable(), - [{ string() | atom() | binary() , term()}]) -> - {ok, any()} | notset | {error, bad_nesting}. +%%-spec dump_to_file(any(), string()) -> ok. +%%dump_to_file(ErlangTerm, Filename) -> +%% {ok, S} = file:open(Filename, [write,append]), +%% io:format(S, "~p~n", [ErlangTerm]), +%% _ = file:close(S), +%% ok. + +%%-spec generate_config(atom(), [string()]|string(), list()) -> list(). +%%generate_config(strings, SchemaStrings, Conf) -> +%% Schema = cuttlefish_schema:strings(SchemaStrings), +%% cuttlefish_generator:map(Schema, Conf); +%% +%%generate_config(string, SchemaString, Conf) -> +%% Schema = cuttlefish_schema:strings([SchemaString]), +%% cuttlefish_generator:map(Schema, Conf); +%% +%%generate_config(file, SchemaFile, Conf) -> +%% generate_config(SchemaFile, Conf). + +-spec key_no_match(string()) -> fun((atom() | string() | binary()) -> boolean()). +key_no_match(Key) -> + fun({E, _}) when is_atom(E) -> E =/= list_to_atom(Key); + ({E, _}) when is_list(E) -> E =/= Key; + ({E, _}) when is_binary(E) -> E =/= list_to_binary(Key); + (_) -> true + end. + +-spec path( + cuttlefish_variable:variable(), + [{ string() | atom() | binary() , term()}]) + -> {ok, any()} | notset | {error, bad_nesting}. path(_, []) -> {error, bad_nesting}; path(_, undefined) -> @@ -157,42 +291,174 @@ path([H|T], Proplist) when is_list(H)-> Other end. --spec key_no_match(string()) -> fun((atom() | string() | binary()) -> boolean()). -key_no_match(Key) -> - fun({E, _}) when is_atom(E) -> E =/= list_to_atom(Key); - ({E, _}) when is_list(E) -> E =/= Key; - ({E, _}) when is_binary(E) -> E =/= list_to_binary(Key); - (_) -> true +-spec render_template(FileName :: file:name_all(), Context :: mustache_ctx()) + -> string(). +render_template(FileName, Context) -> + %% The mustache module may only be available in the context of a rebar run. + case find_mustache() of + false -> + erlang:error( + "No suitable mustache module loaded. " + "Run this test in a rebar context."); + Mod -> + {ok, Bin} = file:read_file(FileName), + render_template(Mod, Bin, Context) end. --spec dump_to_file(any(), string()) -> ok. -dump_to_file(ErlangTerm, Filename) -> - {ok, S} = file:open(Filename, [write,append]), - io:format(S, "~p~n", [ErlangTerm]), - _ = file:close(S), - ok. +%% =================================================================== +%% Internal +%% =================================================================== + +-spec render_template( + Mustache :: module(), + Template :: binary(), + Context :: mustache_ctx()) + -> string(). +% +% Even where we're matching on the module, always use the Module variable so +% xref and dialyzer don't complain. If running in Rebar there will be a +% suitable module available at runtime, but usually there won't be an explicit +% project dependency on one. +% +% Unicode support is sketchy, as it is throughout cuttlefish. Someday... +% +render_template(bbmustache = Mustache, Template, Context) -> + % It's unclear whether there could be supplementary UTF-8 bytes that could + % be misinterpreted as relevant characters by the scanner, but as noted + % above we're not putting much effort into Unicode for now. + % The render call raises an error if there's a problem, so no need to + % check return pattern. + unicode:characters_to_list( + Mustache:render(Template, mustache_context(Mustache, Context))); + +render_template(Mustache, Template, Context) -> + % Previous versions of this file escaped the template, but that actually + % seems to break things I've tested, so it's just converted to a list. + Data = case unicode:characters_to_list(Template, utf8) of + Utf8 when erlang:is_list(Utf8) -> + Utf8; + _ -> + % Not legal UTF-8, treat it as an 8-bit ISO-8859 encoding, which + % will always succeed but _may_ be improperly mapped if it's not + % specifically Latin-1. However, all of the punctuation characters + % the scanner cares about should be fine. + unicode:characters_to_list(Template, latin1) + end, + Mustache:render(Data, mustache_context(Mustache, Context)). + +-spec find_mustache() -> module() | false. +% +% Finds a module that is likely to be a mustache implementation that +% render_template/3 knows how to use. +% Once identified, the module is cached in the process environment, so it'll +% be lost when the process goes away, which should coincide with eunit test +% setup/teardown when the available modules might change. +% +find_mustache() -> + Key = {?MODULE, mustache_module}, + case erlang:get(Key) of + undefined -> + Ret = find_mustache([bbmustache, mustache, rebar_mustache]), + _ = erlang:put(Key, Ret), + Ret; + Val -> + Val + end. + +-spec find_mustache(Mods :: [module()]) -> module() | false. +% +% Let find_mustache/0 call this, not much use anywhere else. +% +find_mustache([Mod | Mods]) -> + case code:ensure_loaded(Mod) of + {module, _} -> + case erlang:function_exported(Mod, render, 2) of + true -> + Mod; + _ -> + find_mustache(Mods) + end; + _ -> + find_mustache(Mods) + end; +find_mustache([]) -> + false. + +-spec mustache_context(Module :: module(), Context :: mustache_ctx()) -> term(). +% +% Returns an appropriate mapping context for the mustache Module. +% +mustache_context(bbmustache, Context) -> + % Despite what the docs say, bbmustache seems to need the keys to be + % strings. This _could_ be limited to the pre-OTP-17 code, but we need R16 + % compatibility during the transition so I'm not going to sweat it. + [mkey_string(Elem) || Elem <- Context]; +mustache_context(rebar_mustache, Context) -> + dict:from_list(Context); +mustache_context(_, Context) -> + Context. + +-spec mkey_string(Elem :: mustache_def()) -> {string(), mustache_val()}. +% +% Ensure the Key of the specified Elem is a string. +% Guards are ordered by assumed likelihood. +% +mkey_string({Key, Val}) when erlang:is_atom(Key) -> + {erlang:atom_to_list(Key), Val}; +mkey_string({Key, _} = Elem) when erlang:is_list(Key) -> + Elem; +mkey_string({Key, Val}) when erlang:is_binary(Key) -> + {erlang:binary_to_list(Key), Val}. + +-spec lib_sub_dir(Module :: module(), SubDir :: string()) -> string() | false. +% +% Find the specified sub-directory of the application containing Module. +% We use the definitive code:get_object_code/1 initially, because we're often +% going to be running in a test scenario in which code:which/1 is going to +% return `cover_compiled', after which we'd have to resort to +% code:get_object_code/1 anyway. +% +lib_sub_dir(Module, SubDir) -> + case code:get_object_code(Module) of + {Module, _, Beam} -> + Lib = filename:dirname(filename:dirname(Beam)), + Dir = filename:join(Lib, SubDir), + case filelib:is_dir(Dir) of + true -> + Dir; + _ -> + false + end; + _ -> + false + end. + +%% =================================================================== +%% Tests +%% =================================================================== -ifdef(TEST). path_test() -> ?assertEqual( - {ok, "disable"}, - path(["vm_args", "-smp"], [{vm_args, [{'-smp', "disable"}]}])), - ok. + {ok, "disable"}, + path(["vm_args", "-smp"], [{vm_args, [{'-smp', "disable"}]}])). multiple_schema_generate_templated_config_test() -> lager:start(), - Context = [ - {mustache, "mustache"} - ], - PrereqSchema = {[], [ - cuttlefish_mapping:parse( - {mapping, "c", "app.c", [ - {default, "/c"} - ]}) - ], []}, - - Config = cuttlefish_unit:generate_templated_config("../test/sample_mustache.schema", [], Context, PrereqSchema), + Context = [{mustache, "mustache"}], + PrereqSchema = { + [], + [cuttlefish_mapping:parse({mapping, "c", "app.c", [{default, "/c"}]})], + [] }, + + TestDir = lib_test_dir(?MODULE), + ?assertNotEqual(false, TestDir), + Schema = filename:join(TestDir, "sample_mustache.schema"), + + Config = cuttlefish_unit:generate_templated_config( + Schema, [], Context, PrereqSchema), + lager:error("~p", [Config]), assert_config(Config, "app_a.setting_b", "/c/mustache/a.b"), ok. diff --git a/src/cuttlefish_util.erl b/src/cuttlefish_util.erl index b1c8e287..ce174aa6 100644 --- a/src/cuttlefish_util.erl +++ b/src/cuttlefish_util.erl @@ -23,7 +23,6 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. -export([ diff --git a/src/cuttlefish_validator.erl b/src/cuttlefish_validator.erl index 4d6c29e7..4b9e1271 100644 --- a/src/cuttlefish_validator.erl +++ b/src/cuttlefish_validator.erl @@ -23,7 +23,6 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. -record(validator, { diff --git a/src/cuttlefish_variable.erl b/src/cuttlefish_variable.erl index 76fa1815..08bdf821 100644 --- a/src/cuttlefish_variable.erl +++ b/src/cuttlefish_variable.erl @@ -30,7 +30,6 @@ -define(QC_OUT(Prop), on_output(fun(F,A) -> io:format(user, F, A) end, Prop)). -endif. -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. -export([ diff --git a/src/cuttlefish_vmargs.erl b/src/cuttlefish_vmargs.erl index 5eca3e83..89081891 100644 --- a/src/cuttlefish_vmargs.erl +++ b/src/cuttlefish_vmargs.erl @@ -4,7 +4,6 @@ -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -endif. %% @doc turns a proplist into a list of strings suitable for vm.args files diff --git a/test/cuttlefish_escript_integration_tests.erl b/test/cuttlefish_escript_integration_tests.erl index e4e4682e..eecef576 100644 --- a/test/cuttlefish_escript_integration_tests.erl +++ b/test/cuttlefish_escript_integration_tests.erl @@ -1,32 +1,55 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014-2017 Basho Technologies, Inc. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + -module(cuttlefish_escript_integration_tests). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). escript_utf8_test() -> cuttlefish_lager_test_backend:bounce(error), - ?assertThrow(stop_deactivate, cuttlefish_escript:main( - "-d ../test_fixtures/escript_utf8_test/generated.config " - "-s ../test_fixtures/escript_utf8_test/lib " - "-e ../test_fixtures/escript_utf8_test/etc " - "-c ../test_fixtures/escript_utf8_test/etc/utf8.conf generate" - )), + ?assertThrow(stop_deactivate, cuttlefish_escript:main(lists:flatten([ + "-d ", cuttlefish_test_util:fixtures_file("escript_utf8_test/generated.config"), + " -s ", cuttlefish_test_util:fixtures_file("escript_utf8_test/lib"), + " -e ", cuttlefish_test_util:fixtures_file("escript_utf8_test/etc"), + " -c ", cuttlefish_test_util:fixtures_file("escript_utf8_test/etc/utf8.conf"), + " generate" + ]))), [Log] = cuttlefish_lager_test_backend:get_logs(), ?assertMatch({match, _}, re:run(Log, "utf8.conf: Error converting value on line #1 to latin1")), ok. - advanced_config_format_test() -> cuttlefish_lager_test_backend:bounce(error), - ?assertThrow(stop_deactivate, cuttlefish_escript:main( - "-d ../test_fixtures/acformat/generated.config " - "-s ../test_fixtures/acformat/lib " - "-e ../test_fixtures/acformat/etc " - "-c ../test_fixtures/acformat/etc/acformat.conf generate" - )), + + ?assertThrow(stop_deactivate, cuttlefish_escript:main(lists:flatten([ + "-d ", cuttlefish_test_util:fixtures_file("acformat/generated.config"), + " -s ", cuttlefish_test_util:fixtures_file("acformat/lib"), + " -e ", cuttlefish_test_util:fixtures_file("acformat/etc"), + " -c ", cuttlefish_test_util:fixtures_file("acformat/etc/acformat.conf"), + " generate" + ]))), [Log] = cuttlefish_lager_test_backend:get_logs(), - ?assertMatch({match, _}, re:run(Log, "Error parsing [.][.]/test_fixtures/acformat/etc/advanced.config, incorrect format: \\[\\[a\\],\\[b\\]\\]")), + ?assertMatch({match, _}, re:run(Log, "Error parsing " + ++ cuttlefish_test_util:fixtures_file("acformat/etc/advanced.config") + ++ ", incorrect format: \\[\\[a\\],\\[b\\]\\]")), ok. escript_prune_test_() -> @@ -38,9 +61,11 @@ escript_prune_test_() -> escript_prune(DashM, ExpectedMax) -> %% Empty workspace - case file:list_dir("../test_fixtures/escript_prune_test/generated.config") of + GenCfg = cuttlefish_test_util:fixtures_file("escript_prune_test/generated.config"), + + case file:list_dir(GenCfg) of {ok, FilenamesToDelete} -> - [ file:delete(filename:join(["../test_fixtures/escript_prune_test/generated.config",F])) || F <- FilenamesToDelete ]; + [ file:delete(filename:join(GenCfg, F)) || F <- FilenamesToDelete ]; _ -> ok end, @@ -49,21 +74,15 @@ escript_prune(DashM, ExpectedMax) -> io:format("Running iteration: ~p", [Counter]), %% Timer to keep from generating more than one file per second timer:sleep(1100), - cuttlefish_escript:main( - "-d ../test_fixtures/escript_prune_test/generated.config " - "-s ../test_fixtures/escript_prune_test/lib " - "-e ../test_fixtures/escript_prune_test/etc " - ++ DashM ++ " generate" - ), + cuttlefish_escript:main(lists:flatten([ + "-d ", GenCfg, + " -s ", cuttlefish_test_util:fixtures_file("escript_prune_test/lib"), + " -e ", cuttlefish_test_util:fixtures_file("escript_prune_test/etc"), + $\s, DashM, " generate" + ])), - AppConfigs = - lists:sort( - filelib:wildcard("app.*.config", - "../test_fixtures/escript_prune_test/generated.config")), - VMArgs = - lists:sort( - filelib:wildcard("vm.*.args", - "../test_fixtures/escript_prune_test/generated.config")), + AppConfigs = lists:sort(filelib:wildcard("app.*.config", GenCfg)), + VMArgs = lists:sort(filelib:wildcard("vm.*.args", GenCfg)), {AppConfigs, VMArgs, diff --git a/test/cuttlefish_escript_test.erl b/test/cuttlefish_escript_test.erl index 2d9e8c02..9c28840f 100644 --- a/test/cuttlefish_escript_test.erl +++ b/test/cuttlefish_escript_test.erl @@ -1,7 +1,27 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2014-2017 Basho Technologies, Inc. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + -module(cuttlefish_escript_test). + -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -define(assertPrinted(___Text), begin @@ -51,7 +71,10 @@ describe_test_() -> ]. describe(Key) -> - ?assertThrow(stop_deactivate, cuttlefish_escript:main(["-i", "../test/riak.schema", "-c", "../test/riak.conf", "describe", Key])). + ?assertThrow(stop_deactivate, cuttlefish_escript:main([ + "-i", cuttlefish_test_util:test_file("riak.schema"), + "-c", cuttlefish_test_util:test_file("riak.conf"), + "describe", Key ])). describe_prints_docs() -> ?capturing(begin @@ -93,8 +116,9 @@ describe_prints_no_default() -> describe_prints_not_configured() -> ?capturing(begin - describe("ssl.keyfile"), - ?assertPrinted("Value not set in \\.\\./test/riak.conf") - end). + describe("ssl.keyfile"), + ?assertPrinted("Value not set in " + ++ cuttlefish_test_util:test_file("riak.conf")) + end). -endif. diff --git a/test/cuttlefish_integration_test.erl b/test/cuttlefish_integration_test.erl index 12e40cb2..41a8d6e0 100644 --- a/test/cuttlefish_integration_test.erl +++ b/test/cuttlefish_integration_test.erl @@ -1,40 +1,81 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2013-2017 Basho Technologies, Inc. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + -module(cuttlefish_integration_test). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). -%% This test generates a default .conf file from the riak.schema. view it at ../generated.conf +%% This test generates a default .conf file from the riak.schema. view it at generated.conf generated_conf_file_test() -> - {_, Mappings, _} = cuttlefish_schema:file("../test/riak.schema"), - cuttlefish_conf:generate_file(Mappings, "../generated.conf"), + {_, Mappings, _} = cuttlefish_schema:file( + cuttlefish_test_util:test_file("riak.schema")), + GenConf = cuttlefish_test_util:test_file("generated.conf"), + cuttlefish_conf:generate_file(Mappings, GenConf), + %% Schema generated a conf file, let's parse it! + Conf = cuttlefish_conf:file(GenConf), + ?assertEqual("8099", proplists:get_value(["handoff","port"], Conf)), + ok. + +%% Same as above, but with the files in an .ez archive. +generated_conf_file_ez_test() -> + {_, Mappings, _} = cuttlefish_schema:file("test/riakconf.ez/riakconf/riak.schema"), + cuttlefish_conf:generate_file(Mappings, "generated.conf"), %% Schema generated a conf file, let's parse it! - Conf = cuttlefish_conf:file("../generated.conf"), + Conf = cuttlefish_conf:file("generated.conf"), ?assertEqual("8099", proplists:get_value(["handoff","port"], Conf)), ok. -%% This test generates a .config file from the riak.schema. view it at ../generated.config +%% This test generates a .config file from the riak.schema. view it at generated.config generated_config_file_test() -> - Schema = cuttlefish_schema:file("../test/riak.schema"), - Conf = [], %% conf_parse:file("../test/riak.conf"), + Schema = cuttlefish_schema:file(cuttlefish_test_util:test_file("riak.schema")), + Conf = [], %% conf_parse:file(cuttlefish_test_util:test_file("riak.conf")), + NewConfig = cuttlefish_generator:map(Schema, Conf), + + file:write_file("generated.config",io_lib:fwrite("~p.\n",[NewConfig])), + ok. + +%% Same as above, but with the files in an .ez archive. +generated_config_file_ez_test() -> + Schema = cuttlefish_schema:file("test/riakconf.ez/riakconf/riak.schema"), + Conf = [], %% conf_parse:file("test/riak.conf"), + Schema = cuttlefish_schema:file(cuttlefish_test_util:test_file("riak.schema")), + Conf = [], %% conf_parse:file(cuttlefish_test_util:test_file("riak.conf")), NewConfig = cuttlefish_generator:map(Schema, Conf), - file:write_file("../generated.config",io_lib:fwrite("~p.\n",[NewConfig])), + file:write_file("generated.config",io_lib:fwrite("~p.\n",[NewConfig])), ok. breaks_on_fuzzy_and_strict_match_test() -> - Schema = cuttlefish_schema:file("../test/riak.schema"), + Schema = cuttlefish_schema:file(cuttlefish_test_util:test_file("riak.schema")), Conf = [{["listener", "protobuf", "$name"], "127.0.0.1:8087"}], ?assertMatch({error, add_defaults, _}, cuttlefish_generator:map(Schema, Conf)), ok. breaks_on_rhs_not_found_test() -> - Schema = cuttlefish_schema:file("../test/riak.schema"), + Schema = cuttlefish_schema:file(cuttlefish_test_util:test_file("riak.schema")), Conf = [{["ring", "state_dir"], "$(tyktorp)/ring"}], ?assertMatch({error, rhs_subs, _}, cuttlefish_generator:map(Schema, Conf)), ok. breaks_on_rhs_infinite_loop_test() -> - Schema = cuttlefish_schema:file("../test/riak.schema"), + Schema = cuttlefish_schema:file(cuttlefish_test_util:test_file("riak.schema")), Conf = [ {["ring", "state_dir"], "$(platform_data_dir)/ring"}, {["platform_data_dir"], "$(ring.state_dir)/data"} @@ -43,27 +84,27 @@ breaks_on_rhs_infinite_loop_test() -> ok. breaks_on_bad_enum_test() -> - Schema = cuttlefish_schema:file("../test/riak.schema"), + Schema = cuttlefish_schema:file(cuttlefish_test_util:test_file("riak.schema")), Conf = [{["storage_backend"], penguin}], ?assertMatch({error, transform_datatypes, _}, cuttlefish_generator:map(Schema, Conf)), ok. breaks_on_bad_validation_test() -> - Schema = cuttlefish_schema:file("../test/riak.schema"), + Schema = cuttlefish_schema:file(cuttlefish_test_util:test_file("riak.schema")), Conf = [{["ring_size"], 10}], ?assertMatch({error, validation, _}, cuttlefish_generator:map(Schema, Conf)), ok. %% Tests that the schema can generate a default app.config from nothing all_the_marbles_test() -> - Schema = cuttlefish_schema:file("../test/riak.schema"), - Conf = [], %conf_parse:file("../test/riak.conf"), + Schema = cuttlefish_schema:file(cuttlefish_test_util:test_file("riak.schema")), + Conf = [], %conf_parse:file(cuttlefish_test_util:test_file("riak.conf")), NewConfig = cuttlefish_generator:map(Schema, Conf), ?assert(is_proplist(NewConfig)), NewConfigWithoutVmargs = proplists:delete(vm_args, NewConfig), - {ok, [AppConfig]} = file:consult("../test/default.config"), + {ok, [AppConfig]} = file:consult(cuttlefish_test_util:test_file("default.config")), ?assert(is_proplist(AppConfig)), @@ -72,7 +113,9 @@ all_the_marbles_test() -> multibackend_test() -> lager:start(), - Schema = cuttlefish_schema:files(["../test/riak.schema", "../test/multi_backend.schema"]), + Schema = cuttlefish_schema:files([ + cuttlefish_test_util:test_file("riak.schema"), + cuttlefish_test_util:test_file("multi_backend.schema") ]), Conf = [ {["storage_backend"], "multi"}, @@ -143,7 +186,8 @@ multibackend_test() -> unset_translation_test() -> lager:start(), - Schema = cuttlefish_schema:files(["../test/unset_translation.schema"]), + Schema = cuttlefish_schema:files( + [cuttlefish_test_util:test_file("unset_translation.schema")]), Conf = [ {["a", "b"], "8"} ], @@ -154,14 +198,16 @@ unset_translation_test() -> not_found_error_test() -> lager:start(), - Schema = cuttlefish_schema:files(["../test/throw_not_found.schema"]), + Schema = cuttlefish_schema:files( + [cuttlefish_test_util:test_file("throw_not_found.schema")]), Conf = [], NewConfig = cuttlefish_generator:map(Schema, Conf), ?assertMatch({error, apply_translations, _}, NewConfig). duration_test() -> lager:start(), - Schema = cuttlefish_schema:files(["../test/durations.schema"]), + Schema = cuttlefish_schema:files( + [cuttlefish_test_util:test_file("durations.schema")]), %% Test that the duration parsing doesn't emit "error" into the %% config instead of the extended type. diff --git a/test/cuttlefish_nested_schema_test.erl b/test/cuttlefish_nested_schema_test.erl index 50065bd1..0ba7e8fc 100644 --- a/test/cuttlefish_nested_schema_test.erl +++ b/test/cuttlefish_nested_schema_test.erl @@ -1,7 +1,6 @@ -module(cuttlefish_nested_schema_test). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). nested_schema_test() -> Conf = [ diff --git a/test/cuttlefish_test_group_leader.erl b/test/cuttlefish_test_group_leader.erl index ce76c44e..7007074d 100644 --- a/test/cuttlefish_test_group_leader.erl +++ b/test/cuttlefish_test_group_leader.erl @@ -24,6 +24,8 @@ tidy_up/1, get_output/0]). +-compile(nowarn_deprecated_function). + %% @doc spawns the new group leader new_group_leader(Runner) -> spawn_link(?MODULE, group_leader_loop, [Runner, queue:new()]). diff --git a/test/cuttlefish_test_util.erl b/test/cuttlefish_test_util.erl new file mode 100644 index 00000000..3e66f25c --- /dev/null +++ b/test/cuttlefish_test_util.erl @@ -0,0 +1,150 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2017 Basho Technologies, Inc. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + +%% +%% @doc Common functions for use in tests. +%% +-module(cuttlefish_test_util). + +% Public API +-export([ + fixtures_dir/0, + fixtures_file/1, + priv_dir/0, + priv_file/1, + test_dir/0, + test_file/1 +]). + +-include_lib("eunit/include/eunit.hrl"). + +%% =================================================================== +%% Public API +%% =================================================================== + +-spec fixtures_dir() -> file:filename(). +%% +%% @doc Returns the path to the "test_fixtures" directory. +%% +%% Rebar versions 2 and 3 differ on where the "test_fixtures" directory is +%% relative to the current test directory. +%% +fixtures_dir() -> + Key = {?MODULE, fixtures_dir}, + case erlang:get(Key) of + undefined -> + {ok, CWD} = file:get_cwd(), + Here = filename:join(CWD, "test_fixtures"), + Dir = case filelib:is_dir(Here) of + true -> + Here; + _ -> + UpOne = filename:join(filename:dirname(CWD), "test_fixtures"), + ?assertEqual(true, filelib:is_dir(UpOne)), + UpOne + end, + _ = erlang:put(Key, Dir), + Dir; + Val -> + Val + end. + +-spec fixtures_file(File :: file:filename()) -> file:filename(). +%% +%% @doc Returns the path to a file in the "test_fixtures" directory. +%% +%% `File' is assumed to be a simple (possibly relative) filename under the +%% directory returned by {@fixtures_dir/0}. +%% +fixtures_file(File) -> + filename:join(fixtures_dir(), File). + +-spec priv_dir() -> file:filename(). +%% +%% @doc Returns the path to the current "priv" directory. +%% +%% Rebar versions 2 and 3 differ on where the "priv" directory is relative +%% to the current test directory. +%% +priv_dir() -> + Key = {?MODULE, priv_dir}, + case erlang:get(Key) of + undefined -> + TestPeer = filename:join(filename:dirname(test_dir()), "priv"), + Dir = case filelib:is_dir(TestPeer) of + true -> + TestPeer; + _ -> + {ok, CWD} = file:get_cwd(), + Here = filename:join(CWD, "priv"), + case filelib:is_dir(Here) of + true -> + Here; + _ -> + UpOne = filename:join(filename:dirname(CWD), "priv"), + ?assertEqual(true, filelib:is_dir(UpOne)), + UpOne + end + end, + _ = erlang:put(Key, Dir), + Dir; + Val -> + Val + end. + +-spec priv_file(File :: file:filename()) -> file:filename(). +%% +%% @doc Returns the path to a file in the current "priv" directory. +%% +%% `File' is assumed to be a simple (possibly relative) filename under the +%% directory returned by {@link priv_dir/0}. +%% +priv_file(File) -> + filename:join(priv_dir(), File). + +-spec test_dir() -> file:filename(). +%% +%% @doc Returns the path to the current "test" directory. +%% +%% Rebar versions 2 and 3 differ widely on how and where files are laid out +%% when running eunit, but when in the "test" directory both place the beam +%% file in the same directory as its source. +%% +test_dir() -> + Key = {?MODULE, test_dir}, + case erlang:get(Key) of + undefined -> + Dir = filename:dirname(code:which(?MODULE)), + _ = erlang:put(Key, Dir), + Dir; + Val -> + Val + end. + +-spec test_file(File :: file:filename()) -> file:filename(). +%% +%% @doc Returns the path to a file in the current "test" directory. +%% +%% `File' is assumed to be a simple (possibly relative) filename under the +%% directory returned by {@link test_dir/0}. +%% +test_file(File) -> + filename:join(test_dir(), File). + diff --git a/test/erlang_vm_schema_tests.erl b/test/erlang_vm_schema_tests.erl index 292778d8..3cca843b 100644 --- a/test/erlang_vm_schema_tests.erl +++ b/test/erlang_vm_schema_tests.erl @@ -1,15 +1,34 @@ +%% ------------------------------------------------------------------- +%% +%% Copyright (c) 2013-2017 Basho Technologies, Inc. +%% +%% This file is provided to you under the Apache License, +%% Version 2.0 (the "License"); you may not use this file +%% except in compliance with the License. You may obtain +%% a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, +%% software distributed under the License is distributed on an +%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +%% KIND, either express or implied. See the License for the +%% specific language governing permissions and limitations +%% under the License. +%% +%% ------------------------------------------------------------------- + -module(erlang_vm_schema_tests). -include_lib("eunit/include/eunit.hrl"). --compile(export_all). %% basic schema test will check to make sure that all defaults from the schema %% make it into the generated app.config basic_schema_test() -> - %% The defaults are defined in ../priv/riak_kv.schema and multi_backend.schema. + %% The defaults are defined in priv/riak_kv.schema and multi_backend.schema. %% they are the files under test. Config = cuttlefish_unit:generate_templated_config( - ["../priv/erlang_vm.schema"], [], context()), + [cuttlefish_test_util:priv_file("erlang_vm.schema")], [], context()), cuttlefish_unit:assert_config(Config, "vm_args.-smp", enable), cuttlefish_unit:assert_config(Config, "vm_args.+W", "w"), @@ -65,7 +84,7 @@ override_schema_test() -> ], Config = cuttlefish_unit:generate_templated_config( - ["../priv/erlang_vm.schema"], Conf, context()), + [cuttlefish_test_util:priv_file("erlang_vm.schema")], Conf, context()), cuttlefish_unit:assert_config(Config, "vm_args.-smp", disable), cuttlefish_unit:assert_config(Config, "vm_args.+W", "i"), @@ -99,30 +118,31 @@ override_schema_test() -> ok. erlang_scheduler_test() -> + ErlVmSchema = cuttlefish_test_util:priv_file("erlang_vm.schema"), Conf1 = [ {["erlang", "schedulers", "total"], 4}, {["erlang", "schedulers", "online"], 1} ], Config1 = cuttlefish_unit:generate_templated_config( - ["../priv/erlang_vm.schema"], Conf1, context()), + [ErlVmSchema], Conf1, context()), cuttlefish_unit:assert_config(Config1, "vm_args.+S", "4:1"), Conf2 = [ {["erlang", "schedulers", "total"], 4} ], Config2 = cuttlefish_unit:generate_templated_config( - ["../priv/erlang_vm.schema"], Conf2, context()), + [ErlVmSchema], Conf2, context()), cuttlefish_unit:assert_config(Config2, "vm_args.+S", "4"), Conf3 = [ {["erlang", "schedulers", "online"], 4} ], Config3 = cuttlefish_unit:generate_templated_config( - ["../priv/erlang_vm.schema"], Conf3, context()), + [ErlVmSchema], Conf3, context()), cuttlefish_unit:assert_config(Config3, "vm_args.+S", ":4"), Config4 = cuttlefish_unit:generate_templated_config( - ["../priv/erlang_vm.schema"], [], context()), + [ErlVmSchema], [], context()), cuttlefish_unit:assert_not_configured(Config4, "vm_args.+S"), @@ -136,27 +156,39 @@ async_threads_stack_size_test() -> Correct = cuttlefish_bytesize:to_string(WordSize * 1024 * 32), MinSize = cuttlefish_bytesize:to_string(WordSize * 1024 * 16), MaxSize = cuttlefish_bytesize:to_string(WordSize * 1024 * 8192), - CorrectRaw = 32, - + CorrectRaw = 32, + ErlVmSchema = cuttlefish_test_util:priv_file("erlang_vm.schema"), + Conf0 = [], - Config0 = cuttlefish_unit:generate_templated_config(["../priv/erlang_vm.schema"], Conf0, context()), + Config0 = cuttlefish_unit:generate_templated_config( + [ErlVmSchema], Conf0, context()), cuttlefish_unit:assert_not_configured(Config0, "vm_args.+a"), - + Conf1 = [{["erlang", "async_threads", "stack_size"], Correct}], - Config1 = cuttlefish_unit:generate_templated_config(["../priv/erlang_vm.schema"], Conf1, context()), + Config1 = cuttlefish_unit:generate_templated_config( + [ErlVmSchema], Conf1, context()), cuttlefish_unit:assert_config(Config1, "vm_args.+a", CorrectRaw), Conf2 = [{["erlang", "async_threads", "stack_size"], TooSmall}], - Config2 = cuttlefish_unit:generate_templated_config(["../priv/erlang_vm.schema"], Conf2, context()), - cuttlefish_unit:assert_error_message(Config2, "erlang.async_threads.stack_size invalid, must be in the range of " ++ MinSize ++ " to " ++ MaxSize), + Config2 = cuttlefish_unit:generate_templated_config( + [ErlVmSchema], Conf2, context()), + cuttlefish_unit:assert_error_message(Config2, + "erlang.async_threads.stack_size invalid, must be in the range of " + ++ MinSize ++ " to " ++ MaxSize), Conf3 = [{["erlang", "async_threads", "stack_size"], TooLarge}], - Config3 = cuttlefish_unit:generate_templated_config(["../priv/erlang_vm.schema"], Conf3, context()), - cuttlefish_unit:assert_error_message(Config3, "erlang.async_threads.stack_size invalid, must be in the range of " ++ MinSize ++ " to " ++ MaxSize), + Config3 = cuttlefish_unit:generate_templated_config( + [ErlVmSchema], Conf3, context()), + cuttlefish_unit:assert_error_message(Config3, + "erlang.async_threads.stack_size invalid, must be in the range of " + ++ MinSize ++ " to " ++ MaxSize), Conf4 = [{["erlang", "async_threads", "stack_size"], Indivisible}], - Config4 = cuttlefish_unit:generate_templated_config(["../priv/erlang_vm.schema"], Conf4, context()), - cuttlefish_unit:assert_error_message(Config4, "erlang.async_threads.stack_size invalid, must be divisible by " ++ integer_to_list(WordSize)), + Config4 = cuttlefish_unit:generate_templated_config( + [ErlVmSchema], Conf4, context()), + cuttlefish_unit:assert_error_message(Config4, + "erlang.async_threads.stack_size invalid, must be divisible by " + ++ integer_to_list(WordSize)), ok. @@ -176,6 +208,7 @@ inet_dist_use_interface_test() -> InputConfig = "erlang.distribution.interface", GeneratedConfig = "kernel.inet_dist_use_interface", InputConfigPoint = string:tokens(InputConfig, "."), + ErlVmSchema = cuttlefish_test_util:priv_file("erlang_vm.schema"), Pass =[ {"127.0.0.1",{127,0,0,1}}, @@ -191,12 +224,12 @@ inet_dist_use_interface_test() -> lists:foreach(fun({Input, Expected}) -> Config = cuttlefish_unit:generate_templated_config( - ["../priv/erlang_vm.schema"], [{InputConfigPoint, Input}], context()), + [ErlVmSchema], [{InputConfigPoint, Input}], context()), cuttlefish_unit:assert_config(Config, GeneratedConfig, Expected) end, Pass), lists:foreach(fun(Input) -> Config = cuttlefish_unit:generate_templated_config( - ["../priv/erlang_vm.schema"], [{InputConfigPoint, Input}], context()), + [ErlVmSchema], [{InputConfigPoint, Input}], context()), cuttlefish_unit:assert_error_message(Config, InputConfig ++ " invalid, must be a valid IPv4 or IPv6 address") end, Fail). diff --git a/test/riakconf.ez b/test/riakconf.ez new file mode 100644 index 00000000..d90bc3c9 Binary files /dev/null and b/test/riakconf.ez differ