diff --git a/.github/workflows/elixir_test.yml b/.github/workflows/elixir_test.yml index 2a25d5a3..62b2855a 100644 --- a/.github/workflows/elixir_test.yml +++ b/.github/workflows/elixir_test.yml @@ -11,6 +11,9 @@ jobs: steps: - uses: actions/checkout@v1 + with: + submodules: true + - name: Install Dependencies run: | mix local.rebar --force diff --git a/.gitignore b/.gitignore index e9fad5c6..f2b1435c 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,8 @@ ex_mentor-*.tar test_results.json analysis.json +.mix/ + tmp/ /priv/plts/*.plt diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..2212afac --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "elixir"] + path = elixir + url = https://github.com/exercism/elixir.git diff --git a/config/test.exs b/config/test.exs index 279392a4..28c45b13 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,3 +1,3 @@ use Mix.Config -config :logger, level: :error +config :logger, level: :warn diff --git a/elixir b/elixir new file mode 160000 index 00000000..1a1cec4e --- /dev/null +++ b/elixir @@ -0,0 +1 @@ +Subproject commit 1a1cec4e27757edfdd4f59354b27c2ae5d054adc diff --git a/lib/elixir_analyzer.ex b/lib/elixir_analyzer.ex index d548bf40..401caee5 100644 --- a/lib/elixir_analyzer.ex +++ b/lib/elixir_analyzer.ex @@ -43,8 +43,8 @@ defmodule ElixirAnalyzer do * `:output_file`, - specifies the name of the output_file, defaults to `@output_file` (`analysis.json`) - * `:exercise_config` - specifies the path to the JSON exercise configuration, - defaults to `@exercise_config` (`./config/exercise_data.json`) + * `:exercise_config` - specifies the path to the exercise configuration, + defaults to `@exercise_config` (`./config/config.exs`) * `:write_results` - boolean flag if an analysis should output the results to JSON file, defaults to `true` @@ -110,11 +110,12 @@ defmodule ElixirAnalyzer do try do Logger.debug("Getting the exercise config") exercise_config = params.exercise_config[params.exercise] - {code_path, code_file, analysis_module} = do_init(params, exercise_config) + {code_path, code_file, exemplar_path, analysis_module} = do_init(params, exercise_config) Logger.debug("Initialization successful", path: params.path, code_path: code_path, + exemplar_path: exemplar_path, analysis_module: analysis_module ) @@ -131,6 +132,7 @@ defmodule ElixirAnalyzer do submission | code_path: code_path, code_file: code_file, + exemplar_path: exemplar_path, analysis_module: analysis_module } rescue @@ -166,7 +168,14 @@ defmodule ElixirAnalyzer do code_path = Path.dirname(full_code_path) code_file = Path.basename(full_code_path) - {code_path, code_file, exercise_config[:analyzer_module] || ElixirAnalyzer.TestSuite.Default} + exemplar_path = + case meta_config["files"]["exemplar"] do + [path | _] -> Path.join(params.path, path) + _ -> nil + end + + {code_path, code_file, exemplar_path, + exercise_config[:analyzer_module] || ElixirAnalyzer.TestSuite.Default} end # Else, use passed in params to analyze @@ -181,7 +190,9 @@ defmodule ElixirAnalyzer do # Check # - check if the file exists # - read in the code - # - compile + # - check if there is an exemplar + # - read in the exemplar + # - parse the exemplar into an AST defp check(%Submission{halted: true} = submission, _params) do Logger.warning("Check not performed, halted previously") submission @@ -192,7 +203,19 @@ defmodule ElixirAnalyzer do :ok <- Logger.info("Attempting to read code file", code_file_path: path_to_code), {:code_read, {:ok, code_str}} <- {:code_read, File.read(path_to_code)}, :ok <- Logger.info("Code file read successfully"), - {:code_str, submission} <- {:code_str, %{submission | code: code_str}} do + submission <- %{submission | code: code_str}, + :ok <- Logger.info("Check if exemplar exists", exemplar_path: submission.exemplar_path), + {:exemplar_exists, submission, exemplar_path} when not is_nil(exemplar_path) <- + {:exemplar_exists, submission, submission.exemplar_path}, + :ok <- + Logger.info("Exemplar file exists, attempting to read", exemplar_path: exemplar_path), + {:exemplar_read, submission, {:ok, exemplar_code}} <- + {:exemplar_read, submission, File.read(exemplar_path)}, + :ok <- Logger.info("Exemplar file read successfully, attempting to parse"), + {:exemplar_ast, submission, {:ok, exemplar_code}} <- + {:exemplar_ast, submission, Code.string_to_quoted(exemplar_code)}, + :ok <- Logger.info("Exemplar file parsed successfully"), + submission <- %{submission | exemplar_code: exemplar_code} do submission else {:code_read, {:error, reason}} -> @@ -211,6 +234,24 @@ defmodule ElixirAnalyzer do }, type: :essential }) + + {:exemplar_exists, submission, nil} -> + Logger.info("There is no exemplar file for this exercise") + submission + + {:exemplar_read, submission, {:error, reason}} -> + Logger.warning("Exemplar file not found. Reason: #{reason}", + exemplar_path: submission.exemplar_path + ) + + submission + + {:exemplar_ast, submission, {:error, reason}} -> + Logger.warning("Exemplar file could not be parsed. Reason: #{inspect(reason)}", + exemplar_code: submission.exemplar_code + ) + + submission end end @@ -226,7 +267,7 @@ defmodule ElixirAnalyzer do submission = submission - |> submission.analysis_module.analyze(submission.code) + |> submission.analysis_module.analyze(submission.code, submission.exemplar_code) |> Submission.set_analyzed(true) Logger.info("Analyzing code complete") diff --git a/lib/elixir_analyzer/constants.ex b/lib/elixir_analyzer/constants.ex index c1a88097..d63522b6 100644 --- a/lib/elixir_analyzer/constants.ex +++ b/lib/elixir_analyzer/constants.ex @@ -26,6 +26,7 @@ defmodule ElixirAnalyzer.Constants do solution_debug_functions: "elixir.solution.debug_functions", solution_last_line_assignment: "elixir.solution.last_line_assignment", solution_compiler_warnings: "elixir.solution.compiler_warnings", + solution_same_as_exemplar: "elixir.solution.same_as_exemplar", # Concept exercises diff --git a/lib/elixir_analyzer/exercise_test.ex b/lib/elixir_analyzer/exercise_test.ex index 2fd612b2..933e21dd 100644 --- a/lib/elixir_analyzer/exercise_test.ex +++ b/lib/elixir_analyzer/exercise_test.ex @@ -19,7 +19,7 @@ defmodule ElixirAnalyzer.ExerciseTest do import unquote(__MODULE__) @before_compile unquote(__MODULE__) - @dialyzer no_match: {:do_analyze, 3} + @dialyzer no_match: {:do_analyze, 4} end end @@ -43,24 +43,24 @@ defmodule ElixirAnalyzer.ExerciseTest do assert_call_tests = Enum.map(assert_call_data, &AssertCallCompiler.compile(&1, code_ast)) quote do - @spec analyze(Submission.t(), String.t()) :: Submission.t() - def analyze(%Submission{} = submission, code_as_string) when is_binary(code_as_string) do + @spec analyze(Submission.t(), String.t(), nil | Macro.t()) :: Submission.t() + def analyze(%Submission{} = submission, code_as_string, exemplar_ast) do case Code.string_to_quoted(code_as_string) do {:ok, code_ast} -> - do_analyze(submission, code_ast, code_as_string) + do_analyze(submission, code_ast, code_as_string, exemplar_ast) {:error, e} -> append_analysis_failure(submission, e) end end - defp do_analyze(%Submission{} = submission, code_ast, code_as_string) + defp do_analyze(%Submission{} = submission, code_ast, code_as_string, exemplar_ast) when is_binary(code_as_string) do results = Enum.concat([ unquote(feature_tests), unquote(assert_call_tests), - CommonChecks.run(code_ast, code_as_string) + CommonChecks.run(code_ast, code_as_string, exemplar_ast) ]) |> filter_suppressed_results() diff --git a/lib/elixir_analyzer/exercise_test/common_checks.ex b/lib/elixir_analyzer/exercise_test/common_checks.ex index 963ee97c..183a9c71 100644 --- a/lib/elixir_analyzer/exercise_test/common_checks.ex +++ b/lib/elixir_analyzer/exercise_test/common_checks.ex @@ -9,6 +9,7 @@ defmodule ElixirAnalyzer.ExerciseTest.CommonChecks do ModuleAttributeNames, ModulePascalCase, CompilerWarnings, + ExemplarComparison, Indentation } @@ -23,14 +24,15 @@ defmodule ElixirAnalyzer.ExerciseTest.CommonChecks do end end - @spec run(Macro.t(), String.t()) :: [{:pass | :fail | :skip, %Comment{}}] - def run(code_ast, code_as_string) when is_binary(code_as_string) do + @spec run(Macro.t(), String.t(), nil | Macro.t()) :: [{:pass | :fail | :skip, %Comment{}}] + def run(code_ast, code_as_string, exemplar_ast) when is_binary(code_as_string) do [ FunctionNames.run(code_ast), VariableNames.run(code_ast), ModuleAttributeNames.run(code_ast), ModulePascalCase.run(code_ast), CompilerWarnings.run(code_ast), + ExemplarComparison.run(code_ast, exemplar_ast), Indentation.run(code_ast, code_as_string) ] |> List.flatten() diff --git a/lib/elixir_analyzer/exercise_test/common_checks/exemplar_comparaison.ex b/lib/elixir_analyzer/exercise_test/common_checks/exemplar_comparaison.ex new file mode 100644 index 00000000..f94a6451 --- /dev/null +++ b/lib/elixir_analyzer/exercise_test/common_checks/exemplar_comparaison.ex @@ -0,0 +1,27 @@ +defmodule ElixirAnalyzer.ExerciseTest.CommonChecks.ExemplarComparison do + @moduledoc """ + Compares the solution to the exemplar solution for concept exercises. + Ignores practice exercises. + """ + + alias ElixirAnalyzer.Constants + alias ElixirAnalyzer.Comment + + @spec run(Macro.t(), nil | Macro.t()) :: [{:pass | :fail | :skip, %Comment{}}] + def run(_ast, nil), do: [] + + def run(code_ast, exemplar_ast) do + if Macro.to_string(code_ast) == Macro.to_string(exemplar_ast) do + [ + {:pass, + %Comment{ + type: :celebratory, + name: Constants.solution_same_as_exemplar(), + comment: Constants.solution_same_as_exemplar() + }} + ] + else + [] + end + end +end diff --git a/lib/elixir_analyzer/submission.ex b/lib/elixir_analyzer/submission.ex index 083b37ee..18c58579 100644 --- a/lib/elixir_analyzer/submission.ex +++ b/lib/elixir_analyzer/submission.ex @@ -29,6 +29,8 @@ defmodule ElixirAnalyzer.Submission do path: nil, code_path: nil, code_file: nil, + exemplar_path: nil, + exemplar_code: nil, code: nil, analysis_module: nil @@ -40,6 +42,8 @@ defmodule ElixirAnalyzer.Submission do path: String.t(), code_path: String.t(), code_file: String.t(), + exemplar_path: String.t() | nil, + exemplar_code: Macro.t() | nil, code: String.t(), analysis_module: atom() } diff --git a/test/elixir_analyzer/test_suite/bird_count_test.exs b/test/elixir_analyzer/test_suite/bird_count_test.exs index 401a4bdc..ed9da8e5 100644 --- a/test/elixir_analyzer/test_suite/bird_count_test.exs +++ b/test/elixir_analyzer/test_suite/bird_count_test.exs @@ -3,7 +3,7 @@ defmodule ElixirAnalyzer.ExerciseTest.BirdCountTest do exercise_test_module: ElixirAnalyzer.TestSuite.BirdCount test_exercise_analysis "example solution", - comments: [] do + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do defmodule BirdCount do def today([]), do: nil def today([head | _]), do: head diff --git a/test/elixir_analyzer/test_suite/boutique_suggestions_test.exs b/test/elixir_analyzer/test_suite/boutique_suggestions_test.exs index 2bd14b85..166f5264 100644 --- a/test/elixir_analyzer/test_suite/boutique_suggestions_test.exs +++ b/test/elixir_analyzer/test_suite/boutique_suggestions_test.exs @@ -2,8 +2,8 @@ defmodule ElixirAnalyzer.ExerciseTest.BoutiqueSuggestionsTest do use ElixirAnalyzer.ExerciseTestCase, exercise_test_module: ElixirAnalyzer.TestSuite.BoutiqueSuggestions - test_exercise_analysis "correct solutions", - comments: [] do + test_exercise_analysis "example solution", + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do [ defmodule BoutiqueSuggestions do def get_combinations(tops, bottoms, options \\ []) do @@ -18,7 +18,13 @@ defmodule ElixirAnalyzer.ExerciseTest.BoutiqueSuggestionsTest do {top, bottom} end end - end, + end + ] + end + + test_exercise_analysis "correct solutions", + comments: [] do + [ defmodule BoutiqueSuggestions do def get_combinations(tops, bottoms, options \\ []) do maximum_price = Keyword.get(options, :maximum_price, 100.00) diff --git a/test/elixir_analyzer/test_suite/chesboard_test.exs b/test/elixir_analyzer/test_suite/chesboard_test.exs index 4350c0fb..1fca7eb8 100644 --- a/test/elixir_analyzer/test_suite/chesboard_test.exs +++ b/test/elixir_analyzer/test_suite/chesboard_test.exs @@ -3,7 +3,7 @@ defmodule ElixirAnalyzer.ExerciseTest.ChessboardTest do exercise_test_module: ElixirAnalyzer.TestSuite.Chessboard test_exercise_analysis "example solution", - comments: [] do + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do defmodule Chessboard do def rank_range do 1..8 diff --git a/test/elixir_analyzer/test_suite/file_sniffer_test.exs b/test/elixir_analyzer/test_suite/file_sniffer_test.exs index 09f64de0..2686e606 100644 --- a/test/elixir_analyzer/test_suite/file_sniffer_test.exs +++ b/test/elixir_analyzer/test_suite/file_sniffer_test.exs @@ -3,7 +3,7 @@ defmodule ElixirAnalyzer.ExerciseTest.FileSnifferTest do exercise_test_module: ElixirAnalyzer.TestSuite.FileSniffer test_exercise_analysis "example solution", - comments: [] do + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do [ defmodule FileSniffer do def type_from_extension("bmp"), do: "image/bmp" @@ -33,7 +33,13 @@ defmodule ElixirAnalyzer.ExerciseTest.FileSnifferTest do {:error, "Warning, file format and file extension do not match."} end end - end, + end + ] + end + + test_exercise_analysis "other solutions", + comments: [] do + [ defmodule FileSniffer do def type_from_binary(<>), do: "image/bmp" diff --git a/test/elixir_analyzer/test_suite/freelancer_rates_test.exs b/test/elixir_analyzer/test_suite/freelancer_rates_test.exs index cd5f5de3..76c92053 100644 --- a/test/elixir_analyzer/test_suite/freelancer_rates_test.exs +++ b/test/elixir_analyzer/test_suite/freelancer_rates_test.exs @@ -3,7 +3,7 @@ defmodule ElixirAnalyzer.ExerciseTest.FreelancerRatesTest do exercise_test_module: ElixirAnalyzer.TestSuite.FreelancerRates test_exercise_analysis "example solution", - comments: [] do + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do [ defmodule FreelancerRates do def daily_rate(hourly_rate) do @@ -26,7 +26,13 @@ defmodule ElixirAnalyzer.ExerciseTest.FreelancerRatesTest do days_in_budget = budget / daily_rate_after_discount Float.floor(days_in_budget, 1) end - end, + end + ] + end + + test_exercise_analysis "other solutions", + comments: [] do + [ defmodule FreelancerRates do def daily_rate(hourly_rate) do hourly_rate * 8.0 diff --git a/test/elixir_analyzer/test_suite/german_sysadmin_test.exs b/test/elixir_analyzer/test_suite/german_sysadmin_test.exs index 0eeaea09..9d71d19a 100644 --- a/test/elixir_analyzer/test_suite/german_sysadmin_test.exs +++ b/test/elixir_analyzer/test_suite/german_sysadmin_test.exs @@ -3,7 +3,7 @@ defmodule ElixirAnalyzer.ExerciseTest.GermanSysadminTest do exercise_test_module: ElixirAnalyzer.TestSuite.GermanSysadmin test_exercise_analysis "example solution", - comments: [] do + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do defmodule Username do def sanitize('') do '' diff --git a/test/elixir_analyzer/test_suite/guessing_game_test.exs b/test/elixir_analyzer/test_suite/guessing_game_test.exs index 860be0d1..51e44a82 100644 --- a/test/elixir_analyzer/test_suite/guessing_game_test.exs +++ b/test/elixir_analyzer/test_suite/guessing_game_test.exs @@ -3,7 +3,7 @@ defmodule ElixirAnalyzer.ExerciseTest.GuessingGameTest do exercise_test_module: ElixirAnalyzer.TestSuite.GuessingGame test_exercise_analysis "example solution", - comments: [] do + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do defmodule GuessingGame do def compare(secret_number, guess \\ :no_guess) diff --git a/test/elixir_analyzer/test_suite/high_score_test.exs b/test/elixir_analyzer/test_suite/high_score_test.exs index b407fe59..4ad69cbb 100644 --- a/test/elixir_analyzer/test_suite/high_score_test.exs +++ b/test/elixir_analyzer/test_suite/high_score_test.exs @@ -3,7 +3,7 @@ defmodule ElixirAnalyzer.ExerciseTest.HighScoreTest do exercise_test_module: ElixirAnalyzer.TestSuite.HighScore test_exercise_analysis "example solution", - comments: [] do + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do [ defmodule HighScore do @initial_score 0 @@ -29,7 +29,13 @@ defmodule ElixirAnalyzer.ExerciseTest.HighScoreTest do def get_players(scores) do Map.keys(scores) end - end, + end + ] + end + + test_exercise_analysis "other solutions", + comments: [] do + [ defmodule HighScore do @initial_score 0 diff --git a/test/elixir_analyzer/test_suite/lasagna_test.exs b/test/elixir_analyzer/test_suite/lasagna_test.exs index 18de1d5e..42777450 100644 --- a/test/elixir_analyzer/test_suite/lasagna_test.exs +++ b/test/elixir_analyzer/test_suite/lasagna_test.exs @@ -1,9 +1,11 @@ defmodule ElixirAnalyzer.TestSuite.LasagnaTest do + alias ElixirAnalyzer.Constants + use ElixirAnalyzer.ExerciseTestCase, exercise_test_module: ElixirAnalyzer.TestSuite.Lasagna test_exercise_analysis "example solution", - comments: [] do + comments: [Constants.solution_same_as_exemplar()] do defmodule Lasagna do def expected_minutes_in_oven() do 40 diff --git a/test/elixir_analyzer/test_suite/log_level_test.exs b/test/elixir_analyzer/test_suite/log_level_test.exs index 603abddc..1d34f20c 100644 --- a/test/elixir_analyzer/test_suite/log_level_test.exs +++ b/test/elixir_analyzer/test_suite/log_level_test.exs @@ -3,7 +3,7 @@ defmodule ElixirAnalyzer.ExerciseTest.LogLevelTest do exercise_test_module: ElixirAnalyzer.TestSuite.LogLevel test_exercise_analysis "example solution", - comments: [] do + comments: [Constants.solution_same_as_exemplar()] do [ defmodule LogLevel do def to_label(level, legacy?) do diff --git a/test/elixir_analyzer/test_suite/name_badge_test.exs b/test/elixir_analyzer/test_suite/name_badge_test.exs index 3191843a..d12f3293 100644 --- a/test/elixir_analyzer/test_suite/name_badge_test.exs +++ b/test/elixir_analyzer/test_suite/name_badge_test.exs @@ -3,7 +3,7 @@ defmodule ElixirAnalyzer.ExerciseTest.NameBadgeTest do exercise_test_module: ElixirAnalyzer.TestSuite.NameBadge test_exercise_analysis "example solution", - comments: [] do + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do [ defmodule NameBadge do def print(id, name, department) do @@ -31,7 +31,13 @@ defmodule ElixirAnalyzer.ExerciseTest.NameBadgeTest do prefix <> "#{name} - #{String.upcase(department)}" end - end, + end + ] + end + + test_exercise_analysis "other solutions", + comments: [] do + [ defmodule NameBadge do def totally_not_if(a, b, c), do: if(a, do: b, else: c) diff --git a/test/elixir_analyzer/test_suite/need_for_speed_test.exs b/test/elixir_analyzer/test_suite/need_for_speed_test.exs index e78698e8..73b1273f 100644 --- a/test/elixir_analyzer/test_suite/need_for_speed_test.exs +++ b/test/elixir_analyzer/test_suite/need_for_speed_test.exs @@ -3,7 +3,7 @@ defmodule ElixirAnalyzer.TestSuite.NeedForSpeedTest do exercise_test_module: ElixirAnalyzer.TestSuite.NeedForSpeed test_exercise_analysis "perfect solution", - comments: [] do + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do ~S''' defmodule NeedForSpeed do alias NeedForSpeed.Race diff --git a/test/elixir_analyzer/test_suite/newsletter_test.exs b/test/elixir_analyzer/test_suite/newsletter_test.exs index 09fcafb1..7b71a5e7 100644 --- a/test/elixir_analyzer/test_suite/newsletter_test.exs +++ b/test/elixir_analyzer/test_suite/newsletter_test.exs @@ -3,7 +3,7 @@ defmodule ElixirAnalyzer.ExerciseTest.NewsletterTest do exercise_test_module: ElixirAnalyzer.TestSuite.Newsletter test_exercise_analysis "example solution", - comments: [] do + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do defmodule Newsletter do def read_emails(path) do path diff --git a/test/elixir_analyzer/test_suite/pacman_rules_test.exs b/test/elixir_analyzer/test_suite/pacman_rules_test.exs index e68c60d5..830fd218 100644 --- a/test/elixir_analyzer/test_suite/pacman_rules_test.exs +++ b/test/elixir_analyzer/test_suite/pacman_rules_test.exs @@ -3,7 +3,7 @@ defmodule ElixirAnalyzer.ExerciseTest.PacmanRulesTest do exercise_test_module: ElixirAnalyzer.TestSuite.PacmanRules test_exercise_analysis "example solution", - comments: [] do + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do defmodule Rules do def eat_ghost?(power_pellet_active, touching_ghost) do power_pellet_active and touching_ghost diff --git a/test/elixir_analyzer/test_suite/rpg_character_sheet_test.exs b/test/elixir_analyzer/test_suite/rpg_character_sheet_test.exs index 59b5ea48..ccd0405f 100644 --- a/test/elixir_analyzer/test_suite/rpg_character_sheet_test.exs +++ b/test/elixir_analyzer/test_suite/rpg_character_sheet_test.exs @@ -5,7 +5,7 @@ defmodule ElixirAnalyzer.TestSuite.RpgCharacterSheetTest do alias ElixirAnalyzer.Constants test_exercise_analysis "example solution", - comments: [] do + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do [ defmodule RPG.CharacterSheet do def welcome() do @@ -42,7 +42,13 @@ defmodule ElixirAnalyzer.TestSuite.RpgCharacterSheetTest do IO.inspect(character, label: "Your character") end - end, + end + ] + end + + test_exercise_analysis "other solutions", + comments: [] do + [ defmodule RPG.CharacterSheet do def welcome() do IO.puts("Welcome! Let's fill out your character sheet together.") diff --git a/test/elixir_analyzer/test_suite/rpn_calculator_output_test.exs b/test/elixir_analyzer/test_suite/rpn_calculator_output_test.exs index aef0c7bb..027ce4c0 100644 --- a/test/elixir_analyzer/test_suite/rpn_calculator_output_test.exs +++ b/test/elixir_analyzer/test_suite/rpn_calculator_output_test.exs @@ -3,7 +3,7 @@ defmodule ElixirAnalyzer.TestSuite.RpnCalculatorOutputTest do exercise_test_module: ElixirAnalyzer.TestSuite.RpnCalculatorOutput test_exercise_analysis "example solution", - comments: [] do + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do [ defmodule RPNCalculator.Output do def write(resource, filename, equation) do @@ -19,7 +19,13 @@ defmodule ElixirAnalyzer.TestSuite.RpnCalculatorOutputTest do resource.close(file) end end - end, + end + ] + end + + test_exercise_analysis "other solutions", + comments: [] do + [ defmodule RPNCalculator.Output do def write(my_resource, my_filename, my_equation) do {:ok, file} = my_resource.open(my_filename) diff --git a/test/elixir_analyzer/test_suite/take_a_number_test.exs b/test/elixir_analyzer/test_suite/take_a_number_test.exs index c265d1af..9ea9e0fc 100644 --- a/test/elixir_analyzer/test_suite/take_a_number_test.exs +++ b/test/elixir_analyzer/test_suite/take_a_number_test.exs @@ -3,7 +3,7 @@ defmodule ElixirAnalyzer.TestSuite.TakeANumberTest do exercise_test_module: ElixirAnalyzer.TestSuite.TakeANumber test_exercise_analysis "example solution", - comments: [] do + comments: [ElixirAnalyzer.Constants.solution_same_as_exemplar()] do defmodule TakeANumber do def start() do spawn(fn -> loop(0) end) diff --git a/test/elixir_analyzer_test.exs b/test/elixir_analyzer_test.exs index e8daa5c6..0d980c51 100644 --- a/test/elixir_analyzer_test.exs +++ b/test/elixir_analyzer_test.exs @@ -6,10 +6,9 @@ defmodule ElixirAnalyzerTest do alias ElixirAnalyzer.Submission - describe "ElixirAnalyzer" do + describe "ElixirAnalyzer for practice exercise" do @options [puts_summary: false, write_results: false] - # @tag :pending test "solution with no comments" do exercise = "two-fer" path = "./test_data/two_fer/perfect_solution/" @@ -22,7 +21,6 @@ defmodule ElixirAnalyzerTest do assert Submission.to_json(analyzed_exercise) == String.trim(expected_output) end - # @tag :pending test "referred solution with comments" do exercise = "two-fer" path = "./test_data/two_fer/imperfect_solution/" @@ -34,7 +32,6 @@ defmodule ElixirAnalyzerTest do assert Submission.to_json(analyzed_exercise) == expected_output end - # @tag :pending test "error solution" do exercise = "two-fer" path = "./test_data/two_fer/error_solution/" @@ -77,6 +74,60 @@ defmodule ElixirAnalyzerTest do end end + describe "ElixirAnalyzer for concept exercise" do + @options [puts_summary: false, write_results: false] + + test "perfect solution" do + exercise = "lasagna" + path = "./test_data/lasagna/perfect_solution/" + analyzed_exercise = ElixirAnalyzer.analyze_exercise(exercise, path, path, @options) + + expected_output = + "{\"comments\":[{\"comment\":\"elixir.solution.same_as_exemplar\",\"type\":\"celebratory\"}],\"summary\":\"🎉\"}" + + assert Submission.to_json(analyzed_exercise) == String.trim(expected_output) + end + + test "failing solution with comments" do + exercise = "lasagna" + path = "./test_data/lasagna/failing_solution/" + analyzed_exercise = ElixirAnalyzer.analyze_exercise(exercise, path, path, @options) + + expected_output = + "{\"comments\":[{\"comment\":\"elixir.lasagna.function_reuse\",\"type\":\"actionable\"}],\"summary\":\"Check the comments for some code suggestions. 📣\"}" + + assert Submission.to_json(analyzed_exercise) == expected_output + end + + test "solution with missing exemplar" do + exercise = "lasagna" + path = "./test_data/lasagna/missing_exemplar/" + + assert capture_log(fn -> + analyzed_exercise = ElixirAnalyzer.analyze_exercise(exercise, path, path, @options) + + expected_output = + "{\"comments\":[],\"summary\":\"Submission analyzed. No automated suggestions found.\"}" + + assert Submission.to_json(analyzed_exercise) == String.trim(expected_output) + end) =~ "Exemplar file not found. Reason: enoent" + end + + test "solution with parsing error for incomplete exemplar" do + exercise = "lasagna" + path = "./test_data/lasagna/wrong_exemplar/" + + assert capture_log(fn -> + analyzed_exercise = ElixirAnalyzer.analyze_exercise(exercise, path, path, @options) + + expected_output = + "{\"comments\":[],\"summary\":\"Submission analyzed. No automated suggestions found.\"}" + + assert Submission.to_json(analyzed_exercise) == String.trim(expected_output) + end) =~ "Exemplar file could not be parsed." + end + end + describe "config" do test "every available exercise test suite assigned to an exercise slug in the config" do {:ok, modules} = :application.get_key(:elixir_analyzer, :modules) diff --git a/test/support/exercise_test_case.ex b/test/support/exercise_test_case.ex index f25e383b..92dac74c 100644 --- a/test/support/exercise_test_case.ex +++ b/test/support/exercise_test_case.ex @@ -12,11 +12,15 @@ defmodule ElixirAnalyzer.ExerciseTestCase do use ExUnit.CaseTemplate @dialyzer no_match: {:assert_comments, 3} + @exercise_config Application.compile_env(:elixir_analyzer, :exercise_config) + @concept_exercice_path "elixir/exercises/concept" + @meta_config ".meta/config.json" using opts do quote do @exercise_test_module unquote(opts)[:exercise_test_module] @unsorted_comments unquote(opts)[:unsorted_comments] + @exemplar_code ElixirAnalyzer.ExerciseTestCase.find_exemplar_code(@exercise_test_module) require ElixirAnalyzer.ExerciseTestCase import ElixirAnalyzer.ExerciseTestCase alias ElixirAnalyzer.Constants @@ -94,7 +98,7 @@ defmodule ElixirAnalyzer.ExerciseTestCase do analysis_module: "" } - result = @exercise_test_module.analyze(empty_submission, unquote(code)) + result = @exercise_test_module.analyze(empty_submission, unquote(code), @exemplar_code) comments = result.comments @@ -151,4 +155,24 @@ defmodule ElixirAnalyzer.ExerciseTestCase do def assert_comments(_, _, _) do :noop end + + # Return the exemplar AST for concept exercises, or nil for pracices exercises and other tests + def find_exemplar_code(test_module) do + with {slug, _test_module} <- + Enum.find(@exercise_config, &match?({_, %{analyzer_module: ^test_module}}, &1)), + {:ok, config_file} <- + Path.join([@concept_exercice_path, slug, @meta_config]) |> File.read() do + get_exemplar_ast!(config_file, slug) + else + _ -> nil + end + end + + defp get_exemplar_ast!(config_file_path, slug) do + %{"files" => %{"exemplar" => [path]}} = Jason.decode!(config_file_path) + + Path.join([@concept_exercice_path, slug, path]) + |> File.read!() + |> Code.string_to_quoted!() + end end diff --git a/test_data/lasagna/failing_solution/.formatter.exs b/test_data/lasagna/failing_solution/.formatter.exs new file mode 100644 index 00000000..d2cda26e --- /dev/null +++ b/test_data/lasagna/failing_solution/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/test_data/lasagna/failing_solution/.gitignore b/test_data/lasagna/failing_solution/.gitignore new file mode 100644 index 00000000..682b719e --- /dev/null +++ b/test_data/lasagna/failing_solution/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +basics-*.tar + diff --git a/test_data/lasagna/failing_solution/.meta/config.json b/test_data/lasagna/failing_solution/.meta/config.json new file mode 100644 index 00000000..0f09981f --- /dev/null +++ b/test_data/lasagna/failing_solution/.meta/config.json @@ -0,0 +1,24 @@ +{ + "blurb": "Learn about the basics of Elixir by following a lasagna recipe.", + "authors": [ + "neenjaw" + ], + "contributors": [ + "angelikatyborska" + ], + "files": { + "solution": [ + "lib/lasagna.ex" + ], + "test": [ + "test/lasagna_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "forked_from": [ + "csharp/lucians-luscious-lasagna" + ], + "language_versions": ">=1.10" +} diff --git a/test_data/lasagna/failing_solution/.meta/exemplar.ex b/test_data/lasagna/failing_solution/.meta/exemplar.ex new file mode 100644 index 00000000..10d7bf38 --- /dev/null +++ b/test_data/lasagna/failing_solution/.meta/exemplar.ex @@ -0,0 +1,21 @@ +defmodule Lasagna do + def expected_minutes_in_oven() do + 40 + end + + def remaining_minutes_in_oven(actual_minutes_in_oven) do + expected_minutes_in_oven() - actual_minutes_in_oven + end + + def preparation_time_in_minutes(number_of_layers) do + number_of_layers * 2 + end + + def total_time_in_minutes(number_of_layers, actual_minutes_in_oven) do + preparation_time_in_minutes(number_of_layers) + actual_minutes_in_oven + end + + def alarm() do + "Ding!" + end +end diff --git a/test_data/lasagna/failing_solution/lib/lasagna.ex b/test_data/lasagna/failing_solution/lib/lasagna.ex new file mode 100644 index 00000000..4361b85f --- /dev/null +++ b/test_data/lasagna/failing_solution/lib/lasagna.ex @@ -0,0 +1,11 @@ +defmodule Lasagna do + # TODO: define the 'expected_minutes_in_oven/0' function + + # TODO: define the 'remaining_minutes_in_oven/1' function + + # TODO: define the 'preparation_time_in_minutes/1' function + + # TODO: define the 'total_time_in_minutes/2' function + + # TODO: define the 'alarm/0' function +end diff --git a/test_data/lasagna/failing_solution/mix.exs b/test_data/lasagna/failing_solution/mix.exs new file mode 100644 index 00000000..09705834 --- /dev/null +++ b/test_data/lasagna/failing_solution/mix.exs @@ -0,0 +1,28 @@ +defmodule Lasagna.MixProject do + use Mix.Project + + def project do + [ + app: :lasagna, + version: "0.1.0", + # elixir: "~> 1.10", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/test_data/lasagna/failing_solution/test/lasagna_test.exs b/test_data/lasagna/failing_solution/test/lasagna_test.exs new file mode 100644 index 00000000..f562bcfb --- /dev/null +++ b/test_data/lasagna/failing_solution/test/lasagna_test.exs @@ -0,0 +1,39 @@ +defmodule LasagnaTest do + use ExUnit.Case + doctest Lasagna + + @tag task_id: 1 + test "expected minutes in oven" do + assert Lasagna.expected_minutes_in_oven() === 40 + end + + @tag task_id: 2 + test "remaining minutes in oven" do + assert Lasagna.remaining_minutes_in_oven(25) === 15 + end + + @tag task_id: 3 + test "preparation time in minutes for one layer" do + assert Lasagna.preparation_time_in_minutes(1) === 2 + end + + @tag task_id: 3 + test "preparation time in minutes for multiple layers" do + assert Lasagna.preparation_time_in_minutes(4) === 8 + end + + @tag task_id: 4 + test "total time in minutes for one layer" do + assert Lasagna.total_time_in_minutes(1, 30) === 32 + end + + @tag task_id: 4 + test "total time in minutes for multiple layers" do + assert Lasagna.total_time_in_minutes(4, 8) === 16 + end + + @tag task_id: 5 + test "notification message" do + assert Lasagna.alarm() === "Ding!" + end +end diff --git a/test_data/lasagna/failing_solution/test/test_helper.exs b/test_data/lasagna/failing_solution/test/test_helper.exs new file mode 100644 index 00000000..e8677a34 --- /dev/null +++ b/test_data/lasagna/failing_solution/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true, seed: 0) diff --git a/test_data/lasagna/missing_exemplar/.formatter.exs b/test_data/lasagna/missing_exemplar/.formatter.exs new file mode 100644 index 00000000..d2cda26e --- /dev/null +++ b/test_data/lasagna/missing_exemplar/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/test_data/lasagna/missing_exemplar/.gitignore b/test_data/lasagna/missing_exemplar/.gitignore new file mode 100644 index 00000000..682b719e --- /dev/null +++ b/test_data/lasagna/missing_exemplar/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +basics-*.tar + diff --git a/test_data/lasagna/missing_exemplar/.meta/config.json b/test_data/lasagna/missing_exemplar/.meta/config.json new file mode 100644 index 00000000..0f09981f --- /dev/null +++ b/test_data/lasagna/missing_exemplar/.meta/config.json @@ -0,0 +1,24 @@ +{ + "blurb": "Learn about the basics of Elixir by following a lasagna recipe.", + "authors": [ + "neenjaw" + ], + "contributors": [ + "angelikatyborska" + ], + "files": { + "solution": [ + "lib/lasagna.ex" + ], + "test": [ + "test/lasagna_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "forked_from": [ + "csharp/lucians-luscious-lasagna" + ], + "language_versions": ">=1.10" +} diff --git a/test_data/lasagna/missing_exemplar/lib/lasagna.ex b/test_data/lasagna/missing_exemplar/lib/lasagna.ex new file mode 100644 index 00000000..10d7bf38 --- /dev/null +++ b/test_data/lasagna/missing_exemplar/lib/lasagna.ex @@ -0,0 +1,21 @@ +defmodule Lasagna do + def expected_minutes_in_oven() do + 40 + end + + def remaining_minutes_in_oven(actual_minutes_in_oven) do + expected_minutes_in_oven() - actual_minutes_in_oven + end + + def preparation_time_in_minutes(number_of_layers) do + number_of_layers * 2 + end + + def total_time_in_minutes(number_of_layers, actual_minutes_in_oven) do + preparation_time_in_minutes(number_of_layers) + actual_minutes_in_oven + end + + def alarm() do + "Ding!" + end +end diff --git a/test_data/lasagna/missing_exemplar/mix.exs b/test_data/lasagna/missing_exemplar/mix.exs new file mode 100644 index 00000000..09705834 --- /dev/null +++ b/test_data/lasagna/missing_exemplar/mix.exs @@ -0,0 +1,28 @@ +defmodule Lasagna.MixProject do + use Mix.Project + + def project do + [ + app: :lasagna, + version: "0.1.0", + # elixir: "~> 1.10", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/test_data/lasagna/missing_exemplar/test/lasagna_test.exs b/test_data/lasagna/missing_exemplar/test/lasagna_test.exs new file mode 100644 index 00000000..f562bcfb --- /dev/null +++ b/test_data/lasagna/missing_exemplar/test/lasagna_test.exs @@ -0,0 +1,39 @@ +defmodule LasagnaTest do + use ExUnit.Case + doctest Lasagna + + @tag task_id: 1 + test "expected minutes in oven" do + assert Lasagna.expected_minutes_in_oven() === 40 + end + + @tag task_id: 2 + test "remaining minutes in oven" do + assert Lasagna.remaining_minutes_in_oven(25) === 15 + end + + @tag task_id: 3 + test "preparation time in minutes for one layer" do + assert Lasagna.preparation_time_in_minutes(1) === 2 + end + + @tag task_id: 3 + test "preparation time in minutes for multiple layers" do + assert Lasagna.preparation_time_in_minutes(4) === 8 + end + + @tag task_id: 4 + test "total time in minutes for one layer" do + assert Lasagna.total_time_in_minutes(1, 30) === 32 + end + + @tag task_id: 4 + test "total time in minutes for multiple layers" do + assert Lasagna.total_time_in_minutes(4, 8) === 16 + end + + @tag task_id: 5 + test "notification message" do + assert Lasagna.alarm() === "Ding!" + end +end diff --git a/test_data/lasagna/missing_exemplar/test/test_helper.exs b/test_data/lasagna/missing_exemplar/test/test_helper.exs new file mode 100644 index 00000000..e8677a34 --- /dev/null +++ b/test_data/lasagna/missing_exemplar/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true, seed: 0) diff --git a/test_data/lasagna/perfect_solution/.formatter.exs b/test_data/lasagna/perfect_solution/.formatter.exs new file mode 100644 index 00000000..d2cda26e --- /dev/null +++ b/test_data/lasagna/perfect_solution/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/test_data/lasagna/perfect_solution/.gitignore b/test_data/lasagna/perfect_solution/.gitignore new file mode 100644 index 00000000..682b719e --- /dev/null +++ b/test_data/lasagna/perfect_solution/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +basics-*.tar + diff --git a/test_data/lasagna/perfect_solution/.meta/config.json b/test_data/lasagna/perfect_solution/.meta/config.json new file mode 100644 index 00000000..0f09981f --- /dev/null +++ b/test_data/lasagna/perfect_solution/.meta/config.json @@ -0,0 +1,24 @@ +{ + "blurb": "Learn about the basics of Elixir by following a lasagna recipe.", + "authors": [ + "neenjaw" + ], + "contributors": [ + "angelikatyborska" + ], + "files": { + "solution": [ + "lib/lasagna.ex" + ], + "test": [ + "test/lasagna_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "forked_from": [ + "csharp/lucians-luscious-lasagna" + ], + "language_versions": ">=1.10" +} diff --git a/test_data/lasagna/perfect_solution/.meta/exemplar.ex b/test_data/lasagna/perfect_solution/.meta/exemplar.ex new file mode 100644 index 00000000..10d7bf38 --- /dev/null +++ b/test_data/lasagna/perfect_solution/.meta/exemplar.ex @@ -0,0 +1,21 @@ +defmodule Lasagna do + def expected_minutes_in_oven() do + 40 + end + + def remaining_minutes_in_oven(actual_minutes_in_oven) do + expected_minutes_in_oven() - actual_minutes_in_oven + end + + def preparation_time_in_minutes(number_of_layers) do + number_of_layers * 2 + end + + def total_time_in_minutes(number_of_layers, actual_minutes_in_oven) do + preparation_time_in_minutes(number_of_layers) + actual_minutes_in_oven + end + + def alarm() do + "Ding!" + end +end diff --git a/test_data/lasagna/perfect_solution/lib/lasagna.ex b/test_data/lasagna/perfect_solution/lib/lasagna.ex new file mode 100644 index 00000000..10d7bf38 --- /dev/null +++ b/test_data/lasagna/perfect_solution/lib/lasagna.ex @@ -0,0 +1,21 @@ +defmodule Lasagna do + def expected_minutes_in_oven() do + 40 + end + + def remaining_minutes_in_oven(actual_minutes_in_oven) do + expected_minutes_in_oven() - actual_minutes_in_oven + end + + def preparation_time_in_minutes(number_of_layers) do + number_of_layers * 2 + end + + def total_time_in_minutes(number_of_layers, actual_minutes_in_oven) do + preparation_time_in_minutes(number_of_layers) + actual_minutes_in_oven + end + + def alarm() do + "Ding!" + end +end diff --git a/test_data/lasagna/perfect_solution/mix.exs b/test_data/lasagna/perfect_solution/mix.exs new file mode 100644 index 00000000..09705834 --- /dev/null +++ b/test_data/lasagna/perfect_solution/mix.exs @@ -0,0 +1,28 @@ +defmodule Lasagna.MixProject do + use Mix.Project + + def project do + [ + app: :lasagna, + version: "0.1.0", + # elixir: "~> 1.10", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/test_data/lasagna/perfect_solution/test/lasagna_test.exs b/test_data/lasagna/perfect_solution/test/lasagna_test.exs new file mode 100644 index 00000000..f562bcfb --- /dev/null +++ b/test_data/lasagna/perfect_solution/test/lasagna_test.exs @@ -0,0 +1,39 @@ +defmodule LasagnaTest do + use ExUnit.Case + doctest Lasagna + + @tag task_id: 1 + test "expected minutes in oven" do + assert Lasagna.expected_minutes_in_oven() === 40 + end + + @tag task_id: 2 + test "remaining minutes in oven" do + assert Lasagna.remaining_minutes_in_oven(25) === 15 + end + + @tag task_id: 3 + test "preparation time in minutes for one layer" do + assert Lasagna.preparation_time_in_minutes(1) === 2 + end + + @tag task_id: 3 + test "preparation time in minutes for multiple layers" do + assert Lasagna.preparation_time_in_minutes(4) === 8 + end + + @tag task_id: 4 + test "total time in minutes for one layer" do + assert Lasagna.total_time_in_minutes(1, 30) === 32 + end + + @tag task_id: 4 + test "total time in minutes for multiple layers" do + assert Lasagna.total_time_in_minutes(4, 8) === 16 + end + + @tag task_id: 5 + test "notification message" do + assert Lasagna.alarm() === "Ding!" + end +end diff --git a/test_data/lasagna/perfect_solution/test/test_helper.exs b/test_data/lasagna/perfect_solution/test/test_helper.exs new file mode 100644 index 00000000..e8677a34 --- /dev/null +++ b/test_data/lasagna/perfect_solution/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true, seed: 0) diff --git a/test_data/lasagna/wrong_exemplar/.formatter.exs b/test_data/lasagna/wrong_exemplar/.formatter.exs new file mode 100644 index 00000000..d2cda26e --- /dev/null +++ b/test_data/lasagna/wrong_exemplar/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/test_data/lasagna/wrong_exemplar/.gitignore b/test_data/lasagna/wrong_exemplar/.gitignore new file mode 100644 index 00000000..682b719e --- /dev/null +++ b/test_data/lasagna/wrong_exemplar/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +basics-*.tar + diff --git a/test_data/lasagna/wrong_exemplar/.meta/config.json b/test_data/lasagna/wrong_exemplar/.meta/config.json new file mode 100644 index 00000000..0f09981f --- /dev/null +++ b/test_data/lasagna/wrong_exemplar/.meta/config.json @@ -0,0 +1,24 @@ +{ + "blurb": "Learn about the basics of Elixir by following a lasagna recipe.", + "authors": [ + "neenjaw" + ], + "contributors": [ + "angelikatyborska" + ], + "files": { + "solution": [ + "lib/lasagna.ex" + ], + "test": [ + "test/lasagna_test.exs" + ], + "exemplar": [ + ".meta/exemplar.ex" + ] + }, + "forked_from": [ + "csharp/lucians-luscious-lasagna" + ], + "language_versions": ">=1.10" +} diff --git a/test_data/lasagna/wrong_exemplar/.meta/exemplar.ex b/test_data/lasagna/wrong_exemplar/.meta/exemplar.ex new file mode 100644 index 00000000..8aac7980 --- /dev/null +++ b/test_data/lasagna/wrong_exemplar/.meta/exemplar.ex @@ -0,0 +1,3 @@ +defmodule Lasagna do + + def expected_minutes_in_oven() do diff --git a/test_data/lasagna/wrong_exemplar/lib/lasagna.ex b/test_data/lasagna/wrong_exemplar/lib/lasagna.ex new file mode 100644 index 00000000..10d7bf38 --- /dev/null +++ b/test_data/lasagna/wrong_exemplar/lib/lasagna.ex @@ -0,0 +1,21 @@ +defmodule Lasagna do + def expected_minutes_in_oven() do + 40 + end + + def remaining_minutes_in_oven(actual_minutes_in_oven) do + expected_minutes_in_oven() - actual_minutes_in_oven + end + + def preparation_time_in_minutes(number_of_layers) do + number_of_layers * 2 + end + + def total_time_in_minutes(number_of_layers, actual_minutes_in_oven) do + preparation_time_in_minutes(number_of_layers) + actual_minutes_in_oven + end + + def alarm() do + "Ding!" + end +end diff --git a/test_data/lasagna/wrong_exemplar/mix.exs b/test_data/lasagna/wrong_exemplar/mix.exs new file mode 100644 index 00000000..09705834 --- /dev/null +++ b/test_data/lasagna/wrong_exemplar/mix.exs @@ -0,0 +1,28 @@ +defmodule Lasagna.MixProject do + use Mix.Project + + def project do + [ + app: :lasagna, + version: "0.1.0", + # elixir: "~> 1.10", + start_permanent: Mix.env() == :prod, + deps: deps() + ] + end + + # Run "mix help compile.app" to learn about applications. + def application do + [ + extra_applications: [:logger] + ] + end + + # Run "mix help deps" to learn about dependencies. + defp deps do + [ + # {:dep_from_hexpm, "~> 0.3.0"}, + # {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"} + ] + end +end diff --git a/test_data/lasagna/wrong_exemplar/test/lasagna_test.exs b/test_data/lasagna/wrong_exemplar/test/lasagna_test.exs new file mode 100644 index 00000000..f562bcfb --- /dev/null +++ b/test_data/lasagna/wrong_exemplar/test/lasagna_test.exs @@ -0,0 +1,39 @@ +defmodule LasagnaTest do + use ExUnit.Case + doctest Lasagna + + @tag task_id: 1 + test "expected minutes in oven" do + assert Lasagna.expected_minutes_in_oven() === 40 + end + + @tag task_id: 2 + test "remaining minutes in oven" do + assert Lasagna.remaining_minutes_in_oven(25) === 15 + end + + @tag task_id: 3 + test "preparation time in minutes for one layer" do + assert Lasagna.preparation_time_in_minutes(1) === 2 + end + + @tag task_id: 3 + test "preparation time in minutes for multiple layers" do + assert Lasagna.preparation_time_in_minutes(4) === 8 + end + + @tag task_id: 4 + test "total time in minutes for one layer" do + assert Lasagna.total_time_in_minutes(1, 30) === 32 + end + + @tag task_id: 4 + test "total time in minutes for multiple layers" do + assert Lasagna.total_time_in_minutes(4, 8) === 16 + end + + @tag task_id: 5 + test "notification message" do + assert Lasagna.alarm() === "Ding!" + end +end diff --git a/test_data/lasagna/wrong_exemplar/test/test_helper.exs b/test_data/lasagna/wrong_exemplar/test/test_helper.exs new file mode 100644 index 00000000..e8677a34 --- /dev/null +++ b/test_data/lasagna/wrong_exemplar/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true, seed: 0)