diff --git a/heptapod.cabal b/heptapod.cabal
--- a/heptapod.cabal
+++ b/heptapod.cabal
@@ -10,7 +10,7 @@
 maintainer: Taylor Fausak
 name: heptapod
 synopsis: Generate UUIDv7 values.
-version: 0.2024.8.16
+version: 1.0.0.0
 
 source-repository head
   location: https://github.com/tfausak/heptapod
@@ -25,6 +25,7 @@
   default-language: Haskell2010
   ghc-options:
     -Weverything
+    -Wno-all-missed-specialisations
     -Wno-implicit-prelude
     -Wno-missing-export-lists
     -Wno-missing-safe-haskell-mode
@@ -35,6 +36,13 @@
   if flag(pedantic)
     ghc-options: -Werror
 
+common executable
+  import: library
+  build-depends: heptapod
+  ghc-options:
+    -rtsopts
+    -threaded
+
 library
   import: library
   build-depends:
@@ -46,3 +54,25 @@
   -- cabal-gild: discover source/library
   exposed-modules: Heptapod
   hs-source-dirs: source/library
+
+test-suite heptapod-test-suite
+  import: executable
+  build-depends:
+    tasty ^>=1.5,
+    tasty-hunit ^>=0.10.2,
+    time,
+    uuid-types,
+
+  hs-source-dirs: source/test-suite
+  main-is: Main.hs
+  type: exitcode-stdio-1.0
+
+benchmark heptapod-benchmark
+  import: executable
+  build-depends:
+    tasty-bench ^>=0.4,
+    uuid ^>=1.3.16,
+
+  hs-source-dirs: source/benchmark
+  main-is: Main.hs
+  type: exitcode-stdio-1.0
diff --git a/source/benchmark/Main.hs b/source/benchmark/Main.hs
new file mode 100644
--- /dev/null
+++ b/source/benchmark/Main.hs
@@ -0,0 +1,15 @@
+import qualified Data.UUID.V1 as UUIDv1
+import qualified Data.UUID.V4 as UUIDv4
+import qualified Heptapod
+import qualified Test.Tasty.Bench as Bench
+
+main :: IO ()
+main =
+  Bench.defaultMain
+    [ Bench.bgroup
+        "uuid"
+        [ Bench.bench "v1" $ Bench.nfIO UUIDv1.nextUUID,
+          Bench.bench "v4" $ Bench.nfIO UUIDv4.nextRandom,
+          Bench.bench "v7" $ Bench.nfIO Heptapod.generate
+        ]
+    ]
diff --git a/source/test-suite/Main.hs b/source/test-suite/Main.hs
new file mode 100644
--- /dev/null
+++ b/source/test-suite/Main.hs
@@ -0,0 +1,37 @@
+import qualified Data.Time as Time
+import qualified Data.Time.Clock.System as Time
+import qualified Data.UUID.Types as Uuid
+import qualified Heptapod
+import qualified Test.Tasty as Tasty
+import qualified Test.Tasty.HUnit as Unit
+
+main :: IO ()
+main = Tasty.defaultMain testTree
+
+testTree :: Tasty.TestTree
+testTree =
+  Tasty.testGroup
+    "Heptapod"
+    [ Tasty.testGroup
+        "generate"
+        [ Unit.testCase "works" $ do
+            uuid <- Heptapod.generate
+            length (Uuid.toString uuid) Unit.@?= 36
+        ],
+      Tasty.testGroup
+        "build"
+        [ Unit.testCase "works with nil" $ do
+            let epoch = Time.MkSystemTime 0 0
+            let uuid = Heptapod.build epoch 0 0
+            Uuid.toString uuid Unit.@?= "00000000-0000-7000-8000-000000000000",
+          Unit.testCase "works with test vector" $ do
+            -- https://datatracker.ietf.org/doc/html/rfc9562#name-example-of-a-uuidv7-value
+            let time =
+                  Time.utcToSystemTime
+                    . Time.localTimeToUTC (Time.hoursToTimeZone (-5))
+                    . Time.LocalTime (Time.fromGregorian 2022 2 22)
+                    $ Time.TimeOfDay 14 22 22
+            let uuid = Heptapod.build time 0xcc3 0x18C4DC0C0C07398F
+            Uuid.toString uuid Unit.@?= "017f22e2-79b0-7cc3-98c4-dc0c0c07398f"
+        ]
+    ]
