diff --git a/lib/cadet/assessments/library.ex b/lib/cadet/assessments/library.ex index e81e42028..00a7f3d2d 100644 --- a/lib/cadet/assessments/library.ex +++ b/lib/cadet/assessments/library.ex @@ -9,12 +9,14 @@ defmodule Cadet.Assessments.Library do @primary_key false embedded_schema do field(:chapter, :integer, default: 1) + field(:variant, :string, default: nil) + field(:exec_time_ms, :integer, default: 1000) field(:globals, :map, default: %{}) embeds_one(:external, ExternalLibrary, on_replace: :update) end @required_fields ~w(chapter)a - @optional_fields ~w(globals)a + @optional_fields ~w(globals variant exec_time_ms)a @required_embeds ~w(external)a def changeset(library, params \\ %{}) do @@ -24,6 +26,8 @@ defmodule Cadet.Assessments.Library do |> put_default_external() |> validate_required(@required_fields ++ @required_embeds) |> validate_globals() + |> validate_chapter() + |> validate_chapter_variant() end defp validate_globals(changeset) do @@ -40,6 +44,45 @@ defmodule Cadet.Assessments.Library do end end + defp validate_chapter(changeset) do + case changeset |> fetch_change(:chapter) do + {:ok, c} when c in 1..4 -> changeset + :error -> changeset + _ -> add_error(changeset, :chapter, "invalid chapter") + end + end + + @valid_chapter_variants [ + {1, "wasm"}, + {1, "lazy"}, + {2, "lazy"}, + {3, "concurrent"}, + {3, "non-det"}, + {4, "gpu"} + ] + + defp validate_chapter_variant(changeset) do + chapter = changeset |> fetch_field(:chapter) + variant = changeset |> fetch_field(:variant) + + case {chapter, variant} do + # no changes + {{:data, _}, {:data, _}} -> + changeset + + # default variant + {{_, _c}, {_, v}} when is_nil(v) or v == "default" -> + changeset + + {{_, chapter}, {_, variant}} -> + if {chapter, variant} in @valid_chapter_variants do + changeset + else + add_error(changeset, :variant, "invalid variant for given chapter") + end + end + end + def put_default_external(changeset) do external = get_change(changeset, :external) diff --git a/lib/cadet/jobs/xml_parser.ex b/lib/cadet/jobs/xml_parser.ex index 1b67fa56b..003091b84 100644 --- a/lib/cadet/jobs/xml_parser.ex +++ b/lib/cadet/jobs/xml_parser.ex @@ -308,7 +308,9 @@ defmodule Cadet.Updater.XMLParser do library_entity |> xpath( ~x"."e, - chapter: ~x"./@interpreter"i + chapter: ~x"./@interpreter"i, + exec_time_ms: ~x"./@exectime"oi, + variant: ~x"./@variant"os ) |> Map.put(:globals, globals) |> Map.put(:external, external) diff --git a/lib/cadet_web/views/assessments_helpers.ex b/lib/cadet_web/views/assessments_helpers.ex index bbe4c0cee..77dfc44c8 100644 --- a/lib/cadet_web/views/assessments_helpers.ex +++ b/lib/cadet_web/views/assessments_helpers.ex @@ -7,6 +7,8 @@ defmodule CadetWeb.AssessmentsHelpers do defp build_library(%{library: library}) do transform_map_for_view(library, %{ chapter: :chapter, + variant: :variant, + execTimeMs: :exec_time_ms, globals: :globals, external: &build_external_library(%{external_library: &1.external}) }) diff --git a/test/cadet/assessments/library_test.exs b/test/cadet/assessments/library_test.exs index 94c4ecf40..4608eafdd 100644 --- a/test/cadet/assessments/library_test.exs +++ b/test/cadet/assessments/library_test.exs @@ -25,6 +25,65 @@ defmodule Cadet.Assessments.LibraryTest do end end + test "invalid changeset invalid chapter", %{valid_params: params} do + params + |> Map.put(:chapter, 100) + |> assert_changeset(:invalid) + + params + |> Map.put(:chapter, 0) + |> assert_changeset(:invalid) + end + + test "valid changeset valid chapter-variant", %{valid_params: params} do + variants = [ + {1, "default"}, + {1, "wasm"}, + {1, "lazy"}, + {2, "default"}, + {2, "lazy"}, + {3, "default"}, + {3, "concurrent"}, + {3, "non-det"}, + {4, "default"}, + {4, "gpu"} + ] + + for {c, v} <- variants do + params + |> Map.merge(%{chapter: c, variant: v}) + |> assert_changeset(:valid) + end + + # no variant + params + |> Map.merge(%{chapter: 1}) + |> assert_changeset(:valid) + end + + test "valid changeset invalid chapter-variant", %{valid_params: params} do + variants = [ + {1, "hello"}, + {1, "concurrent"}, + {1, "non-det"}, + {2, "undefault"}, + {2, "eager"}, + {3, "concurren"}, + {4, "geforce rtx 3080"} + ] + + for {c, v} <- variants do + params + |> Map.merge(%{chapter: c, variant: v}) + |> assert_changeset(:invalid) + end + end + + test "invalid changeset empty" do + refute (%Library{chapter: nil} + |> Library.changeset(%{})).valid? + end + test "valid changeset without globals", %{valid_params: params} do params |> Map.delete(:globals) diff --git a/test/cadet_web/admin_controllers/admin_grading_controller_test.exs b/test/cadet_web/admin_controllers/admin_grading_controller_test.exs index ca1b87995..dfe691e7b 100644 --- a/test/cadet_web/admin_controllers/admin_grading_controller_test.exs +++ b/test/cadet_web/admin_controllers/admin_grading_controller_test.exs @@ -268,7 +268,9 @@ defmodule CadetWeb.AdminGradingControllerTest do "external" => %{ "name" => "#{&1.question.library.external.name}", "symbols" => &1.question.library.external.symbols - } + }, + "execTimeMs" => &1.question.library.exec_time_ms, + "variant" => &1.question.library.variant }, "maxXp" => &1.question.max_xp, "content" => &1.question.question.content, @@ -305,7 +307,9 @@ defmodule CadetWeb.AdminGradingControllerTest do "external" => %{ "name" => "#{&1.question.library.external.name}", "symbols" => &1.question.library.external.symbols - } + }, + "execTimeMs" => &1.question.library.exec_time_ms, + "variant" => &1.question.library.variant }, "maxXp" => &1.question.max_xp, "content" => &1.question.question.content, @@ -352,7 +356,9 @@ defmodule CadetWeb.AdminGradingControllerTest do "external" => %{ "name" => "#{&1.question.library.external.name}", "symbols" => &1.question.library.external.symbols - } + }, + "execTimeMs" => &1.question.library.exec_time_ms, + "variant" => &1.question.library.variant }, "maxXp" => &1.question.max_xp, "content" => &1.question.question.content, @@ -902,7 +908,9 @@ defmodule CadetWeb.AdminGradingControllerTest do "external" => %{ "name" => "#{&1.question.library.external.name}", "symbols" => &1.question.library.external.symbols - } + }, + "execTimeMs" => &1.question.library.exec_time_ms, + "variant" => &1.question.library.variant }, "maxXp" => &1.question.max_xp, "content" => &1.question.question.content, @@ -939,7 +947,9 @@ defmodule CadetWeb.AdminGradingControllerTest do "external" => %{ "name" => "#{&1.question.library.external.name}", "symbols" => &1.question.library.external.symbols - } + }, + "execTimeMs" => &1.question.library.exec_time_ms, + "variant" => &1.question.library.variant }, "content" => &1.question.question.content, "answer" => &1.answer.choice_id, @@ -986,7 +996,9 @@ defmodule CadetWeb.AdminGradingControllerTest do "external" => %{ "name" => "#{&1.question.library.external.name}", "symbols" => &1.question.library.external.symbols - } + }, + "execTimeMs" => &1.question.library.exec_time_ms, + "variant" => &1.question.library.variant }, "maxXp" => &1.question.max_xp, "content" => &1.question.question.content, diff --git a/test/factories/assessments/library_factory.ex b/test/factories/assessments/library_factory.ex index 29d73db3f..e0eab6a9e 100644 --- a/test/factories/assessments/library_factory.ex +++ b/test/factories/assessments/library_factory.ex @@ -9,7 +9,7 @@ defmodule Cadet.Assessments.LibraryFactory do def library_factory do %{ - chapter: Enum.random(1..20), + chapter: Enum.random(1..4), globals: Enum.reduce( 0..5,