From cf628cea35c0866d2fc47f2c395f85d7fe2a1f6d Mon Sep 17 00:00:00 2001 From: Christophe De Troyer Date: Wed, 11 Dec 2024 12:52:44 +0100 Subject: [PATCH] copy tag --- .credo.exs | 217 +++++++++++++++++++++++++++++++++ lib/ex_example.ex | 17 ++- lib/ex_example/executor.ex | 15 ++- lib/examples/stack_examples.ex | 5 + 4 files changed, 250 insertions(+), 4 deletions(-) create mode 100644 .credo.exs diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..ce2973c --- /dev/null +++ b/.credo.exs @@ -0,0 +1,217 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any config using `mix credo -C `. If no config name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: [ + "lib/", + "src/", + "test/", + "web/", + "apps/*/lib/", + "apps/*/src/", + "apps/*/test/", + "apps/*/web/" + ], + excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] + }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: false, + # + # To modify the timeout for parsing files, change this value: + # + parse_timeout: 5000, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: %{ + enabled: [ + # + ## Consistency Checks + # + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, + [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + {Credo.Check.Design.TagFIXME, []}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + + # + ## Readability Checks + # + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + # {Credo.Check.Readability.Specs, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithSingleClause, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.Apply, []}, + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Refactor.FilterCount, []}, + {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.RedundantWithClauseResult, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.Dbg, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.UnsafeExec, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.WrongTestFileExtension, []}, + ], + disabled: [ + # + # Checks scheduled for next check update (opt-in for now) + {Credo.Check.Refactor.UtcNowTruncate, []}, + + # + # Controversial and experimental checks (opt-in, just move the check to `:enabled` + # and be sure to use `mix credo --strict` to see low priority checks) + # + {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, + {Credo.Check.Consistency.UnusedVariableNames, []}, + {Credo.Check.Design.DuplicatedCode, []}, + {Credo.Check.Design.SkipTestWithoutComment, []}, + {Credo.Check.Readability.AliasAs, []}, + {Credo.Check.Readability.BlockPipe, []}, + {Credo.Check.Readability.ImplTrue, []}, + {Credo.Check.Readability.MultiAlias, []}, + {Credo.Check.Readability.NestedFunctionCalls, []}, + {Credo.Check.Readability.OneArityFunctionInPipe, []}, + {Credo.Check.Readability.OnePipePerLine, []}, + {Credo.Check.Readability.SeparateAliasRequire, []}, + {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, + {Credo.Check.Readability.SinglePipe, []}, + {Credo.Check.Readability.StrictModuleLayout, []}, + {Credo.Check.Readability.WithCustomTaggedTuple, []}, + {Credo.Check.Refactor.ABCSize, []}, + {Credo.Check.Refactor.AppendSingleItem, []}, + {Credo.Check.Refactor.DoubleBooleanNegation, []}, + {Credo.Check.Refactor.FilterReject, []}, + {Credo.Check.Refactor.IoPuts, []}, + {Credo.Check.Refactor.MapMap, []}, + {Credo.Check.Refactor.ModuleDependencies, []}, + {Credo.Check.Refactor.NegatedIsNil, []}, + {Credo.Check.Refactor.PassAsyncInTestCases, []}, + {Credo.Check.Refactor.PipeChainStart, []}, + {Credo.Check.Refactor.RejectFilter, []}, + {Credo.Check.Refactor.VariableRebinding, []}, + {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.MapGetUnsafePass, []}, + {Credo.Check.Warning.MixEnv, []}, + {Credo.Check.Warning.UnsafeToAtom, []} + + # {Credo.Check.Refactor.MapInto, []}, + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + } + ] +} diff --git a/lib/ex_example.ex b/lib/ex_example.ex index 35af520..295606e 100644 --- a/lib/ex_example.ex +++ b/lib/ex_example.ex @@ -12,6 +12,8 @@ defmodule ExExample do # module attribute that holds all the examples Module.register_attribute(__MODULE__, :example_dependencies, accumulate: true) Module.register_attribute(__MODULE__, :examples, accumulate: true) + Module.register_attribute(__MODULE__, :copies, accumulate: true) + Module.register_attribute(__MODULE__, :copy, accumulate: false) @before_compile unquote(__MODULE__) end @@ -69,6 +71,11 @@ defmodule ExExample do end) |> Graph.topsort() end + + def __example_copy__(example_name) do + @copies + |> Keyword.get(example_name, nil) + end end end @@ -84,16 +91,24 @@ defmodule ExExample do hidden_example_name = String.to_atom("__#{example_name}__") quote do + # fetch the attribute value, and then clear it for the next examples. + example_copy_tag = Module.get_attribute(unquote(__CALLER__.module), :copy) + Module.delete_attribute(unquote(__CALLER__.module), :copy) + def unquote({hidden_example_name, context, args}) do unquote(body) end + @copies {unquote(example_name), {unquote(__CALLER__.module), example_copy_tag}} @example_dependencies {unquote(example_name), unquote(called_functions)} @examples unquote(example_name) def unquote(name) do example_dependencies = __example_dependencies__(unquote(example_name)) + example_copy = __example_copy__(unquote(example_name)) - Executor.maybe_run_example(__MODULE__, unquote(example_name), example_dependencies) + Executor.maybe_run_example(__MODULE__, unquote(example_name), example_dependencies, + copy: example_copy + ) end end end diff --git a/lib/ex_example/executor.ex b/lib/ex_example/executor.ex index 9be0b58..63f5608 100644 --- a/lib/ex_example/executor.ex +++ b/lib/ex_example/executor.ex @@ -43,8 +43,8 @@ defmodule ExExample.Executor do If any of the example its dependencies either failed or were skipped, I will skip the example. """ - @spec maybe_run_example(atom(), atom(), list(dependency)) :: any() - def maybe_run_example(module, func, dependencies) do + @spec maybe_run_example(atom(), atom(), list(dependency), Keyword.t()) :: any() + def maybe_run_example(module, func, dependencies, copy: copy) do dependency_results = dependencies |> Enum.map(fn {{module, func}, _arity} -> @@ -64,7 +64,7 @@ defmodule ExExample.Executor do # cached result, no recompile {:ok, result} -> Logger.debug("found cached result for #{inspect(module)}.#{func}") - result.result + copy_result(result, copy).result {:error, :no_result} -> Logger.debug("running #{inspect(module)}.#{func} for the first time") @@ -110,4 +110,13 @@ defmodule ExExample.Executor do result end + + # @doc """ + # Given a result from a previous invocation and a copy function, I create a copy of the result. + # """ + defp copy_result(%Cache.Result{} = result, {_, nil}), do: result + + defp copy_result(%Cache.Result{} = result, {module, func}) do + %{result | result: apply(module, func, [result.result])} + end end diff --git a/lib/examples/stack_examples.ex b/lib/examples/stack_examples.ex index c9a361d..0f86229 100644 --- a/lib/examples/stack_examples.ex +++ b/lib/examples/stack_examples.ex @@ -6,6 +6,7 @@ defmodule Examples.Stack do import ExUnit.Assertions + @copy :do_copy example new_stack do {:ok, stack} = Stack.create() assert stack == %Stack{} @@ -29,4 +30,8 @@ defmodule Examples.Stack do {:ok, stack, 1} = Stack.pop(stack) stack end + + def do_copy(stack) do + %Stack{elements: stack.elements} + end end