This repository contains sample code for exploring the tasty
test framework. We'll build a test suite for a simple IP filtering application and explore two different testing methodologies:
The sample code is adapted from Chapter 8 of Haskell in Depth by Vitaly Bragilevsky. The original source code can be found here. Significant portions of the code have been modified to increase simplicity and readability.
The application can be built and run using either Nix or Cabal.
If you use Nix, you can build the application from the flake.nix
file, which will install compatible versions of the Haskell toolchain as well as a preconfigured VS Codium editor you can use to explore the code.
You must have the following experimental-features
enabled in your Nix configuration (/etc/nix/nix.conf
):
# /etc/nix/nix.conf
experimental-features = flakes nix-command
🚨 IMPORTANT! You must restart the nix-daemon
after modifying nix.conf
to apply the changes
Linux:
sudo systemctl restart nix-daemon
MacOS:
sudo launchctl stop org.nixos.nix-daemon
sudo launchctl start org.nixos.nix-daemon
After cloning the repository, enter the tasty-tutorial
directory and run nix develop
to enter the Nix environment.
Once the dependencies finish building, you can run codium .
to open a preconfigured VS Codium instance with IDE support. Run cabal build
in the integrated terminal to build the project.
Running the application with Cabal only (without Nix) requires a version of GHC >= 9.2.5
and < 9.4
, Cabal >= 3.0
, and a compatible version of Haskell Language Server (HLS).
Use GHCup to install the required tooling and ghcup tui
to adjust versions as needed.
You can then build the project via cabal build
.
You can try the app using the following command:
cabal run tasty-tutorial -- data/ipranges.txt 192.168.1.3
You can run the test suite with the following command:
cabal test tasty-tutorial.cabal
The application is a command-line utility that checks whether a specified IP address is contained within any ranges present in a given text file.
The Main
module in app/Main.hs
makes use of the optparse-applicative
library to provide a CLI and associated help text, accepting a filepath and IP address string as options. The code in this module may be difficult to understand without prior familiarity with optparse-applicative
. It isn't critical to understand this code in detail: the important piece is the run
function, which parses the IP ranges in the file and the provided IP address, then either prints the result or throws exceptions if the data is invalid or malformed.
Our test suite will focus on testing the business logic of the application, which consists of:
- parsing the IP ranges in the input file and the provided IP address
- checking if the address is contained in any of the IP ranges
This functionality is contained in src/ParseIP.hs
and src/LookupIP.hs
, respectively. The custom types used in the application are contained in src/IPTypes.hs
.
The ParseIP
module contains the following functions:
parseIP
: parses an IP address string (i.e."192.168.3.15"
) to aMaybe IP
value (whereIP
is anewtype
wrapping aWord32
value)parseIPRange
: parses a string expressing an IP range (i.e."192.168.0.1,192.168.3.100"
) into aMaybe IPRange
value (whereIPRange
is a product type consisting of twoIP
values).parseIPRanges
: parses a string containing multiple IP range strings (separated by newlines), returning anEither ParseError IPRangeDB
value.- An
IPRangeDB
value is anewtype
wrapping list ofIPRange
values. - A
ParseError
is a customException
type containing anInt
value representing the line number where the error occurred.
- An
The LookupIP
module contains the following functions:
lookupIP
: a predicate function that takes anIPRangeDB
value and anIP
value and indicates whether theIP
is covered by one of theIPRange
values in theIPRangeDB
.reportIP
: performslookupIP
and outputs a formatted string containing the IP address and a YES/NO result indicating whether it satisfies one of the ranges in theIPRangeDB
.