diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1425b8c2ac..80aa70cd39 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,8 +17,8 @@ jobs: runs-on: ubuntu-24.04 strategy: matrix: - elixir: [1.19.2] - otp: [28.1] + elixir: [1.20.1] + otp: [29.0] steps: - name: Checkout code @@ -76,8 +76,6 @@ jobs: strategy: matrix: include: - - elixir: '1.15.0' - otp: '26.0' - elixir: '1.16.0' otp: '26.2' - elixir: '1.17.0' @@ -86,6 +84,8 @@ jobs: otp: '27.2' - elixir: '1.19.2' otp: '28.1' + - elixir: '1.20.1' + otp: '29.0' steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd @@ -121,4 +121,3 @@ jobs: - name: Run Checks run: bin/ci.sh - diff --git a/README.md b/README.md index 57ae0c072c..7c933612b8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ## Setup -The exercises currently target Elixir versions from 1.15 to 1.19 and Erlang/OTP versions from 25 to 27. Detailed installation instructions can be found at +The exercises currently target Elixir versions from 1.16 to 1.20 and Erlang/OTP versions from 26 to 29. Detailed installation instructions can be found at [https://elixir-lang.org/install.html](https://elixir-lang.org/install.html). We recommend using the [asdf version manager](https://github.com/asdf-vm/asdf) to manage multiple Elixir versions. ## Testing diff --git a/exercises/concept/city-office/test/form_test.exs b/exercises/concept/city-office/test/form_test.exs index 0f9623abe4..ce9f809f08 100644 --- a/exercises/concept/city-office/test/form_test.exs +++ b/exercises/concept/city-office/test/form_test.exs @@ -76,7 +76,7 @@ defmodule FormTest do end end - defmacrop assert_type({module_name, type_name}, expected_type_definition) do + defmacrop assert_type({module_name, type_name}, expected_type_definitions) do quote do {:ok, types} = Code.Typespec.fetch_types(unquote(module_name)) @@ -94,15 +94,11 @@ defmodule FormTest do actual_type_definition = Macro.to_string(type_definition) - if is_list(unquote(expected_type_definition)) do - if actual_type_definition in unquote(expected_type_definition) do - assert true - else - # we know this will fail at this point, but we're using it to provide a nice failure message - assert actual_type_definition == hd(unquote(expected_type_definition)) - end + if actual_type_definition in unquote(expected_type_definitions) do + assert true else - assert actual_type_definition == unquote(expected_type_definition) + # we know this will fail at this point, but we're using it to provide a nice failure message + assert actual_type_definition == hd(unquote(expected_type_definitions)) end end end @@ -231,10 +227,10 @@ defmodule FormTest do @tag task_id: 5 test "has a custom 'address_tuple' type with named arguments" do - expected_type_definition = - "{street :: String.t(), postal_code :: String.t(), city :: String.t()}" + expected_type_definitions = + ["{street :: String.t(), postal_code :: String.t(), city :: String.t()}"] - assert_type({Form, :address_tuple}, expected_type_definition) + assert_type({Form, :address_tuple}, expected_type_definitions) end @tag task_id: 5 diff --git a/exercises/concept/kitchen-calculator/test/kitchen_calculator_test.exs b/exercises/concept/kitchen-calculator/test/kitchen_calculator_test.exs index 331cbb4e5e..7363b088a8 100644 --- a/exercises/concept/kitchen-calculator/test/kitchen_calculator_test.exs +++ b/exercises/concept/kitchen-calculator/test/kitchen_calculator_test.exs @@ -88,22 +88,22 @@ defmodule KitchenCalculatorTest do describe "convert from x to y:" do @tag task_id: 4 test "teaspoon to tablespoon" do - assert KitchenCalculator.convert({:teaspoon, 15}, :tablespoon) == {:tablespoon, 5} + assert KitchenCalculator.convert({:teaspoon, 15}, :tablespoon) == {:tablespoon, 5.0} end @tag task_id: 4 test "cups to fluid ounces" do - assert KitchenCalculator.convert({:cup, 4}, :fluid_ounce) == {:fluid_ounce, 32} + assert KitchenCalculator.convert({:cup, 4}, :fluid_ounce) == {:fluid_ounce, 32.0} end @tag task_id: 4 test "fluid ounces to teaspoons" do - assert KitchenCalculator.convert({:fluid_ounce, 4}, :teaspoon) == {:teaspoon, 24} + assert KitchenCalculator.convert({:fluid_ounce, 4}, :teaspoon) == {:teaspoon, 24.0} end @tag task_id: 4 test "tablespoons to cups" do - assert KitchenCalculator.convert({:tablespoon, 320}, :cup) == {:cup, 20} + assert KitchenCalculator.convert({:tablespoon, 320}, :cup) == {:cup, 20.0} end end end diff --git a/exercises/concept/paint-by-number/.meta/exemplar.ex b/exercises/concept/paint-by-number/.meta/exemplar.ex index 145139a7d2..875bd8a4d8 100644 --- a/exercises/concept/paint-by-number/.meta/exemplar.ex +++ b/exercises/concept/paint-by-number/.meta/exemplar.ex @@ -30,7 +30,7 @@ defmodule PaintByNumber do case picture do <<>> -> nil - <> -> pixel_color_index + <> -> pixel_color_index end end @@ -39,7 +39,7 @@ defmodule PaintByNumber do case picture do <<>> -> <<>> - <<_::size(palette_bit_size), rest::bitstring>> -> rest + <<_::size(^palette_bit_size), rest::bitstring>> -> rest end end diff --git a/exercises/concept/top-secret/test/top_secret_test.exs b/exercises/concept/top-secret/test/top_secret_test.exs index 82c9687a07..d3cdd64ebb 100644 --- a/exercises/concept/top-secret/test/top_secret_test.exs +++ b/exercises/concept/top-secret/test/top_secret_test.exs @@ -1,13 +1,16 @@ defmodule TopSecretTest do use ExUnit.Case + # Maintainer notes: + # - in Elixir 1.17, `:...` got [] as an argument list, before that it was nil + # - in Elixir 1.20, `:__block__` got the line number in the metadata, before that it was empty + describe "to_ast/1" do @tag task_id: 1 test "handles an empty string" do string = "" - ast = {:__block__, [], []} - assert TopSecret.to_ast(string) == ast + assert {:__block__, _line_1_or_empty_list, []} = TopSecret.to_ast(string) end @tag task_id: 1 @@ -17,14 +20,12 @@ defmodule TopSecretTest do y = x - 2 """ - ast = - {:__block__, [], - [ - {:=, [line: 1], [{:x, [line: 1], nil}, 7]}, - {:=, [line: 2], [{:y, [line: 2], nil}, {:-, [line: 2], [{:x, [line: 2], nil}, 2]}]} - ]} - - assert TopSecret.to_ast(string) == ast + assert {:__block__, _line_1_or_empty_list, + [ + {:=, [line: 1], [{:x, [line: 1], nil}, 7]}, + {:=, [line: 2], + [{:y, [line: 2], nil}, {:-, [line: 2], [{:x, [line: 2], nil}, 2]}]} + ]} = TopSecret.to_ast(string) end @tag task_id: 1 @@ -45,7 +46,7 @@ defmodule TopSecretTest do [ do: { :__block__, - [], + _line_1_or_empty_list, [ {:@, [line: 2], [ diff --git a/exercises/practice/beer-song/test/beer_song_test.exs b/exercises/practice/beer-song/test/beer_song_test.exs index 2c6d0f4f3e..0ce5ce2fc9 100644 --- a/exercises/practice/beer-song/test/beer_song_test.exs +++ b/exercises/practice/beer-song/test/beer_song_test.exs @@ -46,7 +46,7 @@ defmodule BeerSongTest do describe "lyrics" do @tag :pending test "first two verses" do - assert BeerSong.lyrics(99..98) == """ + assert BeerSong.lyrics(99..98//-1) == """ 99 bottles of beer on the wall, 99 bottles of beer. Take one down and pass it around, 98 bottles of beer on the wall. @@ -57,7 +57,7 @@ defmodule BeerSongTest do @tag :pending test "last three verses" do - assert BeerSong.lyrics(2..0) == """ + assert BeerSong.lyrics(2..0//-1) == """ 2 bottles of beer on the wall, 2 bottles of beer. Take one down and pass it around, 1 bottle of beer on the wall. diff --git a/exercises/practice/collatz-conjecture/test/collatz_conjecture_test.exs b/exercises/practice/collatz-conjecture/test/collatz_conjecture_test.exs index 5ae629868e..22e83d52b2 100644 --- a/exercises/practice/collatz-conjecture/test/collatz_conjecture_test.exs +++ b/exercises/practice/collatz-conjecture/test/collatz_conjecture_test.exs @@ -39,9 +39,4 @@ defmodule CollatzConjectureTest do test "negative value is an error " do assert_raise FunctionClauseError, fn -> CollatzConjecture.calc(-15) end end - - @tag :pending - test "string as input value is an error " do - assert_raise FunctionClauseError, fn -> CollatzConjecture.calc("fubar") end - end end diff --git a/exercises/practice/intergalactic-transmission/.meta/example.ex b/exercises/practice/intergalactic-transmission/.meta/example.ex index b905f6ad1a..0a6fe05678 100644 --- a/exercises/practice/intergalactic-transmission/.meta/example.ex +++ b/exercises/practice/intergalactic-transmission/.meta/example.ex @@ -29,7 +29,7 @@ defmodule Transmission do @spec get_parity(bitstring()) :: <<_::1>> defp get_parity(data) do bit_count = bit_size(data) - <> = data + <> = data parity_int = 0..(bit_count - 1) @@ -71,7 +71,7 @@ defmodule Transmission do defp finalize(data, acc) when bit_size(data) == 7 do bits_count = 8 - rem(bit_size(acc), 8) - <> = data + <> = data <> end end diff --git a/exercises/practice/kindergarten-garden/.meta/example.ex b/exercises/practice/kindergarten-garden/.meta/example.ex index 3d4db7bd37..321994ce10 100644 --- a/exercises/practice/kindergarten-garden/.meta/example.ex +++ b/exercises/practice/kindergarten-garden/.meta/example.ex @@ -51,8 +51,8 @@ defmodule Garden do letters = "#{first_row_letters}#{second_row_letters}" map = add_to_map(map, current_name, letters) - first_row = String.slice(first_row, 2..-1//-1) - second_row = String.slice(second_row, 2..-1//-1) + first_row = String.slice(first_row, 2..-1//1) + second_row = String.slice(second_row, 2..-1//1) populate_map(first_row, second_row, map, student_names) end diff --git a/exercises/practice/list-ops/test/list_ops_test.exs b/exercises/practice/list-ops/test/list_ops_test.exs index e226fe4350..2ef7ce0502 100644 --- a/exercises/practice/list-ops/test/list_ops_test.exs +++ b/exercises/practice/list-ops/test/list_ops_test.exs @@ -42,7 +42,7 @@ defmodule ListOpsTest do @tag :pending @tag :slow test "huge list" do - assert L.reverse(Enum.to_list(1..1_000_000)) == Enum.to_list(1_000_000..1) + assert L.reverse(Enum.to_list(1..1_000_000)) == Enum.to_list(1_000_000..1//-1) end end diff --git a/exercises/practice/pascals-triangle/.meta/example.ex b/exercises/practice/pascals-triangle/.meta/example.ex index f824b1a702..d67fcf2b01 100644 --- a/exercises/practice/pascals-triangle/.meta/example.ex +++ b/exercises/practice/pascals-triangle/.meta/example.ex @@ -24,7 +24,7 @@ defmodule PascalsTriangle do defp each_cons(list) do list |> Enum.flat_map(&[&1, &1]) - |> Enum.slice(1..-2//-1) + |> Enum.slice(1..-2//1) |> Enum.chunk_every(2) end diff --git a/exercises/practice/pig-latin/.meta/example.ex b/exercises/practice/pig-latin/.meta/example.ex index de9fd96335..ccc0d343e2 100644 --- a/exercises/practice/pig-latin/.meta/example.ex +++ b/exercises/practice/pig-latin/.meta/example.ex @@ -54,6 +54,6 @@ defmodule PigLatin do end defp move_prefix_to_the_end(string, prefix) do - String.slice(string, String.length(prefix)..-1) <> prefix + String.slice(string, String.length(prefix)..-1//1) <> prefix end end diff --git a/exercises/practice/rail-fence-cipher/.meta/example.ex b/exercises/practice/rail-fence-cipher/.meta/example.ex index 60d080ddbc..afa0d17d25 100644 --- a/exercises/practice/rail-fence-cipher/.meta/example.ex +++ b/exercises/practice/rail-fence-cipher/.meta/example.ex @@ -16,17 +16,18 @@ defmodule RailFenceCipher do Decode a given rail fence ciphertext to the corresponding plaintext """ @spec decode(String.t(), pos_integer) :: String.t() + def decode("", _), do: "" def decode(str, 1), do: str def decode(str, rails) do - 0..(String.length(str) - 1) + 0..(String.length(str) - 1)//1 |> Enum.to_list() |> fill_fence(rails) |> extract_original(str) end defp extract_original(fence, str) do - 0..(String.length(str) - 1) + 0..(String.length(str) - 1)//1 |> Enum.map(fn i -> String.at(str, Enum.find_index(fence, &(&1 == i))) end) @@ -34,7 +35,8 @@ defmodule RailFenceCipher do end defp fill_fence(list_chars, rails) do - rail_zigzag_indexes = Enum.concat(Enum.to_list(0..(rails - 1)), Enum.to_list((rails - 2)..1)) + rail_zigzag_indexes = + Enum.concat(Enum.to_list(0..(rails - 1)//1), Enum.to_list((rails - 2)..1//-1)) create_empty_fence(rails, length(list_chars)) |> set_chars_to_fence(list_chars, rail_zigzag_indexes) diff --git a/exercises/practice/two-fer/test/two_fer_test.exs b/exercises/practice/two-fer/test/two_fer_test.exs index 86380254b5..5bf38ccf8e 100644 --- a/exercises/practice/two-fer/test/two_fer_test.exs +++ b/exercises/practice/two-fer/test/two_fer_test.exs @@ -14,25 +14,4 @@ defmodule TwoFerTest do test "another name given" do assert TwoFer.two_fer("Bob") == "One for Bob, one for me." end - - @tag :pending - test "when the argument is a number" do - assert_raise FunctionClauseError, fn -> - TwoFer.two_fer(10) - end - end - - @tag :pending - test "when the argument is an atom" do - assert_raise FunctionClauseError, fn -> - TwoFer.two_fer(:bob) - end - end - - @tag :pending - test "when the argument is a charlist" do - assert_raise FunctionClauseError, fn -> - refute TwoFer.two_fer(~c"Jon Snow") - end - end end