This commit is contained in:
Ruidy 2022-03-04 10:04:23 -04:00
parent 3c882052df
commit a99aaccbc9
22 changed files with 820 additions and 0 deletions

View file

@ -0,0 +1,23 @@
{
"blurb": "Learn about errors and rescuing them by working on an experimental Reverse Polish Notation calculator.",
"icon": "instruments-of-texas",
"authors": [
"neenjaw"
],
"contributors": [
"angelikatyborska",
"cjmaxik"
],
"files": {
"solution": [
"lib/rpn_calculator.ex"
],
"test": [
"test/rpn_calculator_test.exs"
],
"exemplar": [
".meta/exemplar.ex"
]
},
"language_versions": ">=1.10"
}

View file

@ -0,0 +1 @@
{"track":"elixir","exercise":"rpn-calculator","id":"ed514077caa14d95be2a2ffb74f4d5c1","url":"https://exercism.org/tracks/elixir/exercises/rpn-calculator","handle":"rjNemo","is_requester":true,"auto_approve":false}

View file

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

24
rpn-calculator/.gitignore vendored Normal file
View file

@ -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").
errors-*.tar

75
rpn-calculator/HELP.md Normal file
View file

@ -0,0 +1,75 @@
# Help
## Running the tests
From the terminal, change to the base directory of the exercise then execute the tests with:
```bash
$ mix test
```
This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs`
Documentation:
* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html)
* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html)
## Pending tests
In test suites of practice exercises, all but the first test have been tagged to be skipped.
Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol.
For example:
```elixir
# @tag :pending
test "shouting" do
assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
end
```
If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command:
```bash
$ mix test --include pending
```
Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`.
```elixir
# ExUnit.configure(exclude: :pending, trace: true)
```
## Useful `mix test` options
* `test/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.exs` whose definition is on line `LINENUM`
* `--failed` - runs only tests that failed the last time they ran
* `--max-failures` - the suite stops evaluating tests when this number of test failures
is reached
* `--seed 0` - disables randomization so the tests in a single file will always be ran
in the same order they were defined in
## Submitting your solution
You can submit your solution using the `exercism submit lib/rpn_calculator.ex` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir)
- [Exercism's support channel on gitter](https://gitter.im/exercism/support)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found.
If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang).

23
rpn-calculator/HINTS.md Normal file
View file

@ -0,0 +1,23 @@
# Hints
## General
- Read about [errors][errors] in the Getting Started guide.
- Read about [`try`][docs-try] in the documentation.
## 1. Warn the team
- Allow the operation function to raise its error.
- To invoke a function in a variable, use the `.` operator.
## 2. Wrap the error
- Make use of try .. rescue to return the intended result.
## 3. Pass on the message
- Make use of try .. rescue to return the intended result.
- The rescue block allows you to pattern match on the error's Module name and also bind the error to a variable.
[errors]: https://elixir-lang.org/getting-started/try-catch-and-rescue.html#errors
[docs-try]: https://hexdocs.pm/elixir/Kernel.SpecialForms.html#try/1

117
rpn-calculator/README.md Normal file
View file

@ -0,0 +1,117 @@
# RPN Calculator
Welcome to RPN Calculator on Exercism's Elixir Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
## Introduction
## Errors
Errors happen. In Elixir, while people often say to "let it crash", there are times when we need to rescue the function call to a known good state to fulfill a software contract. In some languages, errors are used as a method of control flow, but in Elixir, this pattern is discouraged. We can often recognize functions that may raise an error just by their name: functions that raise errors are to have `!` at the end of their name. This is in comparison with functions that return `{:ok, value}` or `:error`. Look at these library examples:
```elixir
Map.fetch(%{a: 1}, :b)
# => :error
Map.fetch!(%{a: 1}, :b)
# => raises KeyError
```
## Try Rescue
Elixir provides a construct for rescuing from errors using `try .. rescue`
```elixir
try do #1
raise RuntimeError, "error" #2
rescue
e in RuntimeError -> :error #3
end
```
Let's examine this construct:
- **Line 1**, the block is declared with `try`.
- **Line 2**, the function call which may error is placed here, in this case we are calling `raise/2`.
- **Line 3**, in the `rescue` section, we pattern match on the _Module_ name of the error raised
- on the left side of `->`:
- `e` is matched to the error struct.
- `in` is a keyword.
- `RuntimeError` is the error that we want to rescue.
- If we wanted to rescue from all errors, we could use `_` instead of the module name or omit the `in` keyword entirely.
- on the right side:
- the instructions to be executed if the error matches.
### Error structs
Errors (sometimes also called "exceptions") that you rescue this way are structs.
Rescuing errors in Elixir is done very rarely.
Usually the rescued error is logged or sent to an external monitoring service, and then reraised.
This means we usually don't care about the internal structure of the specific error struct.
In the [Exceptions concept](/tracks/elixir/concepts/exceptions) you will learn more about error structs, including how to define your own custom error.
## Instructions
While working at _Instruments of Texas_, you are tasked to work on an experimental Reverse Polish Notation [RPN] calculator written in Elixir. Your team is having a problem with some operations raising errors and crashing the process. You have been tasked to write a function which wraps the operation function so that the errors can be handled more elegantly with idiomatic Elixir code.
## 1. Warn the team
Implement the function `calculate!/2` to call the operation function with the stack as the only argument. The operation function is defined elsewhere, but you know that it can either complete successfully or raise an error.
```elixir
stack = []
operation = fn _ -> :ok end
RPNCalculator.calculate!(stack, operation)
# => :ok
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate!(stack, operation)
# => ** (ArgumentError) An error occurred
```
> Function names that end in `!` are a warning to programmers that this function may raise an error
## 2. Wrap the error
When doing more research you notice that many functions use atoms and tuples to indicate their success/failure. Implement `calculate/2` using this strategy.
```elixir
stack = []
operation = fn _ -> "operation completed" end
RPNCalculator.calculate(stack, operation)
# => {:ok, "operation completed"}
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate(stack, operation)
# => :error
```
## 3. Pass on the message
Some of the errors contain important information that your coworkers need to have to ensure the correct operation of the system. Implement `calculate_verbose/2` to pass on the error message. The error is a struct that has a `:message` field.
```elixir
stack = []
operation = fn _ -> "operation completed" end
RPNCalculator.calculate_verbose(stack, operation)
# => {:ok, "operation completed"}
stack = []
operation = fn _ -> raise ArgumentError, "An error occurred" end
RPNCalculator.calculate_verbose(stack, operation)
# => {:error, "An error occurred"}
```
## Source
### Created by
- @neenjaw
### Contributed to by
- @angelikatyborska
- @cjmaxik

View file

@ -0,0 +1,19 @@
defmodule RPNCalculator do
def calculate!(stack, operation), do: operation.(stack)
def calculate(stack, operation) do
try do
{:ok, calculate!(stack, operation)}
rescue
_ in RuntimeError -> :error
end
end
def calculate_verbose(stack, operation) do
try do
{:ok, calculate!(stack, operation)}
rescue
e in ArgumentError -> {:error, e.message}
end
end
end

28
rpn-calculator/mix.exs Normal file
View file

@ -0,0 +1,28 @@
defmodule RPNCalculator.MixProject do
use Mix.Project
def project do
[
app: :rpn_calculator,
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

View file

@ -0,0 +1,38 @@
defmodule RPNCalculatorTest do
use ExUnit.Case
@tag task_id: 1
test "calculate! returns an :ok atom" do
assert RPNCalculator.calculate!([], fn _ -> :ok end) == :ok
end
@tag task_id: 1
test "let it crash" do
assert_raise(RuntimeError, fn ->
RPNCalculator.calculate!([], fn _ -> raise "test error" end)
end)
end
@tag task_id: 2
test "calculate returns a tuple" do
assert RPNCalculator.calculate([], fn _ -> "operation completed" end) ==
{:ok, "operation completed"}
end
@tag task_id: 2
test "rescue the crash, no message" do
assert RPNCalculator.calculate([], fn _ -> raise "test error" end) == :error
end
@tag task_id: 3
test "calculate_verbose returns a tuple" do
assert RPNCalculator.calculate_verbose([], fn _ -> "operation completed" end) ==
{:ok, "operation completed"}
end
@tag task_id: 3
test "rescue the crash, get error tuple with message" do
assert RPNCalculator.calculate_verbose([], fn _ -> raise ArgumentError, "test error" end) ==
{:error, "test error"}
end
end

View file

@ -0,0 +1,2 @@
ExUnit.start()
ExUnit.configure(exclude: :pending, trace: true, seed: 0)

View file

@ -0,0 +1,22 @@
{
"blurb": "Learn about defining custom exceptions by continuing work on your experimental Reverse Polish Notation calculator.",
"icon": "error-handling",
"authors": [
"neenjaw"
],
"contributors": [
"angelikatyborska"
],
"files": {
"solution": [
"lib/rpn_calculator/exception.ex"
],
"test": [
"test/rpn_calculator/exception_test.exs"
],
"exemplar": [
".meta/exemplar.ex"
]
},
"language_versions": ">=1.10"
}

View file

@ -0,0 +1 @@
{"track":"elixir","exercise":"stack-underflow","id":"7c23c23937f64262845fde8806001dd6","url":"https://exercism.org/tracks/elixir/exercises/stack-underflow","handle":"rjNemo","is_requester":true,"auto_approve":false}

View file

@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]

24
stack-underflow/.gitignore vendored Normal file
View file

@ -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").
errors-*.tar

75
stack-underflow/HELP.md Normal file
View file

@ -0,0 +1,75 @@
# Help
## Running the tests
From the terminal, change to the base directory of the exercise then execute the tests with:
```bash
$ mix test
```
This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs`
Documentation:
* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html)
* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html)
## Pending tests
In test suites of practice exercises, all but the first test have been tagged to be skipped.
Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol.
For example:
```elixir
# @tag :pending
test "shouting" do
assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
end
```
If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command:
```bash
$ mix test --include pending
```
Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`.
```elixir
# ExUnit.configure(exclude: :pending, trace: true)
```
## Useful `mix test` options
* `test/<FILE>.exs:LINENUM` - runs only a single test, the test from `<FILE>.exs` whose definition is on line `LINENUM`
* `--failed` - runs only tests that failed the last time they ran
* `--max-failures` - the suite stops evaluating tests when this number of test failures
is reached
* `--seed 0` - disables randomization so the tests in a single file will always be ran
in the same order they were defined in
## Submitting your solution
You can submit your solution using the `exercism submit lib/rpn_calculator/exception.ex` command.
This command will upload your solution to the Exercism website and print the solution page's URL.
It's possible to submit an incomplete solution which allows you to:
- See how others have completed the exercise
- Request help from a mentor
## Need to get help?
If you'd like help solving the exercise, check the following pages:
- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir)
- [Exercism's support channel on gitter](https://gitter.im/exercism/support)
- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found.
If you can't find what you're looking for in the documentation, feel free to ask help in the Exercism's BEAM [gitter channel](https://gitter.im/exercism/xerlang).

28
stack-underflow/HINTS.md Normal file
View file

@ -0,0 +1,28 @@
# Hints
## General
- Read about [errors][getting-started-errors] in the Getting Started guide.
- Read about [`defexception`][defexception] in the documentation.
- Read about the [`Exception` behaviour][exception-behaviour] in the documentation.
- Read the code snippets from the introduction.
## 1. Error for Division by Zero
- Implement the module, specifying the message using a special [built-in macro for defining exceptions][defexception].
- Modules can be nested inside of other modules.
## 2. Error when encountering stack underflow
- Implement the module, specifying the message using a special [built-in macro for defining exceptions][defexception].
- You can use one of the Exception Behaviour callbacks to define an exception whose message changes based on the arguments passed to `raise/2`.
- Modules can be nested inside of other modules.
## 3. Write a dividing function
- Write a multi-clause function using guards for control-flow.
- You can pattern match in the function argument list to bind the stack's values to variables.
[getting-started-errors]: https://elixir-lang.org/getting-started/try-catch-and-rescue.html#errors
[defexception]: https://hexdocs.pm/elixir/Kernel.html#defexception/1
[exception-behaviour]: https://hexdocs.pm/elixir/Exception.html

109
stack-underflow/README.md Normal file
View file

@ -0,0 +1,109 @@
# Stack Underflow
Welcome to Stack Underflow on Exercism's Elixir Track.
If you need help running the tests or submitting your code, check out `HELP.md`.
If you get stuck on the exercise, check out `HINTS.md`, but try and solve it without using those first :)
## Introduction
## Exceptions
All errors in Elixir implement the _Exception Behaviour_. Just like the _Access Behaviour_, the _Exception Behaviour_ defines callback functions that a module must implement to fulfill the software contract of the behaviour. Once an error is defined, it has the following properties:
- The module's name defines the error's name.
- The module defines an error-struct.
- The struct will have a `:message` field.
- The module can be be used with `raise/1` and `raise/2` to raise the intended error
The _Exception Behaviour_ also specifies two callbacks: `message/1` and `exception/1`. If unimplemented, default implementations will be used. `message/1` transforms the error-struct to a readable message when called with `raise`. `exception/1` allows additional context to be added to the message when it is called with `raise/2`
### Defining an exception
To define an exception from an error module, we use the `defexception` macro:
```elixir
# Defines a minimal error, with the name `MyError`
defmodule MyError do
defexception message: "error"
end
# Defines an error with a customized exception/1 function
defmodule MyCustomizedError do
defexception message: "custom error"
@impl true
def exception(value) do
case value do
[] ->
%MyCustomizedError{}
_ ->
%MyCustomizedError{message: "Alert: " <> value}
end
end
end
```
### Using exceptions
Defined errors may be used like a built in error using either `raise/1` or `raise/2`.
- `raise/1` raises a specific error by its module name, or, if the argument is a string, it will raise a `RuntimeError` with the string as the message.
- `raise/2` raises a specific error by its module name, and accepts an attributes argument which is used to obtain the error with the appropriate message.
## Instructions
While continuing your work at _Instruments of Texas_, there is progress being made on the Elixir implementation of the RPN calculator. Your team would like to be able to raise errors that are more specific than the generic errors provided by the standard library. You are doing some research, but you have decided to implement two new errors which implement the _Exception Behaviour_.
## 1. Error for Division by Zero
Dividing a number by zero produces an undefined result, which the team decides is best represented by an error.
Implement the `DivisionByZeroError` module to have the error message: `"division by zero occurred"`
```elixir
raise DivisionByZeroError
# => ** (DivisionByZeroError) division by zero occurred
```
## 2. Error when encountering stack underflow
RPN calculators use a _stack_ to keep track of numbers before they are added. The team represents this _stack_ with a list of numbers (integer and floating-point), e.g.: `[3, 4.0]`. Each operation needs a specific number of numbers on the stack in order to perform its calculation. When there are not enough numbers on the stack, this is called a stack underflow error. Implement the `StackUnderflowError` exception which provides a default message, and optional extra context
```elixir
raise StackUnderflowError
# => ** (StackUnderflowError) stack underflow occurred
raise StackUnderflowError, "when dividing"
# => ** (StackUnderflowError) stack underflow occurred, context: when dividing
```
## 3. Write a dividing function
Implement the `divide/1` function which takes a stack _(list of numbers)_ and:
- raises _stack underflow_ when the stack does not contain enough numbers
- raises _division by zero_ when the divisor is 0 (note the stack of numbers is stored in the reverse order)
- performs the division when no errors are raised
```elixir
RPNCalculator.Exception.divide([])
# => ** (StackUnderflowError) stack underflow occurred, context: when dividing
RPNCalculator.Exception.divide([0, 100])
# => ** (DivisionByZeroError) division by zero occurred
RPNCalculator.Exception.divide([4, 16])
# => 4
```
> Note the order of the list is reversed!
## Source
### Created by
- @neenjaw
### Contributed to by
- @angelikatyborska

View file

@ -0,0 +1,22 @@
defmodule RPNCalculator.Exception do
defmodule DivisionByZeroError do
defexception message: "division by zero occurred"
end
defmodule StackUnderflowError do
defexception message: "stack underflow occurred"
@impl true
def exception(context) do
case context do
[] -> %__MODULE__{}
_ -> %__MODULE__{message: "stack underflow occurred, context: #{context}"}
end
end
end
def divide([]), do: raise(StackUnderflowError, "when dividing")
def divide([_]), do: raise(StackUnderflowError, "when dividing")
def divide([0 | _]), do: raise(DivisionByZeroError)
def divide([denom, num]), do: div(num, denom)
end

28
stack-underflow/mix.exs Normal file
View file

@ -0,0 +1,28 @@
defmodule RPNCalculator.MixProject do
use Mix.Project
def project do
[
app: :stack_underflow,
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

View file

@ -0,0 +1,140 @@
defmodule RPNCalculator.ExceptionTest do
use ExUnit.Case
config = ExUnit.configuration()
# When modules used in the test suite don't exist, or don't implement the desired struct, a compile error occurs
# so the test suite is never run, so using the cond to conditionally create test cases based on if the module is defined and loaded
# allows for meaningful error messages.
cond do
config[:undefined_division_by_zero_error_module] ->
@tag task_id: 1
test "DivisionByZeroError defined" do
flunk("Implement the DivisionByZeroError inside of the `RPNCalculator.Exception` module")
end
config[:undefined_division_by_zero_error_exception] ->
@tag task_id: 1
test "DivisionByZeroError defined" do
flunk("define DivisionByZeroError exception using `defexception`")
end
true ->
@tag task_id: 1
test "DivisionByZeroError fields defined by `defexception`" do
assert %RPNCalculator.Exception.DivisionByZeroError{}.__exception__ == true
assert %RPNCalculator.Exception.DivisionByZeroError{}.__struct__ ==
RPNCalculator.Exception.DivisionByZeroError
assert %RPNCalculator.Exception.DivisionByZeroError{}.message ==
"division by zero occurred"
end
@tag task_id: 1
test "DivisionByZeroError message when raised with raise/1" do
assert_raise(
RPNCalculator.Exception.DivisionByZeroError,
"division by zero occurred",
fn ->
raise RPNCalculator.Exception.DivisionByZeroError
end
)
end
end
cond do
config[:undefined_stack_underflow_error_module] ->
@tag task_id: 2
test "StackUnderflowError defined" do
flunk("Implement the StackUnderflowError inside of the `RPNCalculator.Exception` module")
end
config[:undefined_stack_underflow_error_exception] ->
@tag task_id: 2
test "StackUnderflowError defined" do
flunk("define StackUnderflowError exception using `defexception`")
end
true ->
@tag task_id: 2
test "StackUnderflowError fields defined by `defexception`" do
assert %RPNCalculator.Exception.StackUnderflowError{}.__exception__ == true
assert %RPNCalculator.Exception.StackUnderflowError{}.__struct__ ==
RPNCalculator.Exception.StackUnderflowError
assert %RPNCalculator.Exception.StackUnderflowError{}.message ==
"stack underflow occurred"
end
@tag task_id: 2
test "StackUnderflowError message when raised with raise/1" do
assert_raise(
RPNCalculator.Exception.StackUnderflowError,
"stack underflow occurred",
fn ->
raise RPNCalculator.Exception.StackUnderflowError
end
)
end
@tag task_id: 2
test "StackUnderflowError message when raised with raise/2" do
assert_raise(
RPNCalculator.Exception.StackUnderflowError,
"stack underflow occurred, context: test",
fn ->
raise RPNCalculator.Exception.StackUnderflowError, "test"
end
)
end
end
describe "divide/1" do
@tag task_id: 3
test "when stack doesn't contain any numbers, raise StackUnderflowError" do
assert_raise(
RPNCalculator.Exception.StackUnderflowError,
"stack underflow occurred, context: when dividing",
fn ->
RPNCalculator.Exception.divide([])
end
)
end
@tag task_id: 3
test "when stack contains only one number, raise StackUnderflowError" do
assert_raise(
RPNCalculator.Exception.StackUnderflowError,
"stack underflow occurred, context: when dividing",
fn ->
RPNCalculator.Exception.divide([3])
end
)
end
@tag task_id: 3
test "when stack contains only one number, raise StackUnderflowError, even when it's a zero" do
assert_raise(
RPNCalculator.Exception.StackUnderflowError,
"stack underflow occurred, context: when dividing",
fn ->
RPNCalculator.Exception.divide([0])
end
)
end
@tag task_id: 3
test "when divisor is 0, raise DivisionByZeroError" do
assert_raise(RPNCalculator.Exception.DivisionByZeroError, "division by zero occurred", fn ->
RPNCalculator.Exception.divide([0, 2])
end)
end
@tag task_id: 3
test "divisor is not 0, don't raise" do
assert RPNCalculator.Exception.divide([2, 4]) == 2
end
end
end

View file

@ -0,0 +1,13 @@
options = [
undefined_division_by_zero_error_module:
Code.ensure_compiled(RPNCalculator.Exception.DivisionByZeroError) == {:error, :nofile},
undefined_stack_underflow_error_module:
Code.ensure_compiled(RPNCalculator.Exception.StackUnderflowError) == {:error, :nofile},
undefined_struct_division_by_zero_error_exception:
not function_exported?(RPNCalculator.Exception.DivisionByZeroError, :__struct__, 0),
undefined_struct_stack_underflow_error_exception:
not function_exported?(RPNCalculator.Exception.StackUnderflowError, :__struct__, 0)
]
ExUnit.start(options)
ExUnit.configure(exclude: :pending, trace: true, seed: 0)