Packages
An extension on `Ecto.Schema` where you can provide additional options, which will be read by the corresponding `PropSchema.TestHarness` module, used in the test files to generate property tests.
Current section
16 Versions
Jump to
Current section
16 Versions
Compare versions
21
files changed
+299
additions
-266
deletions
| @@ -5,16 +5,6 @@ | |
| 5 5 | An extension of both `Ecto.Schema` and `ExUnitProperties` for auto-generation of changeset property tests. |
| 6 6 | Consists of two main modules: `PropSchema` and `PropSchema.TestHarness` |
| 7 7 | |
| 8 | - ## Installation |
| 9 | - |
| 10 | - ```elixir |
| 11 | - def deps do |
| 12 | - [ |
| 13 | - {:prop_schema, "~> 0.1.0"} |
| 14 | - ] |
| 15 | - end |
| 16 | - ``` |
| 17 | - |
| 18 8 | ## Usage |
| 19 9 | |
| 20 10 | #### Look at the documentation for full usage |
| @@ -6,23 +6,29 @@ | |
| 6 6 | {<<"files">>, |
| 7 7 | [<<"lib">>,<<"lib/mix">>,<<"lib/mix/tasks">>,<<"lib/mix/tasks/print.ex">>, |
| 8 8 | <<"lib/prop_schema">>,<<"lib/prop_schema.ex">>, |
| 9 | - <<"lib/prop_schema/additional_properties.ex">>, |
| 9 | + <<"lib/prop_schema/customizers">>, |
| 10 | + <<"lib/prop_schema/customizers/additional_properties.ex">>, |
| 11 | + <<"lib/prop_schema/customizers/modifications.ex">>, |
| 10 12 | <<"lib/prop_schema/examples">>,<<"lib/prop_schema/examples/animagus">>, |
| 11 13 | <<"lib/prop_schema/examples/animagus/csv_mapping.ex">>, |
| 12 14 | <<"lib/prop_schema/examples/animagus/csv_mapping_properties.ex">>, |
| 13 15 | <<"lib/prop_schema/examples/animagus/results_of_print.exs">>, |
| 14 | - <<"lib/prop_schema/examples/example_additional_properties.ex">>, |
| 15 | - <<"lib/prop_schema/examples/example_module.ex">>, |
| 16 | - <<"lib/prop_schema/examples/example_owner.ex">>, |
| 16 | + <<"lib/prop_schema/examples/customizers">>, |
| 17 | + <<"lib/prop_schema/examples/customizers/example_additional_properties.ex">>, |
| 18 | + <<"lib/prop_schema/examples/customizers/example_modifications.ex">>, |
| 17 19 | <<"lib/prop_schema/examples/example_streamed.ex">>, |
| 18 | - <<"lib/prop_schema/executor.ex">>,<<"lib/prop_schema/generator">>, |
| 20 | + <<"lib/prop_schema/examples/schemas">>, |
| 21 | + <<"lib/prop_schema/examples/schemas/example_module.ex">>, |
| 22 | + <<"lib/prop_schema/examples/schemas/example_owner.ex">>, |
| 23 | + <<"lib/prop_schema/generator">>, |
| 19 24 | <<"lib/prop_schema/generator/base_properties.ex">>, |
| 25 | + <<"lib/prop_schema/generator/expression_modifier.ex">>, |
| 20 26 | <<"lib/prop_schema/generator/generator.ex">>, |
| 21 27 | <<"lib/prop_schema/stream.ex">>,<<"lib/prop_schema/test_harness.ex">>, |
| 22 28 | <<"mix.exs">>,<<"README.md">>]}. |
| 23 29 | {<<"licenses">>,[<<"MIT">>]}. |
| 24 30 | {<<"links">>, |
| 25 | - [{<<"Docs">>,<<"https://hexdocs.pm/prop_schema/0.4.1">>}, |
| 31 | + [{<<"Docs">>,<<"https://hexdocs.pm/prop_schema/1.0.0">>}, |
| 26 32 | {<<"GitHub">>,<<"https://github.com/podium/prop_schema">>}]}. |
| 27 33 | {<<"name">>,<<"prop_schema">>}. |
| 28 34 | {<<"requirements">>, |
| @@ -36,4 +42,4 @@ | |
| 36 42 | {<<"optional">>,false}, |
| 37 43 | {<<"repository">>,<<"hexpm">>}, |
| 38 44 | {<<"requirement">>,<<"~> 0.4">>}]]}. |
| 39 | - {<<"version">>,<<"0.4.1">>}. |
| 45 | + {<<"version">>,<<"1.0.0">>}. |
| @@ -1,9 +1,9 @@ | |
| 1 1 | defmodule Mix.Tasks.PropSchema.Print do |
| 2 2 | use Mix.Task |
| 3 3 | alias Mix.Tasks.Compile |
| 4 | - alias PropSchema.Executor |
| 4 | + alias PropSchema.TestHarness |
| 5 5 | require Logger |
| 6 | - require Executor |
| 6 | + require TestHarness |
| 7 7 | |
| 8 8 | @moduledoc """ |
| 9 9 | Prints the property tests generated for the given module. Use `--help` to see usage directions. |
| @@ -65,6 +65,8 @@ defmodule Mix.Tasks.PropSchema.Print do | |
| 65 65 | output_path Will output the results to a file at the given path. If file doesn't exist, it will be created |
| 66 66 | additional_props A module where the custom properties you have written are found. Make sure the compiler will |
| 67 67 | actually compile the module for the dev environemnt. |
| 68 | + filters A module where the custom filters you have written are found. Make sure the compiler will |
| 69 | + actually compile the module for the dev environemnt. |
| 68 70 | """ |
| 69 71 | |
| 70 72 | def run(args) do |
| @@ -73,9 +75,10 @@ defmodule Mix.Tasks.PropSchema.Print do | |
| 73 75 | Compile.run(args) |
| 74 76 | module = get_module(argv) |
| 75 77 | additional = get_additional(parsed) |
| 78 | + filters = get_filters(parsed) |
| 76 79 | |
| 77 80 | module |
| 78 | - |> get_prop_tests(additional) |
| 81 | + |> get_prop_tests(additional, filters) |
| 79 82 | |> Enum.map(&Macro.to_string(&1)) |
| 80 83 | |> Enum.join("\n\n") |
| 81 84 | |> output(module, parsed) |
| @@ -84,7 +87,12 @@ defmodule Mix.Tasks.PropSchema.Print do | |
| 84 87 | defp parse_args(args) do |
| 85 88 | {parsed, argv, errors} = |
| 86 89 | OptionParser.parse(args, |
| 87 | - strict: [help: :boolean, output_path: :string, additional_props: :string] |
| 90 | + strict: [ |
| 91 | + help: :boolean, |
| 92 | + output_path: :string, |
| 93 | + additional_props: :string, |
| 94 | + filters: :string |
| 95 | + ] |
| 88 96 | ) |
| 89 97 | |
| 90 98 | Enum.each(errors, fn error -> Logger.warn("Erroneous option given: #{inspect(error)}") end) |
| @@ -111,18 +119,30 @@ defmodule Mix.Tasks.PropSchema.Print do | |
| 111 119 | end |
| 112 120 | end |
| 113 121 | |
| 114 | - defp get_prop_tests(module, additional) do |
| 122 | + defp get_filters(parsed) do |
| 123 | + case Keyword.get(parsed, :filters) do |
| 124 | + nil -> |
| 125 | + nil |
| 126 | + |
| 127 | + additional -> |
| 128 | + String.to_existing_atom("Elixir." <> additional) |
| 129 | + end |
| 130 | + end |
| 131 | + |
| 132 | + defp get_prop_tests(module, additional, filters) do |
| 115 133 | List.flatten([ |
| 116 | - Executor.__create_prop_test__( |
| 134 | + TestHarness.__create_prop_test__( |
| 117 135 | module, |
| 118 136 | :all_fields, |
| 119 137 | module.__prop_schema__(), |
| 120 | - additional |
| 138 | + additional, |
| 139 | + filters |
| 121 140 | ), |
| 122 | - Executor.__create_prop_test__( |
| 141 | + TestHarness.__create_prop_test__( |
| 123 142 | module, |
| 124 143 | module.__prop_schema__(), |
| 125 | - additional |
| 144 | + additional, |
| 145 | + filters |
| 126 146 | ) |
| 127 147 | ]) |
| 128 148 | end |
| @@ -62,8 +62,6 @@ defmodule PropSchema do | |
| 62 62 | Or for a more realistic example see example: [module](https://github.com/podium/prop_schema/blob/master/lib/prop_schema/examples/animagus/csv_mapping.ex), [properties](https://github.com/podium/prop_schema/blob/master/lib/prop_schema/examples/animagus/csv_mapping_properties.ex), and [generated tests](https://github.com/podium/prop_schema/blob/master/lib/prop_schema/examples/animagus/results_of_print.ex). |
| 63 63 | """ |
| 64 64 | |
| 65 | - alias PropSchema.Types |
| 66 | - |
| 67 65 | defmacro __using__(_opts) do |
| 68 66 | quote do |
| 69 67 | import PropSchema, only: [prop_schema: 2, prop_embedded: 1, prop_embedded_schema: 1] |
| @@ -90,7 +88,7 @@ defmodule PropSchema do | |
| 90 88 | field(:example_float, :float) |
| 91 89 | end |
| 92 90 | """ |
| 93 | - @spec prop_schema(String.t(), do: Types.ast_expression()) :: Types.ast_expression() |
| 91 | + @spec prop_schema(String.t(), do: Macro.t()) :: Macro.t() |
| 94 92 | defmacro prop_schema(source, do: block) do |
| 95 93 | schema(source, block) |
| 96 94 | end |
| @@ -99,7 +97,7 @@ defmodule PropSchema do | |
| 99 97 | Deprecated in favor of `prop_embedded_schema/1`, see for usage details. |
| 100 98 | """ |
| 101 99 | @deprecated "Use prop_embedded_schema/1 instead" |
| 102 | - @spec prop_embedded(do: Types.ast_expression()) :: Types.ast_expression() |
| 100 | + @spec prop_embedded(do: Macro.t()) :: Macro.t() |
| 103 101 | defmacro prop_embedded(do: block) do |
| 104 102 | embedded_schema(block) |
| 105 103 | end |
| @@ -115,7 +113,7 @@ defmodule PropSchema do | |
| 115 113 | field(:example_float, :float) |
| 116 114 | end |
| 117 115 | """ |
| 118 | - @spec prop_embedded_schema(do: Types.ast_expression()) :: Types.ast_expression() |
| 116 | + @spec prop_embedded_schema(do: Macro.t()) :: Macro.t() |
| 119 117 | defmacro prop_embedded_schema(do: block) do |
| 120 118 | embedded_schema(block) |
| 121 119 | end |
| @@ -153,7 +151,7 @@ defmodule PropSchema do | |
| 153 151 | @doc """ |
| 154 152 | Declares a field in the schema, processes it for use in `PropSchema.TestHarness` and then passes it through to `Ecto.Schema.field/3`. See `prop_schema/2` for examples. |
| 155 153 | """ |
| 156 | - @spec prop_field(atom(), atom(), keyword()) :: Types.ast_expression() |
| 154 | + @spec prop_field(atom(), atom(), keyword()) :: Macro.t() |
| 157 155 | defmacro prop_field(name, type \\ :string, opts \\ []) do |
| 158 156 | quote do |
| 159 157 | PropSchema.__field__(__MODULE__, unquote(name), unquote(type), unquote(opts)) |
| @@ -189,7 +187,7 @@ defmodule PropSchema do | |
| 189 187 | Declares a field in `__prop_schema__/2` which will either be the `name` with `"_id"` appended or the value of the `:foreign_key` option. |
| 190 188 | A UUID generator is provided for uid ids. |
| 191 189 | """ |
| 192 | - @spec prop_belongs_to(atom(), module(), Keyword.t()) :: Types.ast_expression() |
| 190 | + @spec prop_belongs_to(atom(), module(), Keyword.t()) :: Macro.t() |
| 193 191 | defmacro prop_belongs_to(name, queryable, opts \\ []) do |
| 194 192 | ecto_opts = |
| 195 193 | Enum.reject(opts, fn {k, _v} -> not Enum.member?(@valid_belongs_to_options, k) end) |
| @@ -221,7 +219,7 @@ defmodule PropSchema do | |
| 221 219 | The only addition to the normal `Ecto.Schema.has_one/3` call is the `:additional_props` option which will tell the generator where |
| 222 220 | to find the additional props for building the associated struct. |
| 223 221 | """ |
| 224 | - @spec prop_has_one(atom(), module(), Keyword.t()) :: Types.ast_expression() |
| 222 | + @spec prop_has_one(atom(), module(), Keyword.t()) :: Macro.t() |
| 225 223 | defmacro prop_has_one(name, queryable, opts \\ []) do |
| 226 224 | ecto_opts = Enum.reject(opts, fn {k, _v} -> not Enum.member?(@valid_has_options, k) end) |
| 227 225 | |
| @@ -242,7 +240,7 @@ defmodule PropSchema do | |
| 242 240 | The only addition to the normal `Ecto.Schema.has_many/3` call is the `:additional_props` option which will tell the generator where |
| 243 241 | to find the additional props for building the associated structs. |
| 244 242 | """ |
| 245 | - @spec prop_has_many(atom(), module(), Keyword.t()) :: Types.ast_expression() |
| 243 | + @spec prop_has_many(atom(), module(), Keyword.t()) :: Macro.t() |
| 246 244 | defmacro prop_has_many(name, queryable, opts \\ []) do |
| 247 245 | ecto_opts = Enum.reject(opts, fn {k, _v} -> not Enum.member?(@valid_has_options, k) end) |
| 248 246 | |
| @@ -275,7 +273,7 @@ defmodule PropSchema do | |
| 275 273 | associated struct's `__prop_schema__/2` results will be modified so as to not generate it's associated many_to_many |
| 276 274 | structs due to the endless recursion that would cause. |
| 277 275 | """ |
| 278 | - @spec prop_many_to_many(atom(), module(), Keyword.t()) :: Types.ast_expression() |
| 276 | + @spec prop_many_to_many(atom(), module(), Keyword.t()) :: Macro.t() |
| 279 277 | defmacro prop_many_to_many(name, queryable, opts \\ []) do |
| 280 278 | ecto_opts = |
| 281 279 | Enum.reject(opts, fn {k, _v} -> not Enum.member?(@valid_many_to_many_options, k) end) |
| @@ -298,7 +296,7 @@ defmodule PropSchema do | |
| 298 296 | The only addition to the normal `Ecto.Schema.embeds_one/3` call is the `:additional_props` option which will tell the generator where |
| 299 297 | to find the additional props for building the associated struct. |
| 300 298 | """ |
| 301 | - @spec prop_embeds_one(atom(), module(), Keyword.t()) :: Types.ast_expression() |
| 299 | + @spec prop_embeds_one(atom(), module(), Keyword.t()) :: Macro.t() |
| 302 300 | defmacro prop_embeds_one(name, queryable, opts \\ []) do |
| 303 301 | ecto_opts = |
| 304 302 | Enum.reject(opts, fn {k, _v} -> not Enum.member?(@valid_embeds_one_options, k) end) |
| @@ -322,7 +320,7 @@ defmodule PropSchema do | |
| 322 320 | The only addition to the normal `Ecto.Schema.embeds_many/3` call is the `:additional_props` option which will tell the generator where |
| 323 321 | to find the additional props for building the associated structs. |
| 324 322 | """ |
| 325 | - @spec prop_embeds_many(atom(), module(), Keyword.t()) :: Types.ast_expression() |
| 323 | + @spec prop_embeds_many(atom(), module(), Keyword.t()) :: Macro.t() |
| 326 324 | defmacro prop_embeds_many(name, queryable, opts \\ []) do |
| 327 325 | ecto_opts = |
| 328 326 | Enum.reject(opts, fn {k, _v} -> not Enum.member?(@valid_embeds_many_options, k) end) |
| @@ -1,37 +0,0 @@ | |
| 1 | - defmodule PropSchema.AdditionalProperties do |
| 2 | - @moduledoc """ |
| 3 | - A behaviour that is used to define additional properties not provided in the base properties module. |
| 4 | - |
| 5 | - ### How to Implement |
| 6 | - |
| 7 | - Once you have implemented the callbacks below (see `c:generate_prop/3` below), add the module that implements this behaviour as the option `additional_properties` in the `PropSchema.TestHarness.__using__/1` declaration. |
| 8 | - """ |
| 9 | - |
| 10 | - @doc """ |
| 11 | - Implement to define additional properties not provided in the `PropSchema.BaseProperties` module. |
| 12 | - |
| 13 | - ## Example |
| 14 | - |
| 15 | - def generate_prop(field, :float, %{positive: true, required: true}) do |
| 16 | - quote do |
| 17 | - {unquote(Atom.to_string(field)), float(min: 1)} |
| 18 | - end |
| 19 | - end |
| 20 | - """ |
| 21 | - @callback generate_prop(atom(), atom(), map()) :: [{String.t(), StreamData.t()}] |
| 22 | - @optional_callbacks generate_prop: 3 |
| 23 | - |
| 24 | - @doc """ |
| 25 | - Implement to define miscellaneous properties. |
| 26 | - |
| 27 | - ## Example |
| 28 | - |
| 29 | - def generate_misc(:test_int) do |
| 30 | - quote do |
| 31 | - {"decoy_int", integer()} |
| 32 | - end |
| 33 | - end |
| 34 | - """ |
| 35 | - @callback generate_misc(excluded :: atom()) :: [{String.t(), StreamData.t()}] |
| 36 | - @optional_callbacks generate_misc: 1 |
| 37 | - end |
Loading more files…