├── .credo.exs ├── .formatter.exs ├── .gitignore ├── .tool-versions ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── benchmarks └── general.exs ├── config └── config.exs ├── lib └── constantizer.ex ├── mix.exs ├── mix.lock └── test ├── constantizer_test.exs └── test_helper.exs /.credo.exs: -------------------------------------------------------------------------------- 1 | # This file contains the configuration for Credo and you are probably reading 2 | # this after creating it with `mix credo.gen.config`. 3 | # 4 | # If you find anything wrong or unclear in this file, please report an 5 | # issue on GitHub: https://github.com/rrrene/credo/issues 6 | # 7 | %{ 8 | # 9 | # You can have as many configs as you like in the `configs:` field. 10 | configs: [ 11 | %{ 12 | # 13 | # Run any exec using `mix credo -C `. If no exec name is given 14 | # "default" is used. 15 | # 16 | name: "default", 17 | # 18 | # These are the files included in the analysis: 19 | files: %{ 20 | # 21 | # You can give explicit globs or simply directories. 22 | # In the latter case `**/*.{ex,exs}` will be used. 23 | # 24 | included: ["lib/", "src/", "test/", "web/", "apps/"], 25 | excluded: [~r"/_build/", ~r"/deps/"] 26 | }, 27 | # 28 | # If you create your own checks, you must specify the source files for 29 | # them here, so they can be loaded by Credo before running the analysis. 30 | # 31 | requires: [], 32 | # 33 | # If you want to enforce a style guide and need a more traditional linting 34 | # experience, you can change `strict` to `true` below: 35 | # 36 | strict: false, 37 | # 38 | # If you want to use uncolored output by default, you can change `color` 39 | # to `false` below: 40 | # 41 | color: true, 42 | # 43 | # You can customize the parameters of any check by adding a second element 44 | # to the tuple. 45 | # 46 | # To disable a check put `false` as second element: 47 | # 48 | # {Credo.Check.Design.DuplicatedCode, false} 49 | # 50 | checks: [ 51 | # 52 | ## Consistency Checks 53 | # 54 | {Credo.Check.Consistency.ExceptionNames}, 55 | {Credo.Check.Consistency.LineEndings}, 56 | {Credo.Check.Consistency.ParameterPatternMatching}, 57 | {Credo.Check.Consistency.SpaceAroundOperators}, 58 | {Credo.Check.Consistency.SpaceInParentheses}, 59 | {Credo.Check.Consistency.TabsOrSpaces}, 60 | 61 | # 62 | ## Design Checks 63 | # 64 | # You can customize the priority of any check 65 | # Priority values are: `low, normal, high, higher` 66 | # 67 | {Credo.Check.Design.AliasUsage, priority: :low}, 68 | # For some checks, you can also set other parameters 69 | # 70 | # If you don't want the `setup` and `test` macro calls in ExUnit tests 71 | # or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just 72 | # set the `excluded_macros` parameter to `[:schema, :setup, :test]`. 73 | # 74 | {Credo.Check.Design.DuplicatedCode, excluded_macros: []}, 75 | # You can also customize the exit_status of each check. 76 | # If you don't want TODO comments to cause `mix credo` to fail, just 77 | # set this value to 0 (zero). 78 | # 79 | {Credo.Check.Design.TagTODO, exit_status: 2}, 80 | {Credo.Check.Design.TagFIXME}, 81 | 82 | # 83 | ## Readability Checks 84 | # 85 | {Credo.Check.Readability.AliasOrder}, 86 | {Credo.Check.Readability.FunctionNames}, 87 | {Credo.Check.Readability.LargeNumbers}, 88 | {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 120}, 89 | {Credo.Check.Readability.ModuleAttributeNames}, 90 | {Credo.Check.Readability.ModuleDoc}, 91 | {Credo.Check.Readability.ModuleNames}, 92 | {Credo.Check.Readability.ParenthesesOnZeroArityDefs}, 93 | {Credo.Check.Readability.ParenthesesInCondition}, 94 | {Credo.Check.Readability.PredicateFunctionNames}, 95 | {Credo.Check.Readability.PreferImplicitTry}, 96 | {Credo.Check.Readability.RedundantBlankLines}, 97 | {Credo.Check.Readability.StringSigils}, 98 | {Credo.Check.Readability.TrailingBlankLine}, 99 | {Credo.Check.Readability.TrailingWhiteSpace}, 100 | {Credo.Check.Readability.VariableNames}, 101 | {Credo.Check.Readability.Semicolons}, 102 | {Credo.Check.Readability.SpaceAfterCommas}, 103 | 104 | # 105 | ## Refactoring Opportunities 106 | # 107 | {Credo.Check.Refactor.DoubleBooleanNegation}, 108 | {Credo.Check.Refactor.CondStatements}, 109 | {Credo.Check.Refactor.CyclomaticComplexity}, 110 | {Credo.Check.Refactor.FunctionArity}, 111 | {Credo.Check.Refactor.LongQuoteBlocks}, 112 | {Credo.Check.Refactor.MapInto}, 113 | {Credo.Check.Refactor.MatchInCondition}, 114 | {Credo.Check.Refactor.NegatedConditionsInUnless}, 115 | {Credo.Check.Refactor.NegatedConditionsWithElse}, 116 | {Credo.Check.Refactor.Nesting}, 117 | {Credo.Check.Refactor.PipeChainStart, 118 | excluded_argument_types: [:atom, :binary, :fn, :keyword], excluded_functions: []}, 119 | {Credo.Check.Refactor.UnlessWithElse}, 120 | 121 | # 122 | ## Warnings 123 | # 124 | {Credo.Check.Warning.BoolOperationOnSameValues}, 125 | {Credo.Check.Warning.ExpensiveEmptyEnumCheck}, 126 | {Credo.Check.Warning.IExPry}, 127 | {Credo.Check.Warning.IoInspect}, 128 | {Credo.Check.Warning.LazyLogging}, 129 | {Credo.Check.Warning.OperationOnSameValues}, 130 | {Credo.Check.Warning.OperationWithConstantResult}, 131 | {Credo.Check.Warning.UnusedEnumOperation}, 132 | {Credo.Check.Warning.UnusedFileOperation}, 133 | {Credo.Check.Warning.UnusedKeywordOperation}, 134 | {Credo.Check.Warning.UnusedListOperation}, 135 | {Credo.Check.Warning.UnusedPathOperation}, 136 | {Credo.Check.Warning.UnusedRegexOperation}, 137 | {Credo.Check.Warning.UnusedStringOperation}, 138 | {Credo.Check.Warning.UnusedTupleOperation}, 139 | {Credo.Check.Warning.RaiseInsideRescue}, 140 | 141 | # 142 | # Controversial and experimental checks (opt-in, just remove `, false`) 143 | # 144 | {Credo.Check.Refactor.ABCSize, false}, 145 | {Credo.Check.Refactor.AppendSingleItem, false}, 146 | {Credo.Check.Refactor.VariableRebinding, false}, 147 | {Credo.Check.Warning.MapGetUnsafePass, false}, 148 | {Credo.Check.Consistency.MultiAliasImportRequireUse, false}, 149 | 150 | # 151 | # Deprecated checks (these will be deleted after a grace period) 152 | # 153 | {Credo.Check.Readability.Specs, false} 154 | 155 | # 156 | # Custom checks can be created using `mix credo.gen.check`. 157 | # 158 | ] 159 | } 160 | ] 161 | } 162 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where 3rd-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | constantizer-*.tar 24 | 25 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | elixir 1.7.3-otp-21 2 | erlang 21.0.8 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | matrix: 3 | include: 4 | - elixir: "1.6.6" 5 | otp_release: "20.3.8.2" 6 | - elixir: "1.7.3" 7 | otp_release: "21.0.8" 8 | dist: trusty 9 | 10 | cache: 11 | directories: 12 | - _build 13 | - deps 14 | 15 | before_script: 16 | - mix local.hex --force 17 | - mix deps.get --only test 18 | - mix deps.clean --unused 19 | - MIX_ENV=test mix compile --warnings-as-errors 20 | - MIX_ENV=test travis_wait mix dialyzer --plt 21 | 22 | script: 23 | - mix test 24 | - mix format --check-formatted 25 | - MIX_ENV=test mix credo --strict 26 | - MIX_ENV=test mix dialyzer --format short --halt-exit-status 27 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.2.0(2018-08-10) 2 | 3 | ### Breaking changes 4 | * `resolve_at_compile_time` now defaults to `true` instead of false. 5 | 6 | ### Bug fixes 7 | * Fix issue where you could get an unused alias warning when 8 | `resolve_at_compile_time` is `true`. 9 | 10 | ## 0.1.0 (2018-07-29) 11 | Initial release. 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Constantizer 2 | 3 | Turn functions into constants at compile-time. Very useful when following the 4 | patterns set forth in the ElixirConf [Growing Applications and Taming 5 | Complexity][0] talk. 6 | 7 | ## Installation 8 | 9 | Just add `constantizer` to your list of dependencies. 10 | 11 | ```elixir 12 | def deps do 13 | [{:constantizer, "~> 0.2.0"}] 14 | end 15 | ``` 16 | 17 | ## Usage 18 | 19 | Constantizer allows you to evaluate a 0-arity function at compile time, instead 20 | of repeatedly evaluating it at runtime. This is very helpful when defining 21 | a public api module with swappable backends. 22 | 23 | ```elixir 24 | defmodule MyApp do 25 | import Constantizer 26 | 27 | def register_user(params) do 28 | backend().register_user(params) 29 | end 30 | 31 | defconstp backend do 32 | Application.get_env(:my_app, :backend) 33 | end 34 | end 35 | ``` 36 | 37 | By default, `defconst` and `defconstp` will define a public or private function, 38 | respectively, with its return value being set to the result of the block as it 39 | exists at compile time. 40 | 41 | To allow a constant to be evaluated at runtime (for example, so you can inject a 42 | mock backend in your test environment), modify the following setting in your 43 | config. 44 | 45 | ```elixir 46 | # config/test.exs 47 | 48 | config :constantizer, resolve_at_compile_time: false 49 | ``` 50 | 51 | ## Benchmarks 52 | 53 | You can run constantizer's benchmarks with the following command. 54 | 55 | ``` 56 | mix run benchmarks/general.exs 57 | ``` 58 | 59 | Here is some sample output from our benchmarks that call the following module 60 | 61 | ```elixir 62 | defmodule MyModule do 63 | import Constantizer 64 | 65 | def runtime_lookup do 66 | Application.get_env(:constantizer, :foo) 67 | end 68 | 69 | defconst compile_time_lookup do 70 | Application.get_env(:constantizer, :foo) 71 | end 72 | end 73 | ``` 74 | 75 | ``` 76 | Name ips average deviation median 99th % 77 | compile_time_lookup 87.43 M 0.0114 μs ±17.43% 0.0110 μs 0.0130 μs 78 | runtime_lookup 4.62 M 0.22 μs ±41.15% 0.21 μs 0.40 μs 79 | 80 | Comparison: 81 | compile_time_lookup 87.43 M 82 | runtime_lookup 4.62 M - 18.93x slower 83 | ``` 84 | 85 | The compile time lookup using `defconst` is 18x faster than using a normal `def` because `Application.get_env/2` is evaluated at compile time instead of runtime. 86 | 87 | ## Docs 88 | 89 | The docs for this project are available on [hexdocs][1]. 90 | 91 | [0]: https://www.youtube.com/watch?v=Ue--hvFzr0o 92 | [1]: https://hexdocs.pm/constantizer 93 | -------------------------------------------------------------------------------- /benchmarks/general.exs: -------------------------------------------------------------------------------- 1 | Application.put_env(:constantizer, :resolve_at_compile_time, true) 2 | Application.put_env(:constantizer, :foo, :bar) 3 | 4 | defmodule MyModule do 5 | import Constantizer 6 | 7 | def runtime_lookup do 8 | Application.get_env(:constantizer, :foo) 9 | end 10 | 11 | defconst compile_time_lookup do 12 | Application.get_env(:constantizer, :foo) 13 | end 14 | end 15 | 16 | Benchee.run(%{ 17 | "runtime_lookup" => & MyModule.runtime_lookup/0, 18 | "compile_time_lookup" => & MyModule.compile_time_lookup/0, 19 | }) 20 | -------------------------------------------------------------------------------- /config/config.exs: -------------------------------------------------------------------------------- 1 | # This file is responsible for configuring your application 2 | # and its dependencies with the aid of the Mix.Config module. 3 | use Mix.Config 4 | 5 | # This configuration is loaded before any dependency and is restricted 6 | # to this project. If another project depends on this project, this 7 | # file won't be loaded nor affect the parent project. For this reason, 8 | # if you want to provide default values for your application for 9 | # 3rd-party users, it should be done in your "mix.exs" file. 10 | 11 | # You can configure your application as: 12 | # 13 | # config :constantizer, key: :value 14 | # 15 | # and access this configuration in your application as: 16 | # 17 | # Application.get_env(:constantizer, :key) 18 | # 19 | # You can also configure a 3rd-party app: 20 | # 21 | # config :logger, level: :info 22 | # 23 | 24 | # It is also possible to import configuration files, relative to this 25 | # directory. For example, you can emulate configuration per environment 26 | # by uncommenting the line below and defining dev.exs, test.exs and such. 27 | # Configuration from the imported file will override the ones defined 28 | # here (which is why it is important to import them last). 29 | # 30 | # import_config "#{Mix.env}.exs" 31 | -------------------------------------------------------------------------------- /lib/constantizer.ex: -------------------------------------------------------------------------------- 1 | defmodule Constantizer do 2 | @moduledoc """ 3 | Constantizer is a library for turning functions into constants. 4 | """ 5 | 6 | @doc """ 7 | Defines a public constant 8 | """ 9 | defmacro defconst(name, do: block) do 10 | do_def(name, block, __CALLER__, :public) 11 | end 12 | 13 | @doc """ 14 | Defines a private constant. 15 | """ 16 | defmacro defconstp(name, do: block) do 17 | do_def(name, block, __CALLER__, :private) 18 | end 19 | 20 | defp do_def(name, block, env, visibility) do 21 | if resolve_at_compile_time?() do 22 | {result, _} = Code.eval_quoted(block, [], env) 23 | 24 | dynamic_def( 25 | visibility, 26 | name, 27 | quote do 28 | # Avoid unused alias warnings 29 | _ = fn -> unquote(block) end 30 | 31 | unquote(result) 32 | end 33 | ) 34 | else 35 | dynamic_def( 36 | visibility, 37 | name, 38 | quote do 39 | unquote(block) 40 | end 41 | ) 42 | end 43 | end 44 | 45 | defp dynamic_def(:private, name, ast) do 46 | quote do 47 | defp unquote(name), do: unquote(ast) 48 | end 49 | end 50 | 51 | defp dynamic_def(:public, name, ast) do 52 | quote do 53 | def unquote(name), do: unquote(ast) 54 | end 55 | end 56 | 57 | defp resolve_at_compile_time? do 58 | Application.get_env(:constantizer, :resolve_at_compile_time, true) 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Constantizer.MixProject do 2 | use Mix.Project 3 | 4 | @version "0.2.0" 5 | @maintainers ["Aaron Renner"] 6 | @source_url "https://github.com/aaronrenner/constantizer" 7 | 8 | def project do 9 | [ 10 | app: :constantizer, 11 | version: @version, 12 | elixir: "~> 1.6", 13 | start_permanent: Mix.env() == :prod, 14 | description: description(), 15 | package: package(), 16 | deps: deps(), 17 | 18 | # Docs 19 | name: "Constantizer", 20 | docs: docs() 21 | ] 22 | end 23 | 24 | # Run "mix help compile.app" to learn about applications. 25 | def application do 26 | [ 27 | extra_applications: [:logger] 28 | ] 29 | end 30 | 31 | # Run "mix help deps" to learn about dependencies. 32 | defp deps do 33 | [ 34 | {:benchee, "~> 0.11", only: :dev}, 35 | {:credo, "~> 0.10", only: [:dev, :test], runtime: false}, 36 | {:dialyxir, "~> 1.0.0-rc.3", only: [:dev, :test], runtime: false}, 37 | {:ex_doc, "~> 0.16", only: :dev, runtime: false} 38 | ] 39 | end 40 | 41 | # Run "mix help docs" to learn about docs 42 | defp docs do 43 | [ 44 | main: "Constantizer", 45 | source_url: @source_url, 46 | source_ref: "v#{@version}" 47 | ] 48 | end 49 | 50 | defp description do 51 | "Turns functions into constants at compile-time" 52 | end 53 | 54 | defp package do 55 | [ 56 | maintainers: @maintainers, 57 | licenses: ["MIT"], 58 | links: %{ 59 | "Github" => @source_url 60 | } 61 | ] 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "benchee": {:hex, :benchee, "0.13.2", "30cd4ff5f593fdd218a9b26f3c24d580274f297d88ad43383afe525b1543b165", [:mix], [{:deep_merge, "~> 0.1", [hex: :deep_merge, repo: "hexpm", optional: false]}], "hexpm"}, 3 | "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm"}, 4 | "credo": {:hex, :credo, "0.10.0", "66234a95effaf9067edb19fc5d0cd5c6b461ad841baac42467afed96c78e5e9e", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm"}, 5 | "deep_merge": {:hex, :deep_merge, "0.2.0", "c1050fa2edf4848b9f556fba1b75afc66608a4219659e3311d9c9427b5b680b3", [:mix], [], "hexpm"}, 6 | "dialyxir": {:hex, :dialyxir, "1.0.0-rc.3", "774306f84973fc3f1e2e8743eeaa5f5d29b117f3916e5de74c075c02f1b8ef55", [:mix], [], "hexpm"}, 7 | "earmark": {:hex, :earmark, "1.2.5", "4d21980d5d2862a2e13ec3c49ad9ad783ffc7ca5769cf6ff891a4553fbaae761", [:mix], [], "hexpm"}, 8 | "ex_doc": {:hex, :ex_doc, "0.19.1", "519bb9c19526ca51d326c060cb1778d4a9056b190086a8c6c115828eaccea6cf", [:mix], [{:earmark, "~> 1.1", [hex: :earmark, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.7", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm"}, 9 | "jason": {:hex, :jason, "1.1.1", "d3ccb840dfb06f2f90a6d335b536dd074db748b3e7f5b11ab61d239506585eb2", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"}, 10 | "makeup": {:hex, :makeup, "0.5.1", "966c5c2296da272d42f1de178c1d135e432662eca795d6dc12e5e8787514edf7", [:mix], [{:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, 11 | "makeup_elixir": {:hex, :makeup_elixir, "0.8.0", "1204a2f5b4f181775a0e456154830524cf2207cf4f9112215c05e0b76e4eca8b", [:mix], [{:makeup, "~> 0.5.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 0.2.2", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm"}, 12 | "nimble_parsec": {:hex, :nimble_parsec, "0.2.2", "d526b23bdceb04c7ad15b33c57c4526bf5f50aaa70c7c141b4b4624555c68259", [:mix], [], "hexpm"}, 13 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, 14 | } 15 | -------------------------------------------------------------------------------- /test/constantizer_test.exs: -------------------------------------------------------------------------------- 1 | # credo:disable-for-this-file Credo.Check.Design.AliasUsage 2 | defmodule ConstantizerTest do 3 | use ExUnit.Case 4 | 5 | describe "when :resolve_at_compile_time is true" do 6 | setup do 7 | ensure_env_is_reset(:constantizer, :resolve_at_compile_time) 8 | Application.put_env(:constantizer, :resolve_at_compile_time, true) 9 | 10 | assert capture_err(fn -> 11 | Code.eval_string(""" 12 | defmodule ConstantizerTest.ResolvedConsts do 13 | import Constantizer 14 | 15 | alias ExUnit.Assertions 16 | 17 | defconst public_const do 18 | _ = Assertions 19 | send self(), :called 20 | :public_const_return 21 | end 22 | 23 | def call_private, do: private_const() 24 | 25 | defconstp private_const do 26 | send self(), :private_called 27 | :private_const_return 28 | end 29 | end 30 | """) 31 | end) == "" 32 | 33 | purge_on_exit(ConstantizerTest.ResolvedConsts) 34 | flush() 35 | 36 | :ok 37 | end 38 | 39 | test "defconst does not run the function at runtime" do 40 | assert :public_const_return = ConstantizerTest.ResolvedConsts.public_const() 41 | 42 | refute_received :called 43 | end 44 | 45 | test "defconstp does not run the function at runtime" do 46 | assert :private_const_return = ConstantizerTest.ResolvedConsts.call_private() 47 | 48 | refute_received :private_called 49 | end 50 | end 51 | 52 | describe "when :resolve_at_compile_time is false" do 53 | setup do 54 | ensure_env_is_reset(:constantizer, :resolve_at_compile_time) 55 | Application.put_env(:constantizer, :resolve_at_compile_time, false) 56 | 57 | assert capture_err(fn -> 58 | Code.eval_string(""" 59 | defmodule ConstantizerTest.UnresolvedConsts do 60 | import Constantizer 61 | 62 | alias ExUnit.Assertions 63 | 64 | defconst public_const do 65 | send self(), :called 66 | :public_const_return 67 | end 68 | 69 | def call_private_const, do: private_const() 70 | 71 | defconstp private_const do 72 | _ = Assertions 73 | send self(), :private_called 74 | :private_const_return 75 | end 76 | 77 | end 78 | """) 79 | end) == "" 80 | 81 | purge_on_exit(ConstantizerTest.UnresolvedConsts) 82 | :ok 83 | end 84 | 85 | test "defconst runs the function at runtime" do 86 | assert :public_const_return = ConstantizerTest.UnresolvedConsts.public_const() 87 | 88 | assert_received :called 89 | end 90 | 91 | test "defconstp runs the function at runtime" do 92 | assert :private_const_return = ConstantizerTest.UnresolvedConsts.call_private_const() 93 | 94 | assert_received :private_called 95 | end 96 | end 97 | 98 | defp capture_err(fun) do 99 | ExUnit.CaptureIO.capture_io(:stderr, fun) 100 | end 101 | 102 | defp ensure_env_is_reset(app, setting) do 103 | original_result = Application.fetch_env(app, setting) 104 | 105 | on_exit(fn -> 106 | case original_result do 107 | {:ok, val} -> Application.put_env(app, setting, val) 108 | :error -> Application.delete_env(app, setting) 109 | end 110 | end) 111 | end 112 | 113 | defp purge_on_exit(module) do 114 | on_exit(fn -> purge(module) end) 115 | end 116 | 117 | defp purge(module) do 118 | :code.purge(module) 119 | :code.delete(module) 120 | end 121 | 122 | defp flush do 123 | receive do 124 | _ -> flush() 125 | after 126 | 0 -> :ok 127 | end 128 | end 129 | end 130 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | --------------------------------------------------------------------------------