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-daemonMacOS:
sudo launchctl stop org.nixos.nix-daemon
sudo launchctl start org.nixos.nix-daemonAfter 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 IPvalue (whereIPis anewtypewrapping aWord32value)parseIPRange: parses a string expressing an IP range (i.e."192.168.0.1,192.168.3.100") into aMaybe IPRangevalue (whereIPRangeis a product type consisting of twoIPvalues).parseIPRanges: parses a string containing multiple IP range strings (separated by newlines), returning anEither ParseError IPRangeDBvalue.- An
IPRangeDBvalue is anewtypewrapping list ofIPRangevalues. - A
ParseErroris a customExceptiontype containing anIntvalue representing the line number where the error occurred.
- An
The LookupIP module contains the following functions:
lookupIP: a predicate function that takes anIPRangeDBvalue and anIPvalue and indicates whether theIPis covered by one of theIPRangevalues in theIPRangeDB.reportIP: performslookupIPand outputs a formatted string containing the IP address and a YES/NO result indicating whether it satisfies one of the ranges in theIPRangeDB.