packages feed

hpio 0.8.0.0 → 0.8.0.1

raw patch · 11 files changed

+2683/−15 lines, 11 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- System.GPIO.Linux.Sysfs.Types: instance GHC.Generics.Constructor System.GPIO.Linux.Sysfs.Types.C1_0SysfsEdge
- System.GPIO.Linux.Sysfs.Types: instance GHC.Generics.Constructor System.GPIO.Linux.Sysfs.Types.C1_1SysfsEdge
- System.GPIO.Linux.Sysfs.Types: instance GHC.Generics.Constructor System.GPIO.Linux.Sysfs.Types.C1_2SysfsEdge
- System.GPIO.Linux.Sysfs.Types: instance GHC.Generics.Constructor System.GPIO.Linux.Sysfs.Types.C1_3SysfsEdge
- System.GPIO.Linux.Sysfs.Types: instance GHC.Generics.Datatype System.GPIO.Linux.Sysfs.Types.D1SysfsEdge
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_0Pin
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_0PinActiveLevel
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_0PinCapabilities
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_0PinDirection
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_0PinInputMode
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_0PinInterruptMode
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_0PinOutputMode
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_0PinValue
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_1PinActiveLevel
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_1PinDirection
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_1PinInputMode
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_1PinInterruptMode
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_1PinOutputMode
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_1PinValue
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_2PinInputMode
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_2PinInterruptMode
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_2PinOutputMode
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_3PinInputMode
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_3PinInterruptMode
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_3PinOutputMode
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_4PinOutputMode
- System.GPIO.Types: instance GHC.Generics.Constructor System.GPIO.Types.C1_5PinOutputMode
- System.GPIO.Types: instance GHC.Generics.Datatype System.GPIO.Types.D1Pin
- System.GPIO.Types: instance GHC.Generics.Datatype System.GPIO.Types.D1PinActiveLevel
- System.GPIO.Types: instance GHC.Generics.Datatype System.GPIO.Types.D1PinCapabilities
- System.GPIO.Types: instance GHC.Generics.Datatype System.GPIO.Types.D1PinDirection
- System.GPIO.Types: instance GHC.Generics.Datatype System.GPIO.Types.D1PinInputMode
- System.GPIO.Types: instance GHC.Generics.Datatype System.GPIO.Types.D1PinInterruptMode
- System.GPIO.Types: instance GHC.Generics.Datatype System.GPIO.Types.D1PinOutputMode
- System.GPIO.Types: instance GHC.Generics.Datatype System.GPIO.Types.D1PinValue
- System.GPIO.Types: instance GHC.Generics.Selector System.GPIO.Types.S1_0_0PinCapabilities
- System.GPIO.Types: instance GHC.Generics.Selector System.GPIO.Types.S1_0_1PinCapabilities
- System.GPIO.Types: instance GHC.Generics.Selector System.GPIO.Types.S1_0_2PinCapabilities

Files

.travis.yml view
@@ -104,35 +104,56 @@     curl -L https://www.stackage.org/stack/linux-x86_64 | tar xz --wildcards --strip-components=1 -C ~/.local/bin '*/stack'   fi +  # Use the more reliable S3 mirror of Hackage+  mkdir -p $HOME/.cabal+  echo 'remote-repo: hackage.haskell.org:http://hackage.fpcomplete.com/' > $HOME/.cabal/config+  echo 'remote-repo-cache: $HOME/.cabal/packages' >> $HOME/.cabal/config  ++  if [ "$CABALVER" != "1.16" ]+  then+    echo 'jobs: $ncpus' >> $HOME/.cabal/config+  fi++# Get the list of packages from the stack.yaml file+- PACKAGES=$(stack --install-ghc query locals | grep '^ *path' | sed 's@^ *path:@@')+ install: - echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]" - if [ -f configure.ac ]; then autoreconf -i; fi - |+  set -ex   case "$BUILD" in     stack)-      stack --no-terminal --install-ghc $ARGS test --only-dependencies+      stack --no-terminal --install-ghc $ARGS test --bench --only-dependencies       ;;     cabal)       cabal --version       travis_retry cabal update-      cabal install --only-dependencies --enable-tests --enable-benchmarks --force-reinstalls --ghc-options=-O0 --reorder-goals --max-backjumps=-1 $CABALARGS+      cabal install --only-dependencies --enable-tests --enable-benchmarks --force-reinstalls --ghc-options=-O0 --reorder-goals --max-backjumps=-1 $CABALARGS $PACKAGES       ;;   esac+  set +ex  script: - |+  set -ex   case "$BUILD" in     stack)-      stack --no-terminal $ARGS test --haddock --no-haddock-deps+      stack --no-terminal $ARGS test --bench --no-run-benchmarks --haddock --no-haddock-deps       ;;     cabal)-      cabal configure --enable-tests --enable-benchmarks -v2 --ghc-options="-O0 -Werror"-      cabal build-      cabal check || [ "$CABALVER" == "1.16" ]-      cabal test-      cabal sdist-      cabal copy-      SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && \-        (cd dist && cabal install --force-reinstalls "$SRC_TGZ")+      cabal install --enable-tests --enable-benchmarks --force-reinstalls --ghc-options=-O0 --reorder-goals --max-backjumps=-1 $CABALARGS $PACKAGES++      ORIGDIR=$(pwd)+      for dir in $PACKAGES+      do+        cd $dir+        cabal check || [ "$CABALVER" == "1.16" ]+        cabal sdist+        SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz && \+          (cd dist && cabal install --force-reinstalls "$SRC_TGZ")+        cd $ORIGDIR+      done       ;;   esac+  set +ex
+ changelog.md view
@@ -0,0 +1,8 @@+## 0.8.0.1 (2016-05-30)++Fixes:+  - Fix sdist problem with test files (#47)++## 0.8.0.0 (2016-05-27)++Initial release.
default.nix view
@@ -5,7 +5,7 @@ }: mkDerivation {   pname = "hpio";-  version = "0.8.0.0";+  version = "0.8.0.1";   src = ./.;   isLibrary = true;   isExecutable = true;
hpio.cabal view
@@ -1,5 +1,5 @@ Name:                   hpio-Version:                0.8.0.0+Version:                0.8.0.1 Cabal-Version:          >= 1.10 Build-Type:             Simple Author:                 Drew Hess <src@drewhess.com>@@ -29,6 +29,7 @@ Extra-Doc-Files:        README.md Extra-Source-Files:     .travis.yml                       , Hlint.hs+                      , changelog.md                       , default.nix                       , shell.nix                       , stack.yaml@@ -239,6 +240,12 @@                       , System.GPIO.Monad                       , System.GPIO.Tutorial                       , System.GPIO.Types+                      , Spec+                      , System.GPIO.TypesSpec+                      , System.GPIO.Linux.Sysfs.LinuxBboneSpec+                      , System.GPIO.Linux.Sysfs.MockInternalSpec+                      , System.GPIO.Linux.Sysfs.MockSpec+                      , System.GPIO.Linux.Sysfs.SysfsGpioMockSpec   C-Sources:            src/System/GPIO/Linux/Sysfs/pollSysfs.c   If flag(linux-bbone-tests)     cpp-options: -DRUN_LINUX_BBONE_TESTS=1@@ -250,4 +257,4 @@ Source-Repository this   Type:                 git   Location:             git://github.com/dhess/hpio.git-  Tag:                  v0.8.0.0+  Tag:                  v0.8.0.1
shell.nix view
@@ -11,7 +11,7 @@       }:       mkDerivation {         pname = "hpio";-        version = "0.8.0.0";+        version = "0.8.0.1";         src = ./.;         isLibrary = true;         isExecutable = true;
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --no-main #-}
+ test/System/GPIO/Linux/Sysfs/LinuxBboneSpec.hs view
@@ -0,0 +1,1144 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE OverloadedStrings #-}+{-# OPTIONS_GHC -fno-warn-unused-binds #-}++module System.GPIO.Linux.Sysfs.LinuxBboneSpec (spec) where++import Control.Concurrent (forkIO, threadDelay)+import Control.Concurrent.MVar (newEmptyMVar, putMVar, takeMVar)+import Control.Monad (void)+import Control.Monad.Catch (throwM)+import Control.Monad.IO.Class (MonadIO, liftIO)+import System.Directory (doesDirectoryExist)+import System.GPIO.Linux.Sysfs (SysfsException(..), runSysfsGpioIO)+import System.GPIO.Monad (MonadGpio(..), withPin)+import System.GPIO.Types+       (Pin(..), PinInputMode(..), PinOutputMode(..), PinActiveLevel(..),+        PinDirection(..), PinInterruptMode(..), PinValue(..))+import Test.Hspec++isInvalidPinError :: SysfsException -> Bool+isInvalidPinError (InvalidPin _) = True+isInvalidPinError _ = False++isNotExportedError :: SysfsException -> Bool+isNotExportedError (NotExported _) = True+isNotExportedError _ = False++isUnsupportedInputMode :: SysfsException -> Bool+isUnsupportedInputMode (UnsupportedInputMode _ _) = True+isUnsupportedInputMode _ = False++isUnsupportedOutputMode :: SysfsException -> Bool+isUnsupportedOutputMode (UnsupportedOutputMode _ _) = True+isUnsupportedOutputMode _ = False++isPermissionDeniedError :: SysfsException -> Bool+isPermissionDeniedError (PermissionDenied _) = True+isPermissionDeniedError _ = False++isInvalidOperationError :: SysfsException -> Bool+isInvalidOperationError (InvalidOperation _) = True+isInvalidOperationError _ = False++-- Note: it's not practical to test all exceptional cases, but we do+-- our best.++-- Note: make sure the tests are always compiled, but only actually+-- run on the proper platform.+spec :: Spec+spec =+#ifdef RUN_LINUX_BBONE_TESTS+  runTests+#else+  describe "Linux BeagleBone tests disabled on this platform" $ return ()+#endif++-- NOTE: In order to run these tests properly, the following must be true:+--+-- * They must be run on BeagleBone Black hardware running Linux.+-- * You must have permission to export and unexport pins via the+--   sysfs GPIO filesystem.+-- * You must have permission to write pin attributes via the sysfs+--   GPIO filesystem.+-- * GPIO pins P9-15 (sysfs GPIO pin number 48) and P8-15 (sysfs GPIO+--   pin number 47) must be jumpered together.++testPin1 :: Pin+testPin1 = Pin 48++testPin2 :: Pin+testPin2 = Pin 47++invalidPin :: Pin+invalidPin = Pin 9000++-- Note: tests which modify pin state have a slight delay between+-- opening the pin and modifying any state. This is so that, if the+-- user running the tests is not root and is using a udev action to+-- change pin ownership (which is the only way at the time of writing+-- to do that), the script has time to run.++udevScriptWait :: MonadIO m => m ()+udevScriptWait = liftIO $ threadDelay 500000++runTests :: Spec+runTests =+  do+    describe "runSysfsGpioIO" $+      do context "pins" $+           it "returns the full list of pins on the system" $+             runSysfsGpioIO pins `shouldReturn` (map Pin [0..127])+         context "openPin/closePin" $+           do it "exports/unexports the pin" $+                runSysfsGpioIO+                  (do h <- openPin testPin1+                      exported <- liftIO $ doesDirectoryExist "/sys/class/gpio/gpio48"+                      closePin h+                      stillExported <- liftIO $ doesDirectoryExist "/sys/class/gpio/gpio48"+                      return (exported, stillExported))+                `shouldReturn` (True, False)+              it "openPin doesn't complain if the pin is already exported" $+                  runSysfsGpioIO+                    (withPin testPin1 $ \_ ->+                       do h <- openPin testPin1+                          void $ getPinDirection h+                          return True)+                    `shouldReturn` True+              it "openPin fails if the pin is invalid" $+                  runSysfsGpioIO+                    (do h <- openPin invalidPin+                        dir <- getPinDirection h+                        return dir)+                    `shouldThrow` isInvalidPinError+              it "closePin doesn't complain the pin is already unexported" $+                  runSysfsGpioIO+                    (withPin testPin1 $ \_ ->+                       do h <- openPin testPin1+                          closePin h+                          closePin h)+                    `shouldReturn` ()+         context "withPin" $+           do it "exports/unexports the pin" $+                do runSysfsGpioIO+                     (withPin testPin1 $ const $+                        liftIO $ doesDirectoryExist "/sys/class/gpio/gpio48")+                     `shouldReturn` True+                   doesDirectoryExist "/sys/class/gpio/gpio48" `shouldReturn` False+              it "unexports the pin upon exception" $+                do runSysfsGpioIO+                     (withPin testPin1 $ const $+                        throwM $ userError "Foo")+                     `shouldThrow` anyIOException+                   doesDirectoryExist "/sys/class/gpio/gpio48" `shouldReturn` False+              it "handles double-open and double-close gracefully" $+                do runSysfsGpioIO+                     (withPin testPin1 $ const $+                        withPin testPin1 $ const $+                          liftIO $ doesDirectoryExist "/sys/class/gpio/gpio48")+                     `shouldReturn` True+                   doesDirectoryExist "/sys/class/gpio/gpio48" `shouldReturn` False+              it "fails if the pin is invalid" $+                do runSysfsGpioIO+                     (withPin invalidPin $ const $+                        liftIO $ doesDirectoryExist "/sys/class/gpio/gpio9000")+                     `shouldThrow` isInvalidPinError+         context "getPinDirection" $+           do it "gets the pin's direction" $+                runSysfsGpioIO+                  (withPin testPin1 $ \h ->+                    do udevScriptWait+                       setPinInputMode h InputDefault+                       dir1 <- getPinDirection h+                       setPinOutputMode h OutputDefault Low+                       dir2 <- getPinDirection h+                       setPinInputMode h InputDefault+                       dir3 <- getPinDirection h+                       return (dir1, dir2, dir3))+                  `shouldReturn` (In, Out, In)+         context "getPinInputMode" $+           do it "gets the pin's input mode" $+                runSysfsGpioIO+                   (withPin testPin1 $ \h ->+                     do udevScriptWait+                        setPinInputMode h InputDefault+                        getPinInputMode h)+                   `shouldReturn` InputDefault+              it "fails when the pin is in output mode" $+                runSysfsGpioIO+                  (withPin testPin1 $ \h ->+                    do udevScriptWait+                       setPinOutputMode h OutputDefault Low+                       getPinInputMode h)+                  `shouldThrow` isInvalidOperationError+         context "setPinInputMode" $+           do it "sets the pin's input mode and direction" $+                do runSysfsGpioIO+                     (withPin testPin1 $ \h ->+                       do udevScriptWait+                          setPinInputMode h InputDefault+                          m <- getPinInputMode h+                          d <- getPinDirection h+                          return (m,d))+                     `shouldReturn` (InputDefault,In)+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                        do udevScriptWait+                           setPinOutputMode h OutputDefault Low+                           setPinInputMode h InputDefault+                           m <- getPinInputMode h+                           d <- getPinDirection h+                           return (m,d))+                      `shouldReturn` (InputDefault,In)+              it "fails when the input mode is unsupported" $+                runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                        do udevScriptWait+                           setPinInputMode h InputPullDown+                           getPinInputMode h)+                      `shouldThrow` isUnsupportedInputMode+         context "getPinOutputMode" $+            do it "gets the pin's input mode" $+                 runSysfsGpioIO+                    (withPin testPin1 $ \h ->+                      do udevScriptWait+                         setPinOutputMode h OutputDefault Low+                         getPinOutputMode h)+                    `shouldReturn` OutputDefault+               it "fails when the pin is in input mode" $+                 runSysfsGpioIO+                   (withPin testPin1 $ \h ->+                     do udevScriptWait+                        setPinInputMode h InputDefault+                        getPinOutputMode h)+                   `shouldThrow` isInvalidOperationError+         context "setPinOutputMode" $+           do it "sets the pin's output mode, direction, and value" $+                do runSysfsGpioIO+                     (withPin testPin1 $ \h ->+                       do udevScriptWait+                          setPinInputMode h InputDefault+                          setPinActiveLevel h ActiveHigh+                          setPinOutputMode h OutputDefault Low+                          v <- readPin h+                          m <- getPinOutputMode h+                          d <- getPinDirection h+                          return (v,m,d))+                     `shouldReturn` (Low,OutputDefault,Out)+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                        do udevScriptWait+                           setPinInputMode h InputDefault+                           setPinActiveLevel h ActiveHigh+                           setPinOutputMode h OutputDefault High+                           v <- readPin h+                           m <- getPinOutputMode h+                           d <- getPinDirection h+                           return (v,m,d))+                      `shouldReturn` (High,OutputDefault,Out)+                   runSysfsGpioIO+                     (withPin testPin1 $ \h ->+                       do udevScriptWait+                          setPinActiveLevel h ActiveHigh+                          setPinInputMode h InputDefault+                          setPinOutputMode h OutputDefault High+                          v <- readPin h+                          m <- getPinOutputMode h+                          d <- getPinDirection h+                          return (v,m,d))+                     `shouldReturn` (High,OutputDefault,Out)+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                        do udevScriptWait+                           setPinActiveLevel h ActiveHigh+                           setPinInputMode h InputDefault+                           setPinOutputMode h OutputDefault Low+                           v <- readPin h+                           m <- getPinOutputMode h+                           d <- getPinDirection h+                           return (v,m,d))+                      `shouldReturn` (Low,OutputDefault,Out)+              it "respects the pin's active level" $+                 do runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                        do udevScriptWait+                           setPinInputMode h InputDefault+                           setPinActiveLevel h ActiveLow+                           setPinOutputMode h OutputDefault Low+                           setPinActiveLevel h ActiveHigh+                           v <- readPin h+                           m <- getPinOutputMode h+                           d <- getPinDirection h+                           return (v,m,d))+                      `shouldReturn` (High,OutputDefault,Out)+                    runSysfsGpioIO+                       (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinInputMode h InputDefault+                            setPinActiveLevel h ActiveLow+                            setPinOutputMode h OutputDefault High+                            setPinActiveLevel h ActiveHigh+                            v <- readPin h+                            m <- getPinOutputMode h+                            d <- getPinDirection h+                            return (v,m,d))+                       `shouldReturn` (Low,OutputDefault,Out)+                    runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                        do udevScriptWait+                           setPinActiveLevel h ActiveLow+                           setPinInputMode h InputDefault+                           setPinOutputMode h OutputDefault High+                           setPinActiveLevel h ActiveHigh+                           v <- readPin h+                           m <- getPinOutputMode h+                           d <- getPinDirection h+                           return (v,m,d))+                      `shouldReturn` (Low,OutputDefault,Out)+                    runSysfsGpioIO+                       (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinActiveLevel h ActiveLow+                            setPinInputMode h InputDefault+                            setPinOutputMode h OutputDefault Low+                            setPinActiveLevel h ActiveHigh+                            v <- readPin h+                            m <- getPinOutputMode h+                            d <- getPinDirection h+                            return (v,m,d))+                       `shouldReturn` (High,OutputDefault,Out)+              it "can set the pin's direction to 'Out' when it's configured for edge-triggered reads" $+                runSysfsGpioIO+                  (withPin testPin1 $ \h ->+                    do udevScriptWait+                       setPinInputMode h InputDefault+                       setPinInterruptMode h RisingEdge+                       setPinOutputMode h OutputDefault Low+                       setPinInputMode h InputDefault+                       setPinInterruptMode h FallingEdge+                       setPinOutputMode h OutputDefault High+                       setPinInputMode h InputDefault+                       setPinInterruptMode h Level+                       setPinOutputMode h OutputDefault Low+                       return True)+                  `shouldReturn` True+              it "fails when the output mode is unsupported" $+                 runSysfsGpioIO+                       (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinOutputMode h OutputOpenSourcePullDown Low+                            getPinInputMode h)+                       `shouldThrow` isUnsupportedOutputMode+         context "getPinActiveLevel/setPinActiveLevel" $+           it "gets and sets the pin's active level" $+             runSysfsGpioIO+               (withPin testPin1 $ \h ->+                 do udevScriptWait+                    setPinActiveLevel h ActiveLow+                    level1 <- getPinActiveLevel h+                    setPinActiveLevel h ActiveHigh+                    level2 <- getPinActiveLevel h+                    setPinActiveLevel h ActiveLow+                    level3 <- getPinActiveLevel h+                    return (level1, level2, level3))+               `shouldReturn` (ActiveLow, ActiveHigh, ActiveLow)+         context "togglePinActiveLevel" $+           it "toggles the pin's active level" $+             runSysfsGpioIO+               (withPin testPin1 $ \h ->+                 do udevScriptWait+                    setPinActiveLevel h ActiveHigh+                    level1 <- togglePinActiveLevel h+                    level2 <- togglePinActiveLevel h+                    level3 <- togglePinActiveLevel h+                    return (level1, level2, level3))+               `shouldReturn` (ActiveLow, ActiveHigh, ActiveLow)+         context "readPin/writePin" $+           -- Note: if these tests fail, you might not have hooked pin+           -- P9-15 up to pin P8-15!+           do it "reads and writes the pin's value" $+                runSysfsGpioIO+                  (withPin testPin1 $ \h1 ->+                     withPin testPin2 $ \h2 ->+                       do udevScriptWait+                          setPinInputMode h1 InputDefault+                          setPinActiveLevel h1 ActiveHigh+                          setPinActiveLevel h2 ActiveHigh+                          setPinOutputMode h2 OutputDefault High+                          -- give the pin time to settle+                          liftIO $ threadDelay 250000+                          val1 <- readPin h1+                          writePin h2 Low+                          liftIO $ threadDelay 250000+                          val2 <- readPin h1+                          writePin h2 High+                          liftIO $ threadDelay 250000+                          val3 <- readPin h1+                          return (val1, val2, val3))+                  `shouldReturn` (High, Low, High)+              it "readPin obeys the pin's active level" $+                runSysfsGpioIO+                  (withPin testPin1 $ \h1 ->+                     withPin testPin2 $ \h2 ->+                       do udevScriptWait+                          setPinInputMode h1 InputDefault+                          setPinActiveLevel h1 ActiveLow+                          setPinActiveLevel h2 ActiveHigh+                          setPinOutputMode h2 OutputDefault High+                          -- give the pin time to settle+                          liftIO $ threadDelay 250000+                          val1 <- readPin h1+                          writePin h2 Low+                          liftIO $ threadDelay 250000+                          val2 <- readPin h1+                          writePin h2 High+                          liftIO $ threadDelay 250000+                          val3 <- readPin h1+                          return (val1, val2, val3))+                  `shouldReturn` (Low, High, Low)+              it "readPin works on output pins" $+                 runSysfsGpioIO+                   (withPin testPin2 $ \h ->+                      do udevScriptWait+                         setPinActiveLevel h ActiveHigh+                         setPinOutputMode h OutputDefault High+                         liftIO $ threadDelay 250000+                         val1 <- readPin h+                         writePin h Low+                         liftIO $ threadDelay 250000+                         val2 <- readPin h+                         writePin h High+                         liftIO $ threadDelay 250000+                         val3 <- readPin h+                         return (val1, val2, val3))+                   `shouldReturn` (High, Low, High)+              it "readPin works on output pins (active-low)" $+                 runSysfsGpioIO+                   (withPin testPin2 $ \h ->+                      do udevScriptWait+                         setPinActiveLevel h ActiveLow+                         setPinOutputMode h OutputDefault High+                         liftIO $ threadDelay 250000+                         val1 <- readPin h+                         writePin h Low+                         liftIO $ threadDelay 250000+                         val2 <- readPin h+                         writePin h High+                         liftIO $ threadDelay 250000+                         val3 <- readPin h+                         return (val1, val2, val3))+                   `shouldReturn` (High, Low, High)+              it "writePin obeys the pin's active level" $+                runSysfsGpioIO+                  (withPin testPin1 $ \h1 ->+                     withPin testPin2 $ \h2 ->+                       do udevScriptWait+                          setPinInputMode h1 InputDefault+                          setPinActiveLevel h1 ActiveHigh+                          setPinActiveLevel h2 ActiveLow+                          setPinOutputMode h2 OutputDefault High+                          -- give the pin time to settle+                          liftIO $ threadDelay 250000+                          val1 <- readPin h1+                          writePin h2 Low+                          liftIO $ threadDelay 250000+                          val2 <- readPin h1+                          writePin h2 High+                          liftIO $ threadDelay 250000+                          val3 <- readPin h1+                          return (val1, val2, val3))+                  `shouldReturn` (Low, High, Low)+              it "fails if the pin is configured for input" $+                runSysfsGpioIO+                  (withPin testPin1 $ \h ->+                     do udevScriptWait+                        setPinInputMode h InputDefault+                        writePin h High)+                  `shouldThrow` isPermissionDeniedError+         context "togglePin" $+           -- Note: if these tests fail, you might not have hooked pin+           -- P9-15 up to pin P8-15!+           do it "toggles the pin's value and returns the previous value" $+                runSysfsGpioIO+                  (withPin testPin1 $ \h1 ->+                     withPin testPin2 $ \h2 ->+                       do udevScriptWait+                          setPinInputMode h1 InputDefault+                          setPinActiveLevel h1 ActiveHigh+                          setPinActiveLevel h2 ActiveHigh+                          setPinOutputMode h2 OutputDefault Low+                          h2_val1 <- togglePin h2+                          -- give the pin time to settle+                          liftIO $ threadDelay 250000+                          h1_val1 <- readPin h1+                          h2_val2 <- togglePin h2+                          liftIO $ threadDelay 250000+                          h1_val2 <- readPin h1+                          h2_val3 <- togglePin h2+                          liftIO $ threadDelay 250000+                          h1_val3 <- readPin h1+                          return (h2_val1, h1_val1, h2_val2, h1_val2, h2_val3, h1_val3))+                  `shouldReturn` (High, High, Low, Low, High, High)+              it "togglePin obeys the pin's active level" $+                runSysfsGpioIO+                  (withPin testPin1 $ \h1 ->+                     withPin testPin2 $ \h2 ->+                       do udevScriptWait+                          setPinInputMode h1 InputDefault+                          setPinActiveLevel h1 ActiveHigh+                          setPinActiveLevel h2 ActiveLow+                          setPinOutputMode h2 OutputDefault Low+                          h2_val1 <- togglePin h2+                          -- give the pin time to settle+                          liftIO $ threadDelay 250000+                          h1_val1 <- readPin h1+                          h2_val2 <- togglePin h2+                          liftIO $ threadDelay 250000+                          h1_val2 <- readPin h1+                          h2_val3 <- togglePin h2+                          liftIO $ threadDelay 250000+                          h1_val3 <- readPin h1+                          return (h2_val1, h1_val1, h2_val2, h1_val2, h2_val3, h1_val3))+                  `shouldReturn` (High, Low, Low, High, High, Low)+              it "fails if the pin is configured for input" $+                runSysfsGpioIO+                  (withPin testPin1 $ \h ->+                     do udevScriptWait+                        setPinInputMode h InputDefault+                        void $ togglePin h)+                  `shouldThrow` isPermissionDeniedError+         context "getPinInterruptMode/setPinInterruptMode" $+           do it "gets and sets the pin's interrupt mode" $+                runSysfsGpioIO+                  (withPin testPin1 $ \h ->+                    do udevScriptWait+                       setPinInputMode h InputDefault+                       setPinInterruptMode h RisingEdge+                       trigger1 <- getPinInterruptMode h+                       setPinInterruptMode h FallingEdge+                       trigger2 <- getPinInterruptMode h+                       setPinInterruptMode h Level+                       trigger3 <- getPinInterruptMode h+                       setPinInterruptMode h Disabled+                       trigger4 <- getPinInterruptMode h+                       return (trigger1, trigger2, trigger3, trigger4))+                `shouldReturn` (RisingEdge, FallingEdge, Level, Disabled)+              it "setPinInterruptMode should fail on an output pin" $+                runSysfsGpioIO+                  (withPin testPin2 $ \h ->+                    do udevScriptWait+                       setPinOutputMode h OutputDefault Low+                       setPinInterruptMode h Level)+                `shouldThrow` isInvalidOperationError+         context "pollPin" $+           -- Note: if these tests fail, you might not have hooked pin+           -- P9-15 up to pin P8-15!+           do it "pollPin waits for rising edge" $+                do mvar <- liftIO $ newEmptyMVar+                   void $ liftIO $ forkIO $+                     do runSysfsGpioIO $+                          withPin testPin2 $ \h ->+                            do udevScriptWait+                               setPinActiveLevel h ActiveHigh+                               setPinOutputMode h OutputDefault High+                               liftIO $ void $ takeMVar mvar+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                        putMVar mvar () -- synchronize finish+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinInputMode h InputDefault+                            setPinActiveLevel h ActiveHigh+                            setPinInterruptMode h RisingEdge+                            liftIO $ putMVar mvar ()+                            val1 <- pollPin h+                            val2 <- pollPin h+                            liftIO $ void $ takeMVar mvar -- synchronize finish+                            return (val1, val2))+                   `shouldReturn` (High, High)+              it "pollPin waits for falling edge" $+                do mvar <- liftIO $ newEmptyMVar+                   void $ liftIO $ forkIO $+                     do runSysfsGpioIO $+                          withPin testPin2 $ \h ->+                            do udevScriptWait+                               setPinActiveLevel h ActiveHigh+                               setPinOutputMode h OutputDefault Low+                               liftIO $ void $ takeMVar mvar+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                        putMVar mvar () -- synchronize finish+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinInputMode h InputDefault+                            setPinActiveLevel h ActiveHigh+                            setPinInterruptMode h FallingEdge+                            liftIO $ putMVar mvar ()+                            val1 <- pollPin h+                            val2 <- pollPin h+                            liftIO $ void $ takeMVar mvar -- synchronize finish+                            return (val1, val2))+                   `shouldReturn` (Low, Low)+              it "pollPin waits for level changes" $+                do mvar <- liftIO $ newEmptyMVar+                   void $ liftIO $ forkIO $+                     do runSysfsGpioIO $+                          withPin testPin2 $ \h ->+                            do udevScriptWait+                               setPinActiveLevel h ActiveHigh+                               setPinOutputMode h OutputDefault High+                               liftIO $ void $ takeMVar mvar+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                        putMVar mvar () -- synchronize finish+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinInputMode h InputDefault+                            setPinActiveLevel h ActiveHigh+                            setPinInterruptMode h Level+                            liftIO $ putMVar mvar ()+                            val1 <- pollPin h+                            val2 <- pollPin h+                            val3 <- pollPin h+                            val4 <- pollPin h+                            liftIO $ void $ takeMVar mvar -- synchronize finish+                            return (val1, val2, val3, val4))+                   `shouldReturn` (Low, High, Low, High)+              it "pollPin can be disabled" $+                do mvar <- liftIO $ newEmptyMVar+                   runSysfsGpioIO+                     (withPin testPin1 $ \inPin ->+                        withPin testPin2 $ \outPin ->+                          do udevScriptWait+                             setPinInputMode inPin InputDefault+                             setPinActiveLevel inPin ActiveHigh+                             setPinInterruptMode inPin Disabled+                             setPinActiveLevel outPin ActiveHigh+                             setPinOutputMode outPin OutputDefault Low+                             void $ liftIO $ forkIO $+                               do runSysfsGpioIO $+                                    do liftIO $ void $ takeMVar mvar+                                       liftIO $ threadDelay 500000+                                       void $ togglePin outPin -- ignored+                                       liftIO $ threadDelay 500000+                                       setPinInterruptMode inPin Level+                                       void $ togglePin outPin -- trigger+                                  putMVar mvar () -- synchronize finish+                             liftIO $ putMVar mvar ()+                             val <- pollPin inPin+                             liftIO $ void $ takeMVar mvar -- synchronize finish+                             return val)+                   `shouldReturn` Low+         context "pollPin with active-low logic" $+           -- Note: if these tests fail, you might not have hooked pin+           -- P9-15 up to pin P8-15!+           do it "pollPin waits for (active-low) rising edge" $+                do mvar <- liftIO $ newEmptyMVar+                   void $ liftIO $ forkIO $+                     do runSysfsGpioIO $+                          withPin testPin2 $ \h ->+                            do udevScriptWait+                               setPinActiveLevel h ActiveHigh+                               setPinOutputMode h OutputDefault Low+                               liftIO $ void $ takeMVar mvar+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                        putMVar mvar () -- synchronize finish+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinInputMode h InputDefault+                            setPinActiveLevel h ActiveLow+                            setPinInterruptMode h RisingEdge+                            liftIO $ putMVar mvar ()+                            val1 <- pollPin h+                            val2 <- pollPin h+                            liftIO $ void $ takeMVar mvar -- synchronize finish+                            return (val1, val2))+                   `shouldReturn` (High, High)+              it "pollPin waits for (active-low) falling edge" $+                do mvar <- liftIO $ newEmptyMVar+                   void $ liftIO $ forkIO $+                     do runSysfsGpioIO $+                          withPin testPin2 $ \h ->+                            do udevScriptWait+                               setPinActiveLevel h ActiveHigh+                               setPinOutputMode h OutputDefault High+                               liftIO $ void $ takeMVar mvar+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                        putMVar mvar () -- synchronize finish+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinInputMode h InputDefault+                            setPinActiveLevel h ActiveLow+                            setPinInterruptMode h FallingEdge+                            liftIO $ putMVar mvar ()+                            val1 <- pollPin h+                            val2 <- pollPin h+                            liftIO $ void $ takeMVar mvar -- synchronize finish+                            return (val1, val2))+                   `shouldReturn` (Low, Low)+              it "pollPin waits for level changes" $+                do mvar <- liftIO $ newEmptyMVar+                   void $ liftIO $ forkIO $+                     do runSysfsGpioIO $+                          withPin testPin2 $ \h ->+                            do udevScriptWait+                               setPinActiveLevel h ActiveHigh+                               setPinOutputMode h OutputDefault Low+                               liftIO $ void $ takeMVar mvar+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                        putMVar mvar () -- synchronize finish+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinInputMode h InputDefault+                            setPinActiveLevel h ActiveLow+                            setPinInterruptMode h Level+                            liftIO $ putMVar mvar ()+                            val1 <- pollPin h+                            val2 <- pollPin h+                            val3 <- pollPin h+                            val4 <- pollPin h+                            liftIO $ void $ takeMVar mvar -- synchronize finish+                            return (val1, val2, val3, val4))+                   `shouldReturn` (Low, High, Low, High)+              it "pollPin can be disabled" $+                do mvar <- liftIO $ newEmptyMVar+                   runSysfsGpioIO+                     (withPin testPin1 $ \inPin ->+                        withPin testPin2 $ \outPin ->+                          do udevScriptWait+                             setPinInputMode inPin InputDefault+                             setPinActiveLevel inPin ActiveLow+                             setPinInterruptMode inPin Disabled+                             setPinActiveLevel outPin ActiveHigh+                             setPinOutputMode outPin OutputDefault Low+                             void $ liftIO $ forkIO $+                               do runSysfsGpioIO $+                                    do liftIO $ void $ takeMVar mvar+                                       liftIO $ threadDelay 500000+                                       void $ togglePin outPin -- ignored+                                       liftIO $ threadDelay 500000+                                       setPinInterruptMode inPin Level+                                       void $ togglePin outPin -- trigger+                                  putMVar mvar () -- synchronize finish+                             liftIO $ putMVar mvar ()+                             val <- pollPin inPin+                             liftIO $ void $ takeMVar mvar -- synchronize finish+                             return val)+                   `shouldReturn` High+         context "pollPinTimeout" $+           -- Note: if these tests fail, you might not have hooked pin+           -- P9-15 up to pin P8-15!+           do it "pollPinTimeout waits for rising edge" $+                do mvar <- liftIO $ newEmptyMVar+                   void $ liftIO $ forkIO $+                     do runSysfsGpioIO $+                          withPin testPin2 $ \h ->+                            do udevScriptWait+                               setPinActiveLevel h ActiveHigh+                               setPinOutputMode h OutputDefault High+                               liftIO $ void $ takeMVar mvar+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                        putMVar mvar () -- synchronize finish+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinInputMode h InputDefault+                            setPinActiveLevel h ActiveHigh+                            setPinInterruptMode h RisingEdge+                            liftIO $ putMVar mvar ()+                            val1 <- pollPinTimeout h 10000000+                            val2 <- pollPinTimeout h 10000000+                            liftIO $ void $ takeMVar mvar -- synchronize finish+                            return (val1, val2))+                   `shouldReturn` (Just High, Just High)+              it "pollPinTimeout waits for falling edge" $+                do mvar <- liftIO $ newEmptyMVar+                   void $ liftIO $ forkIO $+                     do runSysfsGpioIO $+                          withPin testPin2 $ \h ->+                            do udevScriptWait+                               setPinActiveLevel h ActiveHigh+                               setPinOutputMode h OutputDefault Low+                               liftIO $ void $ takeMVar mvar+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                        putMVar mvar () -- synchronize finish+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinInputMode h InputDefault+                            setPinActiveLevel h ActiveHigh+                            setPinInterruptMode h FallingEdge+                            liftIO $ putMVar mvar ()+                            val1 <- pollPinTimeout h 10000000+                            val2 <- pollPinTimeout h 10000000+                            liftIO $ void $ takeMVar mvar -- synchronize finish+                            return (val1, val2))+                   `shouldReturn` (Just Low, Just Low)+              it "pollPinTimeout waits for level changes" $+                do mvar <- liftIO $ newEmptyMVar+                   void $ liftIO $ forkIO $+                     do runSysfsGpioIO $+                          withPin testPin2 $ \h ->+                            do udevScriptWait+                               setPinActiveLevel h ActiveHigh+                               setPinOutputMode h OutputDefault High+                               liftIO $ void $ takeMVar mvar+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                        putMVar mvar () -- synchronize finish+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinInputMode h InputDefault+                            setPinActiveLevel h ActiveHigh+                            setPinInterruptMode h Level+                            liftIO $ putMVar mvar ()+                            val1 <- pollPinTimeout h 10000000+                            val2 <- pollPinTimeout h 10000000+                            val3 <- pollPinTimeout h 10000000+                            val4 <- pollPinTimeout h 10000000+                            liftIO $ void $ takeMVar mvar -- synchronize finish+                            return (val1, val2, val3, val4))+                   `shouldReturn` (Just Low, Just High, Just Low, Just High)+              it "pollPinTimeout can be disabled" $+                do mvar <- liftIO $ newEmptyMVar+                   runSysfsGpioIO+                     (withPin testPin1 $ \inPin ->+                        withPin testPin2 $ \outPin ->+                          do udevScriptWait+                             setPinInputMode inPin InputDefault+                             setPinActiveLevel inPin ActiveHigh+                             setPinInterruptMode inPin Disabled+                             setPinActiveLevel outPin ActiveHigh+                             setPinOutputMode outPin OutputDefault Low+                             void $ liftIO $ forkIO $+                               do runSysfsGpioIO $+                                    do liftIO $ void $ takeMVar mvar+                                       liftIO $ threadDelay 500000+                                       void $ togglePin outPin -- ignored+                                       liftIO $ threadDelay 500000+                                       setPinInterruptMode inPin Level+                                       void $ togglePin outPin -- trigger+                                  putMVar mvar () -- synchronize finish+                             liftIO $ putMVar mvar ()+                             val <- pollPinTimeout inPin 10000000+                             liftIO $ void $ takeMVar mvar -- synchronize finish+                             return val)+                   `shouldReturn` Just Low+              it "pollPinTimeout actually times out" $+                 do mvar <- liftIO $ newEmptyMVar+                    runSysfsGpioIO+                      (withPin testPin1 $ \inPin ->+                         withPin testPin2 $ \outPin ->+                           do udevScriptWait+                              setPinInputMode inPin InputDefault+                              setPinActiveLevel inPin ActiveHigh+                              setPinInterruptMode inPin Disabled+                              setPinActiveLevel outPin ActiveHigh+                              setPinOutputMode outPin OutputDefault Low+                              void $ liftIO $ forkIO $+                                do runSysfsGpioIO $+                                     do liftIO $ void $ takeMVar mvar+                                        liftIO $ threadDelay 500000+                                        void $ togglePin outPin -- ignored+                                        liftIO $ threadDelay 500000+                                        void $ togglePin outPin -- ignored+                                   putMVar mvar () -- synchronize finish+                              liftIO $ putMVar mvar ()+                              val <- pollPinTimeout inPin 1000000+                              liftIO $ void $ takeMVar mvar -- synchronize finish+                              return val)+                    `shouldReturn` Nothing+              it "pollPinTimeout times out on output pins" $+                 do runSysfsGpioIO+                      (withPin testPin2 $ \outPin ->+                        do udevScriptWait+                           setPinActiveLevel outPin ActiveHigh+                           setPinOutputMode outPin OutputDefault Low+                           val <- pollPinTimeout outPin 1000000+                           return val)+                    `shouldReturn` Nothing+         context "pollPinTimeout with active-low logic" $+           -- Note: if these tests fail, you might not have hooked pin+           -- P9-15 up to pin P8-15!+           do it "pollPinTimeout waits for (active-low) rising edge" $+                do mvar <- liftIO $ newEmptyMVar+                   void $ liftIO $ forkIO $+                     do runSysfsGpioIO $+                          withPin testPin2 $ \h ->+                            do udevScriptWait+                               setPinActiveLevel h ActiveHigh+                               setPinOutputMode h OutputDefault Low+                               liftIO $ void $ takeMVar mvar+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                        putMVar mvar () -- synchronize finish+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinInputMode h InputDefault+                            setPinActiveLevel h ActiveLow+                            setPinInterruptMode h RisingEdge+                            liftIO $ putMVar mvar ()+                            val1 <- pollPinTimeout h 10000000+                            val2 <- pollPinTimeout h 10000000+                            liftIO $ void $ takeMVar mvar -- synchronize finish+                            return (val1, val2))+                   `shouldReturn` (Just High, Just High)+              it "pollPinTimeout waits for (active-low) falling edge" $+                do mvar <- liftIO $ newEmptyMVar+                   void $ liftIO $ forkIO $+                     do runSysfsGpioIO $+                          withPin testPin2 $ \h ->+                            do udevScriptWait+                               setPinActiveLevel h ActiveHigh+                               setPinOutputMode h OutputDefault High+                               liftIO $ void $ takeMVar mvar+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                        putMVar mvar () -- synchronize finish+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinInputMode h InputDefault+                            setPinActiveLevel h ActiveLow+                            setPinInterruptMode h FallingEdge+                            liftIO $ putMVar mvar ()+                            val1 <- pollPinTimeout h 10000000+                            val2 <- pollPinTimeout h 10000000+                            liftIO $ void $ takeMVar mvar -- synchronize finish+                            return (val1, val2))+                   `shouldReturn` (Just Low, Just Low)+              it "pollPinTimeout waits for level changes" $+                do mvar <- liftIO $ newEmptyMVar+                   void $ liftIO $ forkIO $+                     do runSysfsGpioIO $+                          withPin testPin2 $ \h ->+                            do udevScriptWait+                               setPinActiveLevel h ActiveHigh+                               setPinOutputMode h OutputDefault Low+                               liftIO $ void $ takeMVar mvar+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                               liftIO $ threadDelay 500000+                               void $ togglePin h -- trigger+                        putMVar mvar () -- synchronize finish+                   runSysfsGpioIO+                      (withPin testPin1 $ \h ->+                         do udevScriptWait+                            setPinInputMode h InputDefault+                            setPinActiveLevel h ActiveLow+                            setPinInterruptMode h Level+                            liftIO $ putMVar mvar ()+                            val1 <- pollPinTimeout h 10000000+                            val2 <- pollPinTimeout h 10000000+                            val3 <- pollPinTimeout h 10000000+                            val4 <- pollPinTimeout h 10000000+                            liftIO $ void $ takeMVar mvar -- synchronize finish+                            return (val1, val2, val3, val4))+                   `shouldReturn` (Just Low, Just High, Just Low, Just High)+              it "pollPinTimeout can be disabled" $+                do mvar <- liftIO $ newEmptyMVar+                   runSysfsGpioIO+                     (withPin testPin1 $ \inPin ->+                        withPin testPin2 $ \outPin ->+                          do udevScriptWait+                             setPinInputMode inPin InputDefault+                             setPinActiveLevel inPin ActiveLow+                             setPinInterruptMode inPin Disabled+                             setPinActiveLevel outPin ActiveHigh+                             setPinOutputMode outPin OutputDefault Low+                             void $ liftIO $ forkIO $+                               do runSysfsGpioIO $+                                    do liftIO $ void $ takeMVar mvar+                                       liftIO $ threadDelay 500000+                                       void $ togglePin outPin -- ignored+                                       liftIO $ threadDelay 500000+                                       setPinInterruptMode inPin Level+                                       void $ togglePin outPin -- trigger+                                  putMVar mvar () -- synchronize finish+                             liftIO $ putMVar mvar ()+                             val <- pollPinTimeout inPin 10000000+                             liftIO $ void $ takeMVar mvar -- synchronize finish+                             return val)+                   `shouldReturn` Just High+         context "Various NotExported exceptions" $+           do it "getPinDirection" $+                runSysfsGpioIO+                  (do h <- openPin testPin1+                      closePin h+                      v <- getPinDirection h+                      return v)+                  `shouldThrow` isNotExportedError+              it "getPinInputMode" $+                  runSysfsGpioIO+                    (do h <- openPin testPin1+                        closePin h+                        getPinInputMode h)+                    `shouldThrow` isNotExportedError+              it "setPinInputMode" $+                 runSysfsGpioIO+                   (do h <- openPin testPin1+                       closePin h+                       setPinInputMode h InputDefault)+                   `shouldThrow` isNotExportedError+              it "getPinOutputMode" $+                   runSysfsGpioIO+                     (do h <- openPin testPin1+                         closePin h+                         getPinOutputMode h)+                     `shouldThrow` isNotExportedError+              it "setPinOutputMode" $+                  runSysfsGpioIO+                    (do h <- openPin testPin1+                        closePin h+                        setPinOutputMode h OutputDefault Low)+                    `shouldThrow` isNotExportedError+              it "getPinInterruptMode" $+                runSysfsGpioIO+                  (do h <- openPin testPin1+                      closePin h+                      v <- getPinInterruptMode h+                      return v)+                  `shouldThrow` isNotExportedError+              it "setPinInterruptMode" $+                 runSysfsGpioIO+                   (do h <- openPin testPin1+                       closePin h+                       setPinInterruptMode h RisingEdge)+                   `shouldThrow` isNotExportedError+              it "getPinActiveLevel" $+                runSysfsGpioIO+                  (do h <- openPin testPin1+                      closePin h+                      v <- getPinActiveLevel h+                      return v)+                  `shouldThrow` isNotExportedError+              it "setPinActiveLevel" $+                runSysfsGpioIO+                  (do h <- openPin testPin1+                      closePin h+                      setPinActiveLevel h ActiveHigh)+                  `shouldThrow` isNotExportedError+              it "toggleActiveLevel" $+                 runSysfsGpioIO+                   (do h <- openPin testPin1+                       closePin h+                       v <- togglePinActiveLevel h+                       return v)+                   `shouldThrow` isNotExportedError+              it "readPin" $+                runSysfsGpioIO+                  (do h <- openPin testPin1+                      closePin h+                      v <- readPin h+                      return v)+                  `shouldThrow` isNotExportedError+              it "pollPin" $+                runSysfsGpioIO+                  (do h <- openPin testPin1+                      closePin h+                      v <- pollPin h+                      return v)+                  `shouldThrow` isNotExportedError+              it "pollPinTimeout" $+                runSysfsGpioIO+                  (do h <- openPin testPin1+                      closePin h+                      v <- pollPinTimeout h 100000+                      return v)+                  `shouldThrow` isNotExportedError+              it "writePin" $+                runSysfsGpioIO+                  (do h <- openPin testPin1+                      closePin h+                      writePin h High)+                  `shouldThrow` isNotExportedError+              it "togglePin" $+                runSysfsGpioIO+                  (do h <- openPin testPin1+                      closePin h+                      v <- togglePin h+                      return v)+                  `shouldThrow` isNotExportedError
+ test/System/GPIO/Linux/Sysfs/MockInternalSpec.hs view
@@ -0,0 +1,328 @@+{-# OPTIONS_GHC -fno-warn-incomplete-patterns -fno-warn-incomplete-uni-patterns #-}+{-# LANGUAGE OverloadedStrings #-}++module System.GPIO.Linux.Sysfs.MockInternalSpec (spec) where++import GHC.IO.Exception (IOErrorType(..))+import System.GPIO.Linux.Sysfs.Mock.Internal+import System.IO.Error (ioeGetErrorType, isAlreadyExistsError, isDoesNotExistError)+import Test.Hspec++isInvalidArgumentErrorType :: IOErrorType -> Bool+isInvalidArgumentErrorType InvalidArgument = True+isInvalidArgumentErrorType _ = False++isInvalidArgumentError :: IOError -> Bool+isInvalidArgumentError = isInvalidArgumentErrorType . ioeGetErrorType++isInappropriateTypeErrorType :: IOErrorType -> Bool+isInappropriateTypeErrorType InappropriateType = True+isInappropriateTypeErrorType _ = False++isInappropriateTypeError :: IOError -> Bool+isInappropriateTypeError = isInappropriateTypeErrorType . ioeGetErrorType++sysfsRoot :: Directory+sysfsRoot =+  directory "/"+            []+            [directory "sys"+                       []+                       [directory "class"+                                  []+                                  [directory "gpio"+                                             [File "export" Export+                                             ,File "unexport" Unexport]+                                             []]]]++parentName :: MockFSCrumb -> Name+parentName = _dirNodeName . _node++spec :: Spec+spec =+  let sysfsRootZ = MockFSZipper sysfsRoot []+  in+    do describe "cd" $ do++         context "relative paths" $ do++           it "can traverse downwards one directory at a time" $+             do let Right z1@(MockFSZipper dir1 (crumb1:_)) = cd "sys" sysfsRootZ+                dirName dir1 `shouldBe` "sys"+                parentName crumb1 `shouldBe` "/"+                let Right z2@(MockFSZipper dir2 (crumb2:_)) = cd "class" z1+                dirName dir2 `shouldBe` "class"+                parentName crumb2 `shouldBe` "sys"+                let Right (MockFSZipper dir3 (crumb3:_)) = cd "gpio" z2+                dirName dir3 `shouldBe` "gpio"+                parentName crumb3 `shouldBe` "class"++           it "can traverse downwards multiple directories at a time" $+             do let Right (MockFSZipper dir1 (crumb1:_)) = cd "sys/class/gpio" sysfsRootZ+                dirName dir1 `shouldBe` "gpio"+                parentName crumb1 `shouldBe` "class"++           it "fails when changing to a non-existent child" $+             do let Left result1 = cd "foobar" sysfsRootZ+                isDoesNotExistError result1 `shouldBe` True+                let Left result2 = (cd "sys/class" sysfsRootZ >>= cd "baz" )+                isDoesNotExistError result2 `shouldBe` True++           it "fails when changing to a non-existent grandchild" $+             do let Left result = cd "sys/class/foobar" sysfsRootZ+                isDoesNotExistError result `shouldBe` True++           it "fails when changing to a file name rather than a directory name" $+             do let Left result1 = (cd "sys/class/gpio" sysfsRootZ >>= cd "export")+                isInappropriateTypeError result1 `shouldBe` True+                let Left result2 = (cd "sys/class/gpio" sysfsRootZ >>= cd "export/foobar")+                isInappropriateTypeError result2 `shouldBe` True+                let Left result3 = cd "sys/class/gpio/unexport" sysfsRootZ+                isInappropriateTypeError result3 `shouldBe` True+                let Left result4 = cd "sys/class/gpio/unexport/baz" sysfsRootZ+                isInappropriateTypeError result4 `shouldBe` True++           it "'.' in paths" $+             do cd "sys/." sysfsRootZ `shouldBe` cd "sys" sysfsRootZ+                cd "sys/./class" sysfsRootZ `shouldBe` cd "sys/class" sysfsRootZ++           it "'..' in paths" $+             do cd "sys/.." sysfsRootZ `shouldBe` Right sysfsRootZ+                cd "sys/class/../class" sysfsRootZ `shouldBe` cd "sys/class" sysfsRootZ+                cd "sys/class/gpio/../../class" sysfsRootZ `shouldBe` cd "sys/../sys/class/../class/gpio/.." sysfsRootZ+                cd "sys/class/gpio/../../.." sysfsRootZ `shouldBe` Right sysfsRootZ++           it "'..' beyond root clamps to root" $+             do cd "sys/class/../../../.." sysfsRootZ `shouldBe` Right sysfsRootZ+                cd "../.." sysfsRootZ `shouldBe` Right sysfsRootZ++         context "absolute paths" $ do+           it "can traverse downwards one directory at a time" $+             do let Right z1@(MockFSZipper dir1 (crumb1:_)) = cd "/sys" sysfsRootZ+                dirName dir1 `shouldBe` "sys"+                parentName crumb1 `shouldBe` "/"+                let Right z2@(MockFSZipper dir2 (crumb2:_)) = cd "class" z1+                dirName dir2 `shouldBe` "class"+                parentName crumb2 `shouldBe` "sys"+                let Right (MockFSZipper dir3 (crumb3:_)) = cd "gpio" z2+                dirName dir3 `shouldBe` "gpio"+                parentName crumb3 `shouldBe` "class"+           it "can traverse downwards multiple directories at a time" $+             do let Right (MockFSZipper dir1 (crumb1:_)) = cd "/sys/class/gpio" sysfsRootZ+                dirName dir1 `shouldBe` "gpio"+                parentName crumb1 `shouldBe` "class"+           it "fails when changing to a non-existent child" $+             do let Left result = cd "/foobar" sysfsRootZ+                isDoesNotExistError result `shouldBe` True+           it "fails when changing to a non-existent grandchild" $+             do let Left result = cd "/sys/class/foobar" sysfsRootZ+                isDoesNotExistError result `shouldBe` True+           it "fails when changing to a file name rather than a directory name" $+             do let Left result1 = cd "/sys/class/gpio/export" sysfsRootZ+                isInappropriateTypeError result1 `shouldBe` True+                let Left result2 = cd "/sys/class/gpio/unexport/baz" sysfsRootZ+                isInappropriateTypeError result2 `shouldBe` True++           it "cd / is root" $+             cd "/" sysfsRootZ `shouldBe` Right sysfsRootZ++           it "cd / from deeper starts at root" $ do+             (cd "/sys/class/gpio" sysfsRootZ >>= cd "/") `shouldBe` Right sysfsRootZ+             (cd "/sys/class/gpio" sysfsRootZ >>= cd "/sys/class") `shouldBe` cd "/sys/class" sysfsRootZ++           it "'.' in paths" $+             do cd "/sys/." sysfsRootZ `shouldBe` cd "/sys" sysfsRootZ+                cd "/sys/./class" sysfsRootZ `shouldBe` cd "/sys/class" sysfsRootZ+           it "'..' in paths" $+             do cd "/sys/.." sysfsRootZ `shouldBe` Right sysfsRootZ+                cd "/sys/class/../class" sysfsRootZ `shouldBe` cd "/sys/class" sysfsRootZ+                cd "/sys/class/gpio/../../class" sysfsRootZ `shouldBe` cd "/sys/../sys/class/../class/gpio/.." sysfsRootZ+                cd "/sys/class/gpio/../../.." sysfsRootZ `shouldBe` Right sysfsRootZ+           it "'..' beyond root clamps to root" $+             do cd "/sys/class/../../../.." sysfsRootZ `shouldBe` Right sysfsRootZ+                cd "/../.." sysfsRootZ `shouldBe` Right sysfsRootZ++         context "absolute and relative paths" $ do+           it "produce the same result when they lead to the same directory" $+             do cd "sys/class/gpio" sysfsRootZ `shouldBe` cd "/sys/class/gpio" sysfsRootZ+                cd "sys/class/gpio/.." sysfsRootZ `shouldBe` cd "/sys/class/gpio/.." sysfsRootZ+                cd "sys/class/gpio/../../." sysfsRootZ `shouldBe` cd "/sys/class/gpio/../../." sysfsRootZ+                cd "sys/class/.././class/../../sys" sysfsRootZ `shouldBe` cd "/sys/../sys/class/./gpio/../.." sysfsRootZ++       describe "mkdir" $+         do it "creates a subdirectory in the current directory" $+              do let Right z1@(MockFSZipper dir1 crumb1) = mkdir "xyzzy" sysfsRootZ+                 dirName dir1 `shouldBe` "/"+                 crumb1 `shouldBe` []+                 let Right (MockFSZipper dir2 (crumb2:_)) = cd "xyzzy" z1+                 dirName dir2 `shouldBe` "xyzzy"+                 parentName crumb2 `shouldBe` "/"++            it "can create multiple subdirectories in the same directory" $+              do let Right z1@(MockFSZipper dir1 crumb1) = mkdir "xyzzy" sysfsRootZ+                 dirName dir1 `shouldBe` "/"+                 crumb1 `shouldBe` []+                 let Right z2@(MockFSZipper dir2 crumb2) = mkdir "plugh" z1+                 dirName dir2 `shouldBe` "/"+                 crumb2 `shouldBe` []+                 let Right z3@(MockFSZipper dir3 (crumb3:_)) = cd "xyzzy" z2+                 dirName dir3 `shouldBe` "xyzzy"+                 parentName crumb3 `shouldBe` "/"+                 let Right (MockFSZipper dir4 (crumb4:_)) = cd "../plugh" z3+                 dirName dir4 `shouldBe` "plugh"+                 parentName crumb4 `shouldBe` "/"++            it "works when nested" $+              do let Right (MockFSZipper dir (crumb:_)) = mkdir "abc" sysfsRootZ >>= cd "/abc" >>= mkdir "def" >>= cd "/abc/def"+                 dirName dir `shouldBe` "def"+                 parentName crumb `shouldBe` "abc"++            it "fails when a subdir with the same name already exists" $+              do let Left result = mkdir "sys" sysfsRootZ+                 isAlreadyExistsError result `shouldBe` True++            it "fails when a file with the same name already exists" $+              do let Left result = (cd "/sys/class/gpio" sysfsRootZ >>= mkdir "export")+                 isAlreadyExistsError result `shouldBe` True++            it "fails with an invalid name" $+              do let Left result = mkdir "" sysfsRootZ+                 isInvalidArgumentError result `shouldBe` True++            it "fails when the name contains a '/'" $+              do let Left result1 = mkdir "/abc" sysfsRootZ+                 isInvalidArgumentError result1 `shouldBe` True+                 let Left result2 = mkdir "sys/foobar" sysfsRootZ+                 isInvalidArgumentError result2 `shouldBe` True++       describe "mkfile" $+         do it "creates a file in the current directory when clobber is False" $+              do let Right z1 = cd "/sys/class/gpio" sysfsRootZ+                 let Right (MockFSZipper dir2 (crumb2:_)) = mkfile "gpio1" (Const ["Hey!", "This is gpio1"]) False z1+                 dirName dir2 `shouldBe` "gpio"+                 parentName crumb2 `shouldBe` "class"+                 let file:rest = files dir2+                 _fileName file `shouldBe` "gpio1"+                 _fileType file `shouldBe` (Const ["Hey!", "This is gpio1"])+                 rest `shouldBe` [File {_fileName = "export", _fileType = Export},File {_fileName = "unexport", _fileType = Unexport}]++            it "creates a file in the current directory when clobber is True" $+              do let Right z1 = cd "/sys/class/gpio" sysfsRootZ+                 let Right (MockFSZipper dir2 (crumb2:_)) = mkfile "gpio1" (Const ["Hey!", "This is gpio1"]) True z1+                 dirName dir2 `shouldBe` "gpio"+                 parentName crumb2 `shouldBe` "class"+                 let file:rest = files dir2+                 _fileName file `shouldBe` "gpio1"+                 _fileType file `shouldBe` (Const ["Hey!", "This is gpio1"])+                 rest `shouldBe` [File {_fileName = "export", _fileType = Export},File {_fileName = "unexport", _fileType = Unexport}]++            it "fails when a subdir with the same name already exists" $ do+              let Left result1 = mkfile "sys" (Const []) True sysfsRootZ+              isAlreadyExistsError result1 `shouldBe` True+              let Left result2 = mkfile "sys" (Const []) False sysfsRootZ+              isAlreadyExistsError result2 `shouldBe` True++            it "fails when a file with the same name already exists and clobber is False" $+              do let Left result = (cd "/sys/class/gpio" sysfsRootZ >>= mkfile "export" (Const []) False)+                 isAlreadyExistsError result `shouldBe` True++            it "overwrites an existing file's contents when a file with the same name already exists and clobber is True" $+              do let Right z1 = cd "/sys/class/gpio" sysfsRootZ+                 let Right z2 = mkfile "gpio1" (Const ["Hey!", "This is gpio1"]) False z1+                 let Right (MockFSZipper dir3 (crumb3:_)) = mkfile "gpio1" (Const ["Hey!", "Now I'm gpio1"]) True z2+                 dirName dir3 `shouldBe` "gpio"+                 parentName crumb3 `shouldBe` "class"+                 let file:rest = files dir3+                 _fileName file `shouldBe` "gpio1"+                 _fileType file `shouldBe` (Const ["Hey!", "Now I'm gpio1"])+                 rest `shouldBe` [File {_fileName = "export", _fileType = Export},File {_fileName = "unexport", _fileType = Unexport}]++            it "fails with an invalid name" $ do+              let Left result1 = mkfile "" (Const []) False sysfsRootZ+              isInvalidArgumentError result1 `shouldBe` True+              let Left result2 = mkfile "" (Const []) True sysfsRootZ+              isInvalidArgumentError result2 `shouldBe` True++            it "fails when the name contains a '/'" $+              do let Left result1 = mkfile "/abc" (Const []) False sysfsRootZ+                 isInvalidArgumentError result1 `shouldBe` True+                 let Left result2 = mkfile "/abc" (Const []) True sysfsRootZ+                 isInvalidArgumentError result2 `shouldBe` True+                 let Left result3 = mkfile "sys/foobar" (Const []) False sysfsRootZ+                 isInvalidArgumentError result3 `shouldBe` True+                 let Left result4 = mkfile "sys/foobar" (Const []) True sysfsRootZ+                 isInvalidArgumentError result4 `shouldBe` True++       describe "findFile" $+         do it "finds files in the current directory" $+              do let Right z1@(MockFSZipper dir1 _) = cd "/sys/class/gpio" sysfsRootZ+                 findFile "export" dir1 `shouldBe` (Just Export)+                 findFile "unexport" dir1 `shouldBe` (Just Unexport)+                 let Right (MockFSZipper dir2 _) = mkfile "gpio1" (Const ["Hey!", "This is gpio1"]) False z1+                 findFile "gpio1" dir2 `shouldBe` (Just ((Const ["Hey!", "This is gpio1"])))+            it "doesn't find subdirectories in the current directory" $+              do let Right (MockFSZipper dir1 _) = cd "/sys/class" sysfsRootZ+                 findFile "gpio" dir1 `shouldBe` Nothing+            it "returns failure on non-existent files" $+              do let Right (MockFSZipper dir1 _) = cd "/sys/class" sysfsRootZ+                 findFile "foobar" dir1 `shouldBe` Nothing+                 findFile "export" dir1 `shouldBe` Nothing+            it "doesn't find files in subdirectories of the current directory" $+              do let Right (MockFSZipper dir1 _) = cd "/sys/class" sysfsRootZ+                 findFile "gpio/export" dir1 `shouldBe` Nothing++       describe "rmdir" $+         do it "removes a subdirectory of the current directory" $+              do let Right z1 = cd "/sys" sysfsRootZ >>= mkdir "xyzzy" >>= mkdir "plugh"+                 let Right z2@(MockFSZipper dir2 (crumb2:_)) = rmdir "xyzzy" z1+                 dirName dir2 `shouldBe` "sys"+                 parentName crumb2 `shouldBe` "/"+                 let Right (MockFSZipper dir3 (crumb3:_)) = rmdir "plugh" z2+                 dirName dir3 `shouldBe` "sys"+                 parentName crumb3 `shouldBe` "/"++            it "fails when no subdir with the name exists" $+              do let Left result = rmdir "foo" sysfsRootZ+                 isDoesNotExistError result `shouldBe` True++            it "fails when a file is named" $+              do let Left result = (cd "/sys/class/gpio" sysfsRootZ >>= rmdir "export")+                 isInappropriateTypeError result `shouldBe` True++            it "fails when the name contains a '/'" $+              do let Left result1 = rmdir "/sys" sysfsRootZ+                 isDoesNotExistError result1 `shouldBe` True+                 let Left result2 = rmdir "sys/class" sysfsRootZ+                 isDoesNotExistError result2 `shouldBe` True++       describe "rmfile" $+         do it "removes a file in the current directory" $+              do let Right z1 = cd "/sys" sysfsRootZ >>= mkfile "abc" (Const []) False >>= mkfile "def" (Const []) False+                 let Right z2@(MockFSZipper dir2 (crumb2:_)) = rmfile "abc" z1+                 dirName dir2 `shouldBe` "sys"+                 parentName crumb2 `shouldBe` "/"+                 files dir2 `shouldBe` [File {_fileName = "def", _fileType = (Const [])}]+                 let Right (MockFSZipper dir3 (crumb3:_)) = rmfile "def" z2+                 dirName dir3 `shouldBe` "sys"+                 parentName crumb3 `shouldBe` "/"+                 files dir3 `shouldBe` []++            it "fails when no file with the name exists" $+              do let Left result = rmfile "foo" sysfsRootZ+                 isDoesNotExistError result `shouldBe` True++            it "fails when a directory is named" $+              do let Left result = (cd "/sys/class" sysfsRootZ >>= rmfile "gpio")+                 isInappropriateTypeError result `shouldBe` True++            it "fails when the name contains a '/'" $+              do let Left result = rmfile "/sys/class/gpio/export" sysfsRootZ+                 isDoesNotExistError result `shouldBe` True++       describe "pathFromRoot" $+         do it "returns the path from the current directory to the root directory" $+              do pathFromRoot sysfsRootZ `shouldBe` "/"+                 let Right z1 = cd "/sys/class/gpio" sysfsRootZ+                 pathFromRoot z1 `shouldBe` "/sys/class/gpio"+                 let Right z2 = cd "/sys/class/gpio/../../class" sysfsRootZ+                 pathFromRoot z2 `shouldBe` "/sys/class"
+ test/System/GPIO/Linux/Sysfs/MockSpec.hs view
@@ -0,0 +1,138 @@+{-# OPTIONS_GHC -fno-warn-incomplete-patterns -fno-warn-incomplete-uni-patterns #-}+{-# LANGUAGE OverloadedStrings #-}++module System.GPIO.Linux.Sysfs.MockSpec (spec) where++import Prelude hiding (readFile, writeFile)+import Control.Exception (fromException)+import Data.List (sort)+import GHC.IO.Exception (IOErrorType(..))+import System.GPIO.Linux.Sysfs.Mock+import System.GPIO.Types (Pin(..), PinValue(..))+import System.IO.Error (ioeGetErrorType, isDoesNotExistError, isPermissionError)+import Test.Hspec++isInappropriateTypeErrorType :: IOErrorType -> Bool+isInappropriateTypeErrorType InappropriateType = True+isInappropriateTypeErrorType _ = False++isInappropriateTypeError :: IOError -> Bool+isInappropriateTypeError = isInappropriateTypeErrorType . ioeGetErrorType++evalSysfsMock' :: SysfsMock a -> MockWorld -> [MockGpioChip] -> Either (Maybe IOError) a+evalSysfsMock' a w c = either (Left . fromException) Right $ evalSysfsMock a w c++evalSysfsMockME :: SysfsMock a -> MockWorld -> [MockGpioChip] -> Either (Maybe MockFSException) a+evalSysfsMockME a w c = either (Left . fromException) Right $ evalSysfsMock a w c++execSysfsMock' :: SysfsMock a -> MockWorld -> [MockGpioChip] -> Either (Maybe IOError) MockWorld+execSysfsMock' a w c = either (Left . fromException) Right $ execSysfsMock a w c++spec :: Spec+spec =+  do+    describe "MockPinState" $ do+      it "logicalValue returns the correct pin value" $+        let pinState = defaultMockPinState {_value = Low, _activeLow = False}+        in do+          logicalValue pinState `shouldBe` Low+          logicalValue (pinState {_value = High}) `shouldBe` High+          logicalValue (pinState {_activeLow = True}) `shouldBe` High+          logicalValue (pinState {_value = High, _activeLow = True}) `shouldBe` Low+      it "setLogicalValue sets the correct pin value" $+        let pinState = defaultMockPinState {_value = Low, _activeLow = False}+            activeLowPinState = defaultMockPinState {_value = Low, _activeLow = True}+        in do+          setLogicalValue Low pinState `shouldBe` pinState+          setLogicalValue High pinState `shouldBe` pinState {_value = High}+          setLogicalValue Low activeLowPinState `shouldBe` activeLowPinState {_value = High}+          setLogicalValue High activeLowPinState `shouldBe` activeLowPinState {_value = Low}++    describe "SysfsMockT" $ do++      context "doesDirectoryExist" $ do+        it "relative paths are relative to the initial zipper's working directory" $ do+          evalSysfsMock' (doesDirectoryExist "sys/class/gpio") initialMockWorld [] `shouldBe` Right True+        it "absolute paths work regardless of the initial zipper's working directory" $ do+          evalSysfsMock' (doesDirectoryExist "/sys/class/gpio") initialMockWorld [] `shouldBe` Right True+        it "doesn't change the initial zipper's state" $ do+          execSysfsMock' (doesDirectoryExist "/sys/class/gpio") initialMockWorld [] `shouldBe` Right initialMockWorld+        it "returns False on files" $ do+          evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/export") initialMockWorld [] `shouldBe` Right False+        it "returns False on non-existent names" $ do+          evalSysfsMock' (doesDirectoryExist "/sys/class/foobar") initialMockWorld [] `shouldBe` Right False++      context "doesFileExist" $ do+        it "relative paths are relative to the initial zipper's working directory" $ do+          evalSysfsMock' (doesFileExist "sys/class/gpio/export") initialMockWorld [] `shouldBe` Right True+        it "absolute paths work regardless of the initial zipper's working directory" $ do+          evalSysfsMock' (doesFileExist "/sys/class/gpio/export") initialMockWorld [] `shouldBe` Right True+        it "doesn't change the initial zipper's state" $ do+          execSysfsMock' (doesFileExist "/sys/class/gpio/export") initialMockWorld [] `shouldBe` Right initialMockWorld+        it "returns False on directories" $ do+          evalSysfsMock' (doesFileExist "/sys/class/gpio") initialMockWorld [] `shouldBe` Right False+        it "returns False on non-existent names" $ do+          evalSysfsMock' (doesFileExist "/sys/class/foobar") initialMockWorld [] `shouldBe` Right False++      context "getDirectoryContents" $ do+        it "relative paths are relative to the initial zipper's working directory" $ do+          fmap sort (evalSysfsMock' (getDirectoryContents "sys/class") initialMockWorld []) `shouldBe` Right ["gpio"]+          fmap sort (evalSysfsMock' (getDirectoryContents "sys/class/gpio") initialMockWorld []) `shouldBe` (Right $ sort ["export", "unexport"])+        it "absolute paths work regardless of the initial zipper's working directory" $ do+          fmap sort (evalSysfsMock' (getDirectoryContents "/sys/class") initialMockWorld []) `shouldBe` Right ["gpio"]+          fmap sort (evalSysfsMock' (getDirectoryContents "/sys/class/gpio") initialMockWorld []) `shouldBe` (Right $ sort ["export", "unexport"])+        it "doesn't change the initial zipper's state" $ do+          execSysfsMock' (getDirectoryContents "/sys/class/gpio") initialMockWorld [] `shouldBe` Right initialMockWorld+        it "returns failure on files" $ do+          do let Left (Just result) = evalSysfsMock' (getDirectoryContents "/sys/class/gpio/export") initialMockWorld []+             isInappropriateTypeError result `shouldBe` True+        it "returns failure on non-existent names" $ do+          do let Left (Just result) = evalSysfsMock' (getDirectoryContents "/sys/class/foobar") initialMockWorld []+             isDoesNotExistError result `shouldBe` True++      context "readFile" $ do+        -- Note: most interesting cases are already checked by the+        -- tests in 'SysfsGpioMockSpec.hs' and it would be a bit silly+        -- to try to test them here due to the amount of setup+        -- required to get the filesystem into the necessary state.+        -- (We would basically end up rewriting large chunks of the+        -- mock GPIO code.)+        it "works with 'constant' files" $+          let chip0 = MockGpioChip "chip0" 0 (replicate 16 defaultMockPinState)+          in evalSysfsMock' (readFile "/sys/class/gpio/gpiochip0/base") initialMockWorld [chip0] `shouldBe` Right "0\n"+        it "fails on /sys/class/gpio/export" $+          do let Left (Just result) = evalSysfsMock' (readFile "/sys/class/gpio/export") initialMockWorld []+             isPermissionError result `shouldBe` True+        it "fails on /sys/class/gpio/unexport" $+          do let Left (Just result) = evalSysfsMock' (readFile "/sys/class/gpio/unexport") initialMockWorld []+             isPermissionError result `shouldBe` True+        it "fails on non-existent file" $+          do let Left (Just result) = evalSysfsMock' (readFile "/sys/class/gpio/foo") initialMockWorld []+             isDoesNotExistError result `shouldBe` True+        it "fails on a directory" $+          do let Left (Just result) = evalSysfsMock' (readFile "/sys/class/gpio") initialMockWorld []+             isInappropriateTypeError result `shouldBe` True++      context "writeFile" $+        it "does the right thing" $+          pendingWith "Not implemented" -- See notes for 'readFile'+                                        -- above.++      context "runSysfsMockT" $ do+        let chip0 = MockGpioChip "chip0" 0 (replicate 16 defaultMockPinState)+            chip16 = MockGpioChip "xyz" 16 (replicate 32 defaultMockPinState)+            chip64 = MockGpioChip "abc" 64 (replicate 16 defaultMockPinState)+            invalidChip32 = MockGpioChip "invalid" 32 (replicate 16 defaultMockPinState)+        it "creates the specified gpiochip directories" $ do+          fmap sort (evalSysfsMock' (getDirectoryContents "/sys/class/gpio") initialMockWorld [chip0, chip16, chip64]) `shouldBe` Right ["export", "gpiochip0", "gpiochip16", "gpiochip64", "unexport"]+          evalSysfsMock' (readFile "/sys/class/gpio/gpiochip0/base") initialMockWorld [chip0, chip16, chip64] `shouldBe` Right "0\n"+          evalSysfsMock' (readFile "/sys/class/gpio/gpiochip0/ngpio") initialMockWorld [chip0, chip16, chip64] `shouldBe` Right "16\n"+          evalSysfsMock' (readFile "/sys/class/gpio/gpiochip0/label") initialMockWorld [chip0, chip16, chip64] `shouldBe` Right "chip0\n"+          evalSysfsMock' (readFile "/sys/class/gpio/gpiochip16/base") initialMockWorld [chip0, chip16, chip64] `shouldBe` Right "16\n"+          evalSysfsMock' (readFile "/sys/class/gpio/gpiochip16/ngpio") initialMockWorld [chip0, chip16, chip64] `shouldBe` Right "32\n"+          evalSysfsMock' (readFile "/sys/class/gpio/gpiochip16/label") initialMockWorld [chip0, chip16, chip64] `shouldBe` Right "xyz\n"+          evalSysfsMock' (readFile "/sys/class/gpio/gpiochip64/base") initialMockWorld [chip0, chip16, chip64] `shouldBe` Right "64\n"+          evalSysfsMock' (readFile "/sys/class/gpio/gpiochip64/ngpio") initialMockWorld [chip0, chip16, chip64] `shouldBe` Right "16\n"+          evalSysfsMock' (readFile "/sys/class/gpio/gpiochip64/label") initialMockWorld [chip0, chip16, chip64] `shouldBe` Right "abc\n"+        it "fails when MockGpioChips overlap" $ do+          evalSysfsMockME (readFile "/sys/class/gpio/gpiochip16/ngpio") initialMockWorld [chip0, chip16, invalidChip32] `shouldBe` Left (Just $ GpioChipOverlap $ Pin 47)
+ test/System/GPIO/Linux/Sysfs/SysfsGpioMockSpec.hs view
@@ -0,0 +1,879 @@+{-# OPTIONS_GHC -fno-warn-incomplete-patterns -fno-warn-incomplete-uni-patterns #-}+{-# LANGUAGE OverloadedStrings #-}++module System.GPIO.Linux.Sysfs.SysfsGpioMockSpec (spec) where++import Control.Exception (fromException)+import Control.Monad (void)+import Control.Monad.Catch (MonadCatch, MonadMask, handle)+import qualified Data.Map.Strict as Map (lookup)+import qualified Data.Set as Set (empty, fromList)++import System.GPIO.Linux.Sysfs.Mock+import System.GPIO.Linux.Sysfs.Types (SysfsEdge(..), SysfsException(..))+import System.GPIO.Monad++import Test.Hspec++-- To some extent, these tests only test the implementation of the+-- Mock interpreter, which isn't particularly valuable on its own.+-- However, it does allow us to test that we're able to express the+-- kinds of GpioT programs we want, in a pure environment (and when+-- the platform we're developing on doesn't actually have GPIO+-- functionality).++testOpenClose :: (MonadGpio h m) => m ()+testOpenClose =+  do h <- openPin (Pin 1)+     closePin h++testSetInterruptMode :: (MonadGpio h m) => m (PinInterruptMode, PinInterruptMode, PinInterruptMode, PinInterruptMode)+testSetInterruptMode =+  do d <- openPin (Pin 1)+     setPinInputMode d InputDefault+     setPinInterruptMode d Disabled+     t1 <- getPinInterruptMode d+     setPinInterruptMode d RisingEdge+     t2 <- getPinInterruptMode d+     setPinInterruptMode d FallingEdge+     t3 <- getPinInterruptMode d+     setPinInterruptMode d Level+     t4 <- getPinInterruptMode d+     closePin d+     return (t1, t2, t3, t4)++testSetInterruptModeIdempotent :: (MonadGpio h m) => m (PinInterruptMode, PinInterruptMode)+testSetInterruptModeIdempotent =+  do d <- openPin (Pin 1)+     setPinInputMode d InputDefault+     setPinInterruptMode d FallingEdge+     t1 <- getPinInterruptMode d+     setPinInterruptMode d FallingEdge+     t2 <- getPinInterruptMode d+     closePin d+     return (t1, t2)++testReadWritePin :: (MonadGpio h m) => h -> m (PinValue, PinValue, PinValue)+testReadWritePin d =+  do val1 <- readPin d+     case val1 of+       Low ->+         do writePin d High+            val2 <- readPin d+            writePin d Low+            val3 <- readPin d+            return (val1,val2,val3)+       High ->+         do writePin d Low+            val2 <- readPin d+            writePin d High+            val3 <- readPin d+            return (val1,val2,val3)++testReadWritePinIdempotent :: (MonadGpio h m) => m (PinValue, PinValue)+testReadWritePinIdempotent =+  do d <- openPin (Pin 1)+     setPinOutputMode d OutputDefault Low+     val1 <- readPin d+     case val1 of+       Low ->+         do writePin d Low+            val2 <- readPin d+            closePin d+            return (val1,val2)+       High ->+         do writePin d High+            val2 <- readPin d+            closePin d+            return (val1,val2)++testWritePinFailsOnInputPin :: (MonadGpio h m) => m ()+testWritePinFailsOnInputPin =+  do d <- openPin (Pin 1)+     setPinInputMode d InputDefault+     writePin d High+     closePin d++testTogglePinValue :: (MonadGpio h m) => m (PinValue, PinValue, PinValue, PinValue, PinValue)+testTogglePinValue =+  do d <- openPin (Pin 1)+     setPinOutputMode d OutputDefault Low+     val1 <- readPin d+     val2 <- togglePin d+     val3 <- readPin d+     val4 <- togglePin d+     val5 <- readPin d+     closePin d+     return (val1, val2, val3, val4, val5)++testTogglePinActiveLevel :: (MonadGpio h m) => h -> m (PinActiveLevel, PinActiveLevel, PinActiveLevel, PinActiveLevel, PinActiveLevel)+testTogglePinActiveLevel h =+  do val1 <- getPinActiveLevel h+     val2 <- togglePinActiveLevel h+     val3 <- getPinActiveLevel h+     val4 <- togglePinActiveLevel h+     val5 <- getPinActiveLevel h+     return (val1, val2, val3, val4, val5)++invalidHandle :: (MonadGpio h m) => (h -> m a) -> m a+invalidHandle action =+  do d <- openPin (Pin 1)+     closePin d+     action d++testWithPin :: (MonadGpio h m, MonadMask m) => m PinValue+testWithPin = withPin (Pin 1) $ \h ->+  do setPinOutputMode h OutputDefault Low+     writePin h High+     val <- readPin h+     return val++testNestedWithPin :: (MonadGpio h m, MonadMask m) => Pin -> Pin -> m (PinValue, PinValue)+testNestedWithPin p1 p2 =+  withPin p1 $ \h1 ->+    withPin p2 $ \h2 ->+      do setPinOutputMode h1 OutputDefault Low+         setPinOutputMode h2 OutputDefault Low+         writePin h1 High+         writePin h2 Low+         val1 <- readPin h1+         val2 <- readPin h2+         return (val1, val2)++handleGpioException :: MonadCatch m => (SomeGpioException -> m a) -> m a -> m a+handleGpioException = handle++testWithPinError :: (MonadGpio h m, MonadMask m, MonadCatch m) => Pin -> Pin -> m (Maybe PinValue)+testWithPinError p1 p2 = handleGpioException (const $ return Nothing) $+  withPin p1 $ \h1 ->+    withPin p2 $ \h2 ->+      do setPinOutputMode h1 OutputDefault Low+         setPinInputMode h2 InputDefault+         writePin h1 High -- should fail if p1 == p2+         writePin h2 Low -- should fail in any case+         val1 <- readPin h1+         return $ Just val1++chip0 :: MockGpioChip+chip0 = MockGpioChip "chip0" 0 (replicate 16 defaultMockPinState)++chip1 :: MockGpioChip+chip1 = MockGpioChip "chip1" 32 (replicate 32 defaultMockPinState)++evalSysfsGpioMock' :: SysfsGpioMock a -> MockWorld -> [MockGpioChip] -> Either (Maybe SysfsException) a+evalSysfsGpioMock' a w c = either (Left . fromException) return $ evalSysfsGpioMock a w c++execSysfsGpioMock' :: SysfsGpioMock a -> MockWorld -> [MockGpioChip] -> Either (Maybe SysfsException) MockWorld+execSysfsGpioMock' a w c = either (Left . fromException) return $ execSysfsGpioMock a w c++runSysfsGpioMock' :: SysfsGpioMock a -> MockWorld -> [MockGpioChip] -> Either (Maybe SysfsException) (a, MockWorld)+runSysfsGpioMock' a w c = either (Left . fromException) return $ runSysfsGpioMock a w c++evalSysfsMock' :: SysfsMock a -> MockWorld -> [MockGpioChip] -> Either (Maybe SysfsException) a+evalSysfsMock' a w c = either (Left . fromException) Right $ evalSysfsMock a w c++spec :: Spec+spec =+  do describe "pins" $+       let pinList = map Pin [0..15]+           expectedResult = Right pinList+       in+         it "returns the list of available pins" $+             evalSysfsGpioMock' pins initialMockWorld [chip0] `shouldBe` expectedResult++     describe "pinCapabilities" $+       do it "returns a pin's capabilities" $+            let testChip = MockGpioChip "testChip" 1 [defaultMockPinState+                                                     ,defaultMockPinState {_userVisibleDirection = False}+                                                     ,defaultMockPinState {_edge = Nothing}]+                (Right result) =+                  evalSysfsGpioMock'+                    (do c1 <- pinCapabilities (Pin 1)+                        c2 <- pinCapabilities (Pin 2)+                        c3 <- pinCapabilities (Pin 3)+                        return (c1,c2,c3)+                    )+                    initialMockWorld+                    [testChip]+            in do result+                    `shouldBe`+                    (PinCapabilities (Set.fromList [InputDefault]) (Set.fromList [OutputDefault]) True,+                     PinCapabilities Set.empty Set.empty False,+                     PinCapabilities (Set.fromList [InputDefault]) (Set.fromList [OutputDefault]) False)+          it "doesn't leave any pin state around" $+            let (Right world) =+                  execSysfsGpioMock (void $ pinCapabilities (Pin 1)) initialMockWorld [chip0]+            in evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/gpio1") world [] `shouldBe` Right False+          it "fails if the pin doesn't exist" $+            let (Left failure) =+                  evalSysfsGpioMock' (pinCapabilities (Pin 99)) initialMockWorld [chip0]+            in failure `shouldBe` (Just $ InvalidPin (Pin 99))++     describe "openPin/closePin" $+       do it "cleans up properly" $+            let (Right world) = execSysfsGpioMock' testOpenClose initialMockWorld [chip0]+            in evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/gpio1") world [] `shouldBe` Right False+          it "fails when the pin is unavailable" $+            evalSysfsGpioMock' testOpenClose initialMockWorld [chip1] `shouldBe` Left (Just $ InvalidPin (Pin 1))+          it "openPin does not error when the pin is already open" $+            evalSysfsGpioMock' (openPin (Pin 1) >> openPin (Pin 1) >> return ()) initialMockWorld [chip0] `shouldBe` Right ()++          it "closePin does not error when the pin is already closed" $+            evalSysfsGpioMock' (do { h <- openPin (Pin 1) ; closePin h ; closePin h} ) initialMockWorld [chip0] `shouldBe` Right ()++     describe "getPinDirection" $+       do it "gets the pin's direction" $+            let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_direction = In}]+            in+              do evalSysfsGpioMock' (withPin (Pin 1) getPinDirection) initialMockWorld [chip0] `shouldBe` Right Out+                 evalSysfsGpioMock' (withPin (Pin 1) getPinDirection) initialMockWorld [testChip] `shouldBe` Right In++          it "fails when the pin direction is not settable" $+            let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_userVisibleDirection = False}]+            in evalSysfsGpioMock' (withPin (Pin 1) getPinDirection) initialMockWorld [testChip] `shouldBe` Left (Just $ NoDirectionAttribute (Pin 1))++     describe "getPinInputMode" $+       do it "gets the pin's input mode" $+            let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_direction = In}]+            in+              evalSysfsGpioMock' (withPin (Pin 1) getPinInputMode) initialMockWorld [testChip] `shouldBe` Right InputDefault+          it "fails when the pin's direction is Out" $+            evalSysfsGpioMock' (withPin (Pin 1) getPinInputMode) initialMockWorld [chip0] `shouldBe` Left (Just $ InvalidOperation (Pin 1))++     describe "setPinInputMode" $+       do it "sets the pin's input mode and direction" $+             let testChip1 = MockGpioChip "testChip" 1 [defaultMockPinState {_direction = Out, _edge = Just Falling, _activeLow = True}]+                 testChip2 = MockGpioChip "testChip" 1 [defaultMockPinState {_direction = In, _edge = Just Rising}]+                 (Right (result1,world1)) = runSysfsGpioMock' (withPin (Pin 1) $ \h -> setPinInputMode h InputDefault >> getPinInputMode h) initialMockWorld [testChip1]+                 (Right (result2,world2)) = runSysfsGpioMock' (withPin (Pin 1) $ \h -> setPinInputMode h InputDefault >> getPinInputMode h) initialMockWorld [testChip2]+             in do result1 `shouldBe` InputDefault+                   result2 `shouldBe` InputDefault+                   Map.lookup (Pin 1) (mockWorldPins world1) `shouldBe` Just (defaultMockPinState {_direction = In, _edge = Just Falling, _activeLow = True})+                   Map.lookup (Pin 1) (mockWorldPins world2) `shouldBe` Just (defaultMockPinState {_direction = In, _edge = Just Rising})++          it "fails when the input mode is unsupported" $+             evalSysfsGpioMock' (withPin (Pin 1) $ \h -> setPinInputMode h InputFloating) initialMockWorld [chip0] `shouldBe` Left (Just $ (UnsupportedInputMode InputFloating (Pin 1)))+          it "fails when the pin's direction is not settable" $+             let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_userVisibleDirection = False}]+             in evalSysfsGpioMock' (withPin (Pin 1) (\h -> setPinInputMode h InputDefault)) initialMockWorld [testChip] `shouldBe` Left (Just $ NoDirectionAttribute (Pin 1))++     describe "getPinOutputMode" $+       do it "gets the pin's output mode" $+            evalSysfsGpioMock' (withPin (Pin 1) getPinOutputMode) initialMockWorld [chip0] `shouldBe` Right OutputDefault+          it "fails when the pin's direction is Out" $+            let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_direction = In}]+            in evalSysfsGpioMock' (withPin (Pin 1) getPinOutputMode) initialMockWorld [testChip] `shouldBe` Left (Just $ InvalidOperation (Pin 1))++     describe "setPinOutputMode" $+       do it "sets the pin's output mode, value, and direction" $+            let testChip1 = MockGpioChip "testChip" 1 [defaultMockPinState {_direction = In, _value = Low}]+                (Right (result1,world1)) = runSysfsGpioMock' (withPin (Pin 1) $ \h -> setPinOutputMode h OutputDefault High >> getPinOutputMode h) initialMockWorld [testChip1]+                testChip2 = MockGpioChip "testChip" 1 [defaultMockPinState {_direction = In, _value = Low}]+                (Right (result2,world2)) = runSysfsGpioMock' (withPin (Pin 1) $ \h -> setPinOutputMode h OutputDefault Low >> getPinOutputMode h) initialMockWorld [testChip2]+                testChip3 = MockGpioChip "testChip" 1 [defaultMockPinState {_direction = Out, _value = High}]+                (Right (result3,world3)) = runSysfsGpioMock' (withPin (Pin 1) $ \h -> setPinOutputMode h OutputDefault Low >> getPinOutputMode h) initialMockWorld [testChip3]+                (Right (result4,world4)) = runSysfsGpioMock' (withPin (Pin 1) $ \h -> setPinOutputMode h OutputDefault High >> getPinOutputMode h) initialMockWorld [chip0]+            in do result1 `shouldBe` OutputDefault+                  result2 `shouldBe` OutputDefault+                  result3 `shouldBe` OutputDefault+                  result4 `shouldBe` OutputDefault+                  Map.lookup (Pin 1) (mockWorldPins world1) `shouldBe` Just (defaultMockPinState {_direction = Out, _value = High})+                  Map.lookup (Pin 1) (mockWorldPins world2) `shouldBe` Just (defaultMockPinState {_direction = Out, _value = Low})+                  Map.lookup (Pin 1) (mockWorldPins world3) `shouldBe` Just (defaultMockPinState {_direction = Out, _value = Low})+                  Map.lookup (Pin 1) (mockWorldPins world4) `shouldBe` Just (defaultMockPinState {_direction = Out, _value = High})+          it "respects the pin's active level" $+            let testChip1 = MockGpioChip "testChip" 1 [defaultMockPinState {_direction = In, _value = Low, _activeLow = True}]+                (Right (result1,world1)) = runSysfsGpioMock' (withPin (Pin 1) $ \h -> setPinOutputMode h OutputDefault High >> getPinOutputMode h) initialMockWorld [testChip1]+                testChip2 = MockGpioChip "testChip" 1 [defaultMockPinState {_direction = In, _value = Low, _activeLow = True}]+                (Right (result2,world2)) = runSysfsGpioMock' (withPin (Pin 1) $ \h -> setPinOutputMode h OutputDefault Low >> getPinOutputMode h) initialMockWorld [testChip2]+                testChip3 = MockGpioChip "testChip" 1 [defaultMockPinState {_direction = Out, _value = High, _activeLow = True}]+                (Right (result3,world3)) = runSysfsGpioMock' (withPin (Pin 1) $ \h -> setPinOutputMode h OutputDefault Low >> getPinOutputMode h) initialMockWorld [testChip3]+                testChip4 = MockGpioChip "testChip" 1 [defaultMockPinState {_activeLow = True}]+                (Right (result4,world4)) = runSysfsGpioMock' (withPin (Pin 1) $ \h -> setPinOutputMode h OutputDefault High >> getPinOutputMode h) initialMockWorld [testChip4]+             in do result1 `shouldBe` OutputDefault+                   result2 `shouldBe` OutputDefault+                   result3 `shouldBe` OutputDefault+                   result4 `shouldBe` OutputDefault+                   Map.lookup (Pin 1) (mockWorldPins world1) `shouldBe` Just (defaultMockPinState {_direction = Out, _activeLow = True, _value = Low})+                   Map.lookup (Pin 1) (mockWorldPins world2) `shouldBe` Just (defaultMockPinState {_direction = Out, _activeLow = True, _value = High})+                   Map.lookup (Pin 1) (mockWorldPins world3) `shouldBe` Just (defaultMockPinState {_direction = Out, _activeLow = True, _value = High})+                   Map.lookup (Pin 1) (mockWorldPins world4) `shouldBe` Just (defaultMockPinState {_direction = Out, _activeLow = True, _value = Low})+          it "fails when the output mode is unsupported" $+              evalSysfsGpioMock' (withPin (Pin 1) $ \h -> setPinOutputMode h OutputPushPull Low) initialMockWorld [chip0] `shouldBe` Left (Just $ (UnsupportedOutputMode OutputPushPull (Pin 1)))+          it "can change the pin direction to 'Out' when the pin is configured for edge- or level-triggered reads " $+            evalSysfsGpioMock'+              (withPin (Pin 1) $ \h ->+                do setPinInputMode h InputDefault+                   setPinInterruptMode h RisingEdge+                   setPinOutputMode h OutputDefault Low+                   setPinInputMode h InputDefault+                   setPinInterruptMode h FallingEdge+                   setPinOutputMode h OutputDefault Low+                   setPinInputMode h InputDefault+                   setPinInterruptMode h Level+                   setPinOutputMode h OutputDefault Low+                   return True)+              initialMockWorld+              [chip0]+            `shouldBe` Right True+          it "fails when the pin's direction is not settable" $+            let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_userVisibleDirection = False}]+            in evalSysfsGpioMock' (withPin (Pin 1) (\h -> setPinOutputMode h OutputDefault Low)) initialMockWorld [testChip] `shouldBe` Left (Just $ NoDirectionAttribute (Pin 1))++     describe "getPinInterruptMode" $+       do it "gets the pin's interrupt mode" $+            let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_edge = Just Falling}]+            in+              do evalSysfsGpioMock' (withPin (Pin 1) getPinInterruptMode) initialMockWorld [chip0] `shouldBe` Right Disabled+                 evalSysfsGpioMock' (withPin (Pin 1) getPinInterruptMode) initialMockWorld [testChip] `shouldBe` Right FallingEdge++          it "fails when the pin's interrupt mode is not settable" $+            let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_edge = Nothing}]+            in evalSysfsGpioMock' (withPin (Pin 1) getPinInterruptMode) initialMockWorld [testChip] `shouldBe` Left (Just $ NoEdgeAttribute (Pin 1))++     describe "setPinInterruptMode" $+       do it "sets the pin's interrupt mode" $+            evalSysfsGpioMock' testSetInterruptMode initialMockWorld [chip0] `shouldBe` Right (Disabled, RisingEdge, FallingEdge, Level)+          it "is idempotent" $+            evalSysfsGpioMock' testSetInterruptModeIdempotent initialMockWorld [chip0] `shouldBe` Right (FallingEdge, FallingEdge)+          it "fails when the pin's interrupt mode is not settable" $+            let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_edge = Nothing}, defaultMockPinState]+            in evalSysfsGpioMock' testSetInterruptMode initialMockWorld [testChip] `shouldBe` Left (Just $ NoEdgeAttribute (Pin 1))+          it "fails when the pin is configured for output" $+            evalSysfsGpioMock' (withPin (Pin 1) $ \h -> setPinInterruptMode h Level) initialMockWorld [chip0] `shouldBe` Left (Just $ InvalidOperation (Pin 1))++     describe "getPinActiveLevel" $+       do context "when active level is high" $+            do it "returns the pin's active level" $+                   evalSysfsGpioMock' (withPin (Pin 1) getPinActiveLevel) initialMockWorld [chip0] `shouldBe` Right ActiveHigh+          context "when active level is low" $+            do it "returns the pin's active level" $+                 let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_activeLow = True}]+                 in evalSysfsGpioMock' (withPin (Pin 1) getPinActiveLevel) initialMockWorld [testChip] `shouldBe` Right ActiveLow++     describe "setPinActiveLevel" $+       do context "when active level is high" $+            do it "sets the pin's active level to low" $+                 let (Right (result, world)) = runSysfsGpioMock' (withPin (Pin 1) (\h -> setPinActiveLevel h ActiveLow >> getPinActiveLevel h)) initialMockWorld [chip0]+                 in do result `shouldBe` ActiveLow+                       Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just (defaultMockPinState {_activeLow = True})+               it "sets the pin's active level to high" $+                 let (Right (result, world)) = runSysfsGpioMock' (withPin (Pin 1) (\h -> setPinActiveLevel h ActiveHigh >> getPinActiveLevel h)) initialMockWorld [chip0]+                 in do result `shouldBe` ActiveHigh+                       Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just (defaultMockPinState {_activeLow = False})+          context "when active level is low" $+            let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_activeLow = True}]+            in do it "sets the pin's active level to low" $+                     let (Right (result, world)) = runSysfsGpioMock' (withPin (Pin 1) (\h -> setPinActiveLevel h ActiveLow >> getPinActiveLevel h)) initialMockWorld [testChip]+                    in do result `shouldBe` ActiveLow+                          Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just (defaultMockPinState {_activeLow = True})+                  it "sets the pin's active level to high" $+                     let (Right (result, world)) = runSysfsGpioMock' (withPin (Pin 1) (\h -> setPinActiveLevel h ActiveHigh >> getPinActiveLevel h)) initialMockWorld [chip0]+                    in do result `shouldBe` ActiveHigh+                          Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just (defaultMockPinState {_activeLow = False})++     describe "togglePinActiveLevel" $+       do it "toggles the pin's active level when the pin is configured for output" $+            evalSysfsGpioMock' (withPin (Pin 1) testTogglePinActiveLevel) initialMockWorld [chip0] `shouldBe` Right (ActiveHigh, ActiveLow, ActiveLow, ActiveHigh, ActiveHigh)+          it "and when the pin is configured for input" $+            evalSysfsGpioMock' (withPin (Pin 1) (\h -> setPinInputMode h InputDefault >> testTogglePinActiveLevel h)) initialMockWorld [chip0] `shouldBe` Right (ActiveHigh, ActiveLow, ActiveLow, ActiveHigh, ActiveHigh)++     describe "pollPin" $+       do it "waits for the specified trigger and returns the pin's value" $+            pendingWith "need to implement this"++          it "blocks when the interrupt mode is Disabled, until it is changed" $+            pendingWith "need to implement this"++     describe "pollPinTimeout" $+       do it "waits for the specified trigger and returns the pin's value" $+            pendingWith "need to implement this"+          it "blocks when the interrupt mode is Disabled, until it is changed" $+            pendingWith "need to implement this"+          it "times out" $+            pendingWith "need to implement this"++     describe "readPin" $+       do context "returns the pin's logical level" $+            do it "when the active level is high" $+                 evalSysfsGpioMock' (withPin (Pin 1) (\h -> setPinActiveLevel h ActiveHigh >> readPin h)) initialMockWorld [chip0] `shouldBe` Right Low+               it "when the active level is low" $+                 evalSysfsGpioMock' (withPin (Pin 1) (\h -> setPinActiveLevel h ActiveLow >> readPin h)) initialMockWorld [chip0] `shouldBe` Right High++     describe "writePin" $+       do context "sets the pin's logical value" $+            do it "when the active level is high" $+                 let Right (result, world) = runSysfsGpioMock' (withPin (Pin 1) testReadWritePin) initialMockWorld [chip0]+                 in do result `shouldBe` (Low, High, Low)+                       Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState+               it "when the active level is low" $+                  let Right (result, world) = runSysfsGpioMock' (withPin (Pin 1) (\h -> setPinActiveLevel h ActiveLow >> testReadWritePin h)) initialMockWorld [chip0]+                 in do result `shouldBe` (High, Low, High)+                       Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just (defaultMockPinState {_activeLow = True, _value = Low})++          it "is idempotent" $+            evalSysfsGpioMock' testReadWritePinIdempotent initialMockWorld [chip0] `shouldBe` Right (Low, Low)++          it "fails when the pin direction is In" $+            evalSysfsGpioMock' testWritePinFailsOnInputPin initialMockWorld [chip0] `shouldBe` Left (Just $ PermissionDenied (Pin 1))++     describe "togglePin" $+       do it "toggles the pin's value" $+            evalSysfsGpioMock' testTogglePinValue initialMockWorld [chip0] `shouldBe` Right (Low, High, High, Low, Low)+          it "fails when the pin is not configured for output" $+            evalSysfsGpioMock' (withPin (Pin 1) (\h -> setPinInputMode h InputDefault >> togglePin h)) initialMockWorld [chip0] `shouldBe` Left (Just $ PermissionDenied (Pin 1))++     describe "operations on an invalid handle fail" $+       -- Note: When used on an invalid handle, GPIO commands which+       -- return a 'Maybe' result will fail differently than commands+       -- which do not. This is by design.+       do it "in getPinDirection" $+             evalSysfsGpioMock' (invalidHandle getPinDirection) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++          it "in getPinInputMode" $+            evalSysfsGpioMock' (invalidHandle getPinInputMode) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++          it "in setPinInputMode" $+            evalSysfsGpioMock' (invalidHandle (\d -> setPinInputMode d InputDefault)) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++          it "in getPinOutputMode" $+             evalSysfsGpioMock' (invalidHandle getPinOutputMode) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++          it "in setPinOutputMode" $+             evalSysfsGpioMock' (invalidHandle (\h -> setPinOutputMode h OutputDefault Low)) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++          it "in readPin" $+            evalSysfsGpioMock' (invalidHandle readPin) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++          it "in pollPin" $+            evalSysfsGpioMock' (invalidHandle pollPin) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++          it "in pollPinTimeout" $+            evalSysfsGpioMock' (invalidHandle (\h -> pollPinTimeout h 10000)) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++          it "in writePin" $+             evalSysfsGpioMock' (invalidHandle (\d -> writePin d High)) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++          it "in togglePin" $+            evalSysfsGpioMock' (invalidHandle togglePin) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++          it "in getPinInterruptMode" $+            evalSysfsGpioMock' (invalidHandle getPinInterruptMode) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++          it "in setPinInterruptMode" $+             evalSysfsGpioMock' (invalidHandle (\d -> setPinInterruptMode d Level)) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++          it "in getPinActiveLevel" $+            evalSysfsGpioMock' (invalidHandle getPinActiveLevel) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++          it "in setPinActiveLevel" $+            evalSysfsGpioMock' (invalidHandle (\d -> setPinActiveLevel d ActiveHigh)) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++          it "in togglePinActiveLevel" $+             evalSysfsGpioMock' (invalidHandle togglePinActiveLevel) initialMockWorld [chip0] `shouldBe` Left (Just $ NotExported (Pin 1))++     describe "withPin" $+       do it "opens and closes the pin as expected" $+            let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_value = High}]+                (Right world) = execSysfsGpioMock' testWithPin initialMockWorld [testChip]+            in evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/gpio1") world [] `shouldBe` Right False++          it "throws an exception when the pin doesn't exist" $+            evalSysfsGpioMock' testWithPin initialMockWorld [chip1] `shouldBe` Left (Just $ InvalidPin (Pin 1))++          it "can nest" $+            let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_value = High}, defaultMockPinState]+                (Right (result, world)) = runSysfsGpioMock' (testNestedWithPin (Pin 1) (Pin 2)) initialMockWorld [testChip]+            in do result `shouldBe` (High, Low)+                  evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/gpio1") world [] `shouldBe` Right False+                  evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/gpio2") world [] `shouldBe` Right False++          it "handles double-open and double-close without complaint" $+            let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_value = High}, defaultMockPinState]+                (Right (result, world)) = runSysfsGpioMock' (testNestedWithPin (Pin 1) (Pin 1)) initialMockWorld [testChip]+            in do result `shouldBe` (Low, Low)+                  evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/gpio1") world [] `shouldBe` Right False++          it "cleans up when an exception occurs in the computation" $+            let (Right (result, world)) = runSysfsGpioMock' (testWithPinError (Pin 1) (Pin 2)) initialMockWorld [chip0]+            in do result `shouldBe` Nothing+                  evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/gpio1") world [] `shouldBe` Right False+                  evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/gpio2") world [] `shouldBe` Right False++     describe "InputPin" $+       do context "withInputPin" $+            do it "opens and configures a pin for input, then closes closes it" $+                     let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_direction = In}, defaultMockPinState {_direction = Out}]+                         (Right world) =+                           execSysfsGpioMock'+                             (withInputPin (Pin 1) InputDefault Nothing $ \_ ->+                                withInputPin (Pin 2) InputDefault Nothing $ \_ ->+                                  return ()+                             )+                           initialMockWorld+                           [testChip]+                   in do evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/gpio1") world [] `shouldBe` Right False+                         evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/gpio2") world [] `shouldBe` Right False+                         Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In}+                         Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In}+               it "sets the pin's input mode" $+                 evalSysfsGpioMock'+                     (withInputPin (Pin 1) InputDefault Nothing $ \h ->+                        getInputPinInputMode h+                     )+                     initialMockWorld+                     [chip0]+                   `shouldBe` Right InputDefault+               it "sets (or doesn't) the pin's active level" $+                     let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_activeLow = True}, defaultMockPinState {_activeLow = True}]+                         (Right world) =+                          execSysfsGpioMock'+                            (withInputPin (Pin 1) InputDefault Nothing $ \_ ->+                               withInputPin (Pin 2) InputDefault (Just ActiveHigh) $ \_ ->+                                 return ()+                            )+                            initialMockWorld+                            [testChip]+                     in do Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _activeLow = True}+                           Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _activeLow = False}+               it "fails if the pin's direction is fixed" $+                 let testChip = MockGpioChip "testChip" 1 [defaultMockPinState, defaultMockPinState {_direction = Out, _userVisibleDirection = False}]+                 in evalSysfsGpioMock'+                      (withInputPin (Pin 1) InputDefault Nothing $ \_ ->+                         withInputPin (Pin 2) InputDefault (Just ActiveHigh) $ \_ ->+                           return ()+                      )+                      initialMockWorld+                      [testChip]+                    `shouldBe`+                    Left (Just $ NoDirectionAttribute (Pin 2))+          context "readInputPin" $+            do it "respects the pin's active level" $+                  let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_value = High}, defaultMockPinState {_value = Low}]+                  in evalSysfsGpioMock'+                       (withInputPin (Pin 1) InputDefault Nothing $ \h1 ->+                         withInputPin (Pin 2) InputDefault (Just ActiveLow) $ \h2 ->+                           do v1 <- readInputPin h1+                              v2 <- readInputPin h2+                              return (v1,v2)+                        )+                       initialMockWorld+                       [testChip]+                     `shouldBe`+                     Right (High,High)+          context "getInputPinInputMode" $+            do it "gets the pin's input mode" $+                 evalSysfsGpioMock'+                    (withInputPin (Pin 1) InputDefault Nothing $ \h ->+                       getInputPinInputMode h+                    )+                    initialMockWorld+                    [chip0]+                  `shouldBe` Right InputDefault+          context "get/setInputPinActiveLevel" $+             do it "gets/sets the pin's active level" $+                  let (Right (result, world)) =+                        runSysfsGpioMock'+                         (withInputPin (Pin 1) InputDefault (Just ActiveHigh) $ \h1 ->+                            withInputPin (Pin 2) InputDefault (Just ActiveLow) $ \h2 ->+                              do setInputPinActiveLevel h1 ActiveLow+                                 setInputPinActiveLevel h2 ActiveHigh+                                 l1 <- getInputPinActiveLevel h1+                                 l2 <- getInputPinActiveLevel h2+                                 return (l1,l2)+                         )+                         initialMockWorld+                         [chip0]+                  in do result `shouldBe` (ActiveLow,ActiveHigh)+                        Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _activeLow = True}+                        Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _activeLow = False}+          context "toggleInputPinActiveLevel" $+              do it "toggles the pin's active level and returns the new value" $+                   let (Right (result, world)) =+                         runSysfsGpioMock'+                          (withInputPin (Pin 1) InputDefault (Just ActiveHigh) $ \h1 ->+                            withInputPin (Pin 2) InputDefault (Just ActiveLow) $ \h2 ->+                              do l1 <- toggleInputPinActiveLevel h1+                                 l2 <- toggleInputPinActiveLevel h2+                                 return (l1,l2)+                          )+                          initialMockWorld+                          [chip0]+                   in do result `shouldBe` (ActiveLow,ActiveHigh)+                         Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _activeLow = True}+                         Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _activeLow = False}+     describe "InterruptPin" $+        do context "withInterruptPin" $+             do it "opens and configures a pin for interrupts, sets its interrupt mode, then closes closes it" $+                      let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_direction = In, _edge = Just None}, defaultMockPinState {_direction = Out, _edge = Just Rising}]+                          (Right world) =+                            execSysfsGpioMock'+                              (withInterruptPin (Pin 1) InputDefault FallingEdge Nothing $ \_ ->+                                 withInterruptPin (Pin 2) InputDefault Disabled Nothing $ \_ ->+                                   return ()+                              )+                            initialMockWorld+                            [testChip]+                    in do evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/gpio1") world [] `shouldBe` Right False+                          evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/gpio2") world [] `shouldBe` Right False+                          Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _edge = Just Falling}+                          Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _edge = Just None}+                it "sets (or doesn't) the pin's active level" $+                      let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_activeLow = True}, defaultMockPinState {_activeLow = True}]+                          (Right world) =+                           execSysfsGpioMock'+                             (withInterruptPin (Pin 1) InputDefault Disabled Nothing $ \_ ->+                                withInterruptPin (Pin 2) InputDefault Disabled (Just ActiveHigh) $ \_ ->+                                  return ()+                             )+                             initialMockWorld+                             [testChip]+                      in do Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _activeLow = True}+                            Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _activeLow = False}+                it "fails if the pin's direction is fixed" $+                  let testChip = MockGpioChip "testChip" 1 [defaultMockPinState, defaultMockPinState {_direction = Out, _userVisibleDirection = False}]+                  in evalSysfsGpioMock'+                       (withInterruptPin (Pin 1) InputDefault Disabled Nothing $ \_ ->+                          withInterruptPin (Pin 2) InputDefault Disabled (Just ActiveHigh) $ \_ ->+                            return ()+                       )+                       initialMockWorld+                       [testChip]+                     `shouldBe`+                     Left (Just $ NoDirectionAttribute (Pin 2))+                it "fails if the pin doesn't support interrupts" $+                   let testChip = MockGpioChip "testChip" 1 [defaultMockPinState, defaultMockPinState {_edge = Nothing}]+                   in evalSysfsGpioMock'+                        (withInterruptPin (Pin 1) InputDefault Disabled Nothing $ \_ ->+                           withInterruptPin (Pin 2) InputDefault Disabled (Just ActiveHigh) $ \_ ->+                             return ()+                        )+                        initialMockWorld+                        [testChip]+                      `shouldBe`+                      Left (Just $ NoEdgeAttribute (Pin 2))+           context "readInterruptPin" $+             do it "respects the pin's active level" $+                   let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_value = High}, defaultMockPinState {_value = Low}]+                   in evalSysfsGpioMock'+                        (withInterruptPin (Pin 1) InputDefault Disabled Nothing $ \h1 ->+                          withInterruptPin (Pin 2) InputDefault Disabled (Just ActiveLow) $ \h2 ->+                            do v1 <- readInterruptPin h1+                               v2 <- readInterruptPin h2+                               return (v1,v2)+                         )+                        initialMockWorld+                        [testChip]+                      `shouldBe`+                      Right (High,High)+           context "getInterruptPinInputMode" $+             do it "gets the pin's input mode" $+                  evalSysfsGpioMock'+                     (withInterruptPin (Pin 1) InputDefault Disabled Nothing $ \h ->+                        getInterruptPinInputMode h+                     )+                     initialMockWorld+                     [chip0]+                   `shouldBe` Right InputDefault+           context "get/setInterruptPinActiveLevel" $+              do it "gets/sets the pin's active level" $+                   let (Right (result, world)) =+                         runSysfsGpioMock'+                          (withInterruptPin (Pin 1) InputDefault Disabled (Just ActiveHigh) $ \h1 ->+                             withInterruptPin (Pin 2) InputDefault Disabled (Just ActiveLow) $ \h2 ->+                               do setInterruptPinActiveLevel h1 ActiveLow+                                  setInterruptPinActiveLevel h2 ActiveHigh+                                  l1 <- getInterruptPinActiveLevel h1+                                  l2 <- getInterruptPinActiveLevel h2+                                  return (l1,l2)+                          )+                          initialMockWorld+                          [chip0]+                   in do result `shouldBe` (ActiveLow,ActiveHigh)+                         Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _activeLow = True}+                         Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _activeLow = False}+           context "toggleInterruptPinActiveLevel" $+               do it "toggles the pin's active level and returns the new value" $+                    let (Right (result, world)) =+                          runSysfsGpioMock'+                           (withInterruptPin (Pin 1) InputDefault Disabled (Just ActiveHigh) $ \h1 ->+                             withInterruptPin (Pin 2) InputDefault Disabled (Just ActiveLow) $ \h2 ->+                               do l1 <- toggleInterruptPinActiveLevel h1+                                  l2 <- toggleInterruptPinActiveLevel h2+                                  return (l1,l2)+                           )+                           initialMockWorld+                           [chip0]+                    in do result `shouldBe` (ActiveLow,ActiveHigh)+                          Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _activeLow = True}+                          Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _activeLow = False}+           context "get/setInterruptPinInterruptMode" $+               do it "gets/sets the pin's interrupt mode" $+                    let (Right (result, world)) =+                          runSysfsGpioMock'+                           (withInterruptPin (Pin 1) InputDefault Disabled Nothing $ \h1 ->+                              withInterruptPin (Pin 2) InputDefault RisingEdge Nothing $ \h2 ->+                                do setInterruptPinInterruptMode h1 RisingEdge+                                   setInterruptPinInterruptMode h2 Disabled+                                   m1 <- getInterruptPinInterruptMode h1+                                   m2 <- getInterruptPinInterruptMode h2+                                   return (m1,m2)+                           )+                           initialMockWorld+                           [chip0]+                    in do result `shouldBe` (RisingEdge,Disabled)+                          Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _edge = Just Rising}+                          Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = In, _edge = Just None}+           context "pollInterruptPin" $+             do it "waits for interrupts" $+                  pendingWith "need to implement this"+                it "respects the pin's active level" $+                  pendingWith "need to implement this"++           context "pollInterruptPin" $+             do it "waits for interrupts" $+                  pendingWith "need to implement this"+                it "times out" $+                  pendingWith "need to implement this"+                it "respects the pin's active level" $+                  pendingWith "need to implement this"+     describe "OutputPin" $+        do context "withOutputPin" $+             do it "opens and configures a pin for output, sets its value, then closes closes it" $+                      let testChip = MockGpioChip "testChip" 1 [defaultMockPinState {_direction = In}, defaultMockPinState {_direction = Out, _value = High}]+                          (Right world) =+                            execSysfsGpioMock'+                              (withOutputPin (Pin 1) OutputDefault Nothing High $ \_ ->+                                 withOutputPin (Pin 2) OutputDefault Nothing Low $ \_ ->+                                   return ()+                              )+                            initialMockWorld+                            [testChip]+                    in do evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/gpio1") world [] `shouldBe` Right False+                          evalSysfsMock' (doesDirectoryExist "/sys/class/gpio/gpio2") world [] `shouldBe` Right False+                          Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _value = High}+                          Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _value = Low}+                it "sets (or doesn't) the pin's active level (and the output value is relative to it)" $+                      let (Right world) =+                           execSysfsGpioMock'+                             (withOutputPin (Pin 1) OutputDefault Nothing Low $ \_ ->+                                withOutputPin (Pin 2) OutputDefault Nothing High $ \_ ->+                                  withOutputPin (Pin 3) OutputDefault (Just ActiveLow) Low $ \_ ->+                                    withOutputPin (Pin 4) OutputDefault (Just ActiveLow) High $ \_ ->+                                      withOutputPin (Pin 5) OutputDefault (Just ActiveHigh) Low $ \_ ->+                                        withOutputPin (Pin 6) OutputDefault (Just ActiveHigh) High $ \_ ->+                                          return ()+                             )+                             initialMockWorld+                             [chip0]+                      in do Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState+                            Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _value = High}+                            Map.lookup (Pin 3) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _activeLow = True, _value = High}+                            Map.lookup (Pin 4) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _activeLow = True, _value = Low}+                            Map.lookup (Pin 5) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _value = Low}+                            Map.lookup (Pin 6) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _value = High}+                it "fails if the pin's direction is fixed" $+                  let testChip = MockGpioChip "testChip" 1 [defaultMockPinState, defaultMockPinState {_direction = Out, _userVisibleDirection = False}]+                  in evalSysfsGpioMock'+                       (withOutputPin (Pin 1) OutputDefault Nothing High $ \_ ->+                          withOutputPin (Pin 2) OutputDefault Nothing Low $ \_ ->+                            return ()+                       )+                       initialMockWorld+                       [testChip]+                     `shouldBe`+                     Left (Just $ NoDirectionAttribute (Pin 2))+           context "readOutputPin" $+             do it "respects the pin's active level" $+                  evalSysfsGpioMock'+                    (withOutputPin (Pin 1) OutputDefault Nothing Low $ \h1 ->+                      withOutputPin (Pin 2) OutputDefault (Just ActiveLow) High $ \h2 ->+                        do v1 <- readOutputPin h1+                           v2 <- readOutputPin h2+                           return (v1,v2)+                     )+                     initialMockWorld+                     [chip0]+                     `shouldBe`+                     Right (Low,High)+           context "writeOutputPin" $+             do it "writes the output value and respects the pin's active level" $+                  let (Right world) =+                        execSysfsGpioMock'+                          (withOutputPin (Pin 1) OutputDefault (Just ActiveLow) High $ \h1 ->+                             withOutputPin (Pin 2) OutputDefault (Just ActiveLow) Low $ \h2 ->+                               withOutputPin (Pin 3) OutputDefault (Just ActiveHigh) High $ \h3 ->+                                 withOutputPin (Pin 4) OutputDefault (Just ActiveHigh) Low $ \h4 ->+                                   do writeOutputPin h1 Low+                                      writeOutputPin h2 High+                                      writeOutputPin h3 Low+                                      writeOutputPin h4 High+                          )+                          initialMockWorld+                          [chip0]+                  in do Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _activeLow = True, _value = High}+                        Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _activeLow = True, _value = Low}+                        Map.lookup (Pin 3) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _value = Low}+                        Map.lookup (Pin 4) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _value = High}+           context "toggleOutputPin" $+              do it "toggles the output value, returns the new value, and respects the pin's active level" $+                   let (Right (result, world)) =+                         runSysfsGpioMock'+                           (withOutputPin (Pin 1) OutputDefault (Just ActiveLow) High $ \h1 ->+                              withOutputPin (Pin 2) OutputDefault (Just ActiveLow) Low $ \h2 ->+                                withOutputPin (Pin 3) OutputDefault (Just ActiveHigh) High $ \h3 ->+                                  withOutputPin (Pin 4) OutputDefault (Just ActiveHigh) Low $ \h4 ->+                                    do v1 <- toggleOutputPin h1+                                       v2 <- toggleOutputPin h2+                                       v3 <- toggleOutputPin h3+                                       v4 <- toggleOutputPin h4+                                       return (v1,v2,v3,v4)+                           )+                           initialMockWorld+                           [chip0]+                   in do result `shouldBe` (Low,High,Low,High)+                         Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _activeLow = True, _value = High}+                         Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _activeLow = True, _value = Low}+                         Map.lookup (Pin 3) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _value = Low}+                         Map.lookup (Pin 4) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _value = High}+           context "getOutputPinOutputMode" $+             do it "gets the pin's input mode" $+                  evalSysfsGpioMock'+                     (withOutputPin (Pin 1) OutputDefault Nothing Low $ \h ->+                        getOutputPinOutputMode h+                     )+                     initialMockWorld+                     [chip0]+                   `shouldBe` Right OutputDefault+           context "get/setOutputPinActiveLevel" $+              do it "gets/sets the pin's active level" $+                   let (Right (result, world)) =+                         runSysfsGpioMock'+                          (withOutputPin (Pin 1) OutputDefault (Just ActiveHigh) Low $ \h1 ->+                             withOutputPin (Pin 2) OutputDefault (Just ActiveLow) Low $ \h2 ->+                               do setOutputPinActiveLevel h1 ActiveLow+                                  setOutputPinActiveLevel h2 ActiveHigh+                                  l1 <- getOutputPinActiveLevel h1+                                  l2 <- getOutputPinActiveLevel h2+                                  return (l1,l2)+                          )+                          initialMockWorld+                          [chip0]+                   in do result `shouldBe` (ActiveLow,ActiveHigh)+                         Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _activeLow = True}+                         Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _activeLow = False, _value = High}+           context "toggleOutputPinActiveLevel" $+               do it "toggles the pin's active level and returns the new value" $+                    let (Right (result, world)) =+                          runSysfsGpioMock'+                           (withOutputPin (Pin 1) OutputDefault (Just ActiveHigh) Low $ \h1 ->+                             withOutputPin (Pin 2) OutputDefault (Just ActiveLow) Low $ \h2 ->+                               do l1 <- toggleOutputPinActiveLevel h1+                                  l2 <- toggleOutputPinActiveLevel h2+                                  return (l1,l2)+                           )+                           initialMockWorld+                           [chip0]+                    in do result `shouldBe` (ActiveLow,ActiveHigh)+                          Map.lookup (Pin 1) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _activeLow = True}+                          Map.lookup (Pin 2) (mockWorldPins world) `shouldBe` Just defaultMockPinState {_direction = Out, _activeLow = False, _value = High}
+ test/System/GPIO/TypesSpec.hs view
@@ -0,0 +1,142 @@+{-# OPTIONS_GHC -fno-warn-warnings-deprecations #-}+{-# LANGUAGE CPP #-}++#ifndef MIN_VERSION_base+#define MIN_VERSION_base(x,y,z) 1+#endif++module System.GPIO.TypesSpec (spec) where++import System.GPIO.Types++import Data.Bits+import Test.Hspec+import Test.QuickCheck (property)++-- Test all our hand-derived instances and functions.++-- PinValue mimics Bool with respect to Bits and FiniteBits.+--+ib2v :: (Int -> Bool) -> Int -> PinValue+ib2v f n = boolToValue $ f n++bi2v :: (Bool -> Int) -> PinValue -> Int+bi2v f a = f (valueToBool a)++bb2v :: (Bool -> Bool) -> PinValue -> PinValue+bb2v f a = boolToValue $ f (valueToBool a)++bbb2v :: (Bool -> Bool -> Bool) -> PinValue -> PinValue -> PinValue+bbb2v f a b = boolToValue $ f (valueToBool a) (valueToBool b)++bib2v :: (Bool -> Int -> Bool) -> PinValue -> Int -> PinValue+bib2v f a n = boolToValue $ f (valueToBool a) n++#if MIN_VERSION_base(4,8,0)+newBase :: Spec+newBase =+  do context "implements the new base-4.8.0.0 FiniteBits typeclass methods" $+       do it "countLeadingZeros" $ property $+            \a -> countLeadingZeros a == bi2v countLeadingZeros a+          it "countTrailingZeros" $ property $+            \a -> countTrailingZeros a == bi2v countTrailingZeros a+#else+newBase :: Spec+newBase = return ()+#endif++spec :: Spec+spec =+  do describe "Pin" $+       do it "pinNumber" $ property $+            \p@(Pin n) -> n == pinNumber p++     describe "PinDirection" $+       do it "invertDirection" $+            invertDirection In == Out+              && invertDirection Out == In++     describe "PinValue" $+       do it "invertValue" $ property $+            \a -> invertValue a == complement a++          it "valueToBool" $+            valueToBool Low == False+              && valueToBool High == True++          it "boolToValue" $+            boolToValue False == Low+              && boolToValue True == High++          context "implements the Bits typeclass" $+           do it "(.&.)" $ property $+                \a b -> a .&. b == bbb2v (.&.) a b++              it "(.|.)" $ property $+                \a b -> a .|. b == bbb2v (.|.) a b++              it "xor" $ property $+                \a b -> a `xor` b == bbb2v xor a b++              it "complement" $ property $+                \a -> complement a == bb2v complement a++              it "shift" $ property $+                \a n -> a `shift` n == bib2v shift a n++              it "rotate" $ property $+                \a n -> a `rotate` n == bib2v rotate a n++              it "zeroBits" $ property $+                (zeroBits :: PinValue) == boolToValue (zeroBits:: Bool)++              it "bit" $ property $+                \n -> bit n == ib2v bit n++              it "setBit" $ property $+                \a n -> a `setBit` n == bib2v setBit a n++              it "clearBit" $ property $+                \a n -> a `clearBit` n == bib2v clearBit a n++              it "complementBit" $ property $+                \a n -> a `complementBit` n == bib2v complementBit a n++              it "testBit" $ property $+                \a n -> testBit a n == testBit (valueToBool a) n++              it "bitSizeMaybe" $ property $+                \a -> bitSizeMaybe a == bitSizeMaybe (valueToBool a)++              it "bitSize" $ property $+                \a -> bitSize a == bitSize (valueToBool a)++              it "isSigned" $ property $+                \a -> isSigned a == isSigned (valueToBool a)++              it "shiftL" $ property $+                \a n -> a `shiftL` n == bib2v shiftL a n++              it "unsafeShiftL" $ property $+                \a n -> a `unsafeShiftL` n == bib2v unsafeShiftL a n++              it "shiftR" $ property $+                \a n -> a `shiftR` n == bib2v shiftR a n++              it "unsafeShiftR" $ property $+                \a n -> a `unsafeShiftR` n == bib2v unsafeShiftR a n++              it "rotateL" $ property $+                \a n -> a `rotateL` n == bib2v rotateL a n++              it "rotateR" $ property $+                \a n -> a `rotateR` n == bib2v rotateR a n++              it "popCount" $ property $+                \a -> popCount a == popCount (valueToBool a)++          context "implements the FiniteBits typeclass" $+            do it "finiteBitSize" $ property $+                 \a -> finiteBitSize a == bi2v finiteBitSize a++               newBase