Closed akegalj closed 5 years ago
ps, it might be not related to file handle at all (the first example) but failure is still unexpected (at least to me)
Can you please check if you can reproduce it with HEAD? I fixed a makeOperations: impossible
problem in https://github.com/advancedtelematic/quickcheck-state-machine/commit/fa757ec4f05494c85f4feef1b7630902d9fe48d9 .
With new version seems like error handling is improved, but file handle still gets closed :
Model
== ResetWalletA ==> bla: hPutStr: illegal operation (handle is closed) [ 0 ]
file handle failure FAILED [2]
Failures:
/home/akegalj/projects/serokell/cardano-sl/wallet-new/test/state-machine/Spec.hs:22:17:
1) Tests.IO expected failure
Falsifiable (after 1 test):
PostconditionFailed "BotC" /= Ok
/home/akegalj/projects/serokell/cardano-sl/wallet-new/test/state-machine/Spec.hs:23:17:
2) Tests.IO file handle failure
Falsifiable (after 3 tests and 1 shrink):
Commands { unCommands = [ Command ResetWalletA (fromList []) ] }
ExceptionThrown /= Ok
Randomized with seed 1787723843
Finished in 1.0197 seconds
2 examples, 2 failures
*** Exception: ExitFailure 1
it is obvious now what is the underlying problem - but it is still unexpected that file handle gets closed, right?
I'm never really understood hspec
, but could it be that after
is called after the first test finishes, closing the handle, causing the second test to fail? Perhaps try without parallel
or using two different files for the two tests?
At first it seemed like a good idea (I totally forgot we have added parallel
there - good catch) but when I:
parallel
before f1 $ after f2
I added explicit bracket: around $ bracket f1 f2
withMaxSuccess 100
we are running it only once (using once
)forAllCommands sm (just 10)
to 10 (not to trigger test coverage failure)we have ended up with this https://github.com/input-output-hk/cardano-sl/commit/e42ef40acbb4ffceafc7a3ea5a63b27fdb8d9dbb :
tests :: Spec
tests =
describe "Tests" $ do
around (bracket (liftIO $ F.openFile "bla" F.WriteMode) (liftIO . F.hClose)) $
describe "IO" $ do
it "file handle failure" $ once . prop_test_fail
In this example I am expecting hspec
to run prop_test_fail
only once but I am getting:
Tests
IO
["ResetWalletA","ResetWalletA"]
[]
["ResetWalletA"]
[]
Model
== ResetWalletA ==> bla: hPutStr: illegal operation (handle is closed) [ 0 ]
file handle failure FAILED [1]
Failures:
/home/akegalj/projects/serokell/cardano-sl/wallet-new/test/state-machine/Spec.hs:23:17:
1) Tests.IO file handle failure
Falsifiable (after 1 test and 1 shrink):
Commands { unCommands = [ Command ResetWalletA (fromList []) ] }
ExceptionThrown /= Ok
Again it seems that shrinking tried to use semantics
after handle got closed.
I am going to rewrite it using tasty
to compare the results and report back
tasty
is used in this example https://github.com/input-output-hk/cardano-sl/commit/c3d5db923dca6ec01676d6c50e9708e5c041e38a :
testsTasty :: TestTree
testsTasty = testGroup "Tests tasty"
[ testGroup "IO"
[ fileHandle "file handle failure" $ once . prop_test_fail
]
]
where
fileHandle test prop =
withResource (liftIO $ F.openFile "bla" F.WriteMode) (liftIO . F.hClose)
(\h -> testProperty test (ioProperty (prop <$> h)))
this is based on an quickcheck-state-machine
example TicketDispenser
. Handle is not closed using this implementation, but quickcheck shrinking was also not triggered even once. I believe problem might be in ioProperty
which dissables shrinking of generated commands (at least this is my understanding) https://hackage.haskell.org/package/QuickCheck-2.12.6.1/docs/Test-QuickCheck.html#v:ioProperty:
any random values generated inside of the argument to ioProperty will not currently be shrunk. For best results, generate all random values before calling ioProperty, or use idempotentIOProperty if that is safe.
I am going to check can I avoid using ioProperty
and that way disabling shrinking in this example. I would expect when shrinking is enabled I will trigger the same problem as with hspec
. I am going to try to remove ioProperty
to check this hypothesis
By using idempotentIOProperty
instead of ioProperty
in this example https://github.com/input-output-hk/cardano-sl/commit/0769300f65966959ef0e868eb7eb5122b0767f60 which apperently caches some IO which is not what we want in general, but it served as a good example to check will somehow handle get closed. Quickcheck started shrinking and handle wasn't closed unnexpectendly - so everything behaved as expected:
...
["ResetWalletA","ResetWalletA","ResetWalletA"]
[]
["ResetWalletA","ResetWalletA"]
[]
["ResetWalletA"]
[]
Model
== ResetWalletA ==> ResetWalletR [ 0 ]
Model
FAIL
*** Failed! Falsifiable (after 1 test and 4 shrinks):
Commands { unCommands = [ Command ResetWalletA (fromList []) ] }
PostconditionFailed "BotC" /= Ok
Use --quickcheck-replay=201112 to reproduce.
This raises suspicion on hspec
and functionalities around
, before
, after
which seems like get triggered before shrinking is finished.
Closing this issue as problem doesn't seem related to quickcheck-state-machine
internals
EDIT:
in fact, idempotentIOProperty
might have cached writing to file - so in that case problem might be masked (existing but not showing up)
EDIT2:
I am confident problem is in hspec
. This example https://github.com/input-output-hk/cardano-sl/commit/5d2edf254d7011f4c1d7b58d686e6c283ffbea8b is using tasty without ioProperty and without idempotentIOProperty and everything works as expected. Shrinking works as expected. Sorry for disturbance and thanks for handholding
In this example https://github.com/input-output-hk/cardano-sl/blob/akegalj/co-412/quickcheck-state-machine-error/wallet-new/test/state-machine/Spec.hs :
we open a file and give file handle to the both properties,
prop_test_ok
andprop_test_fail
.prop_test_ok
will expectedly fail with :and
prop_test_fail
will usually fail with:but sometimes (if run enough times) will fail with :
What seems common is that
prop_test_ok
doesn't run any shrinking code whereprop_test_fail
runs shrinking code sometimes (When its not run everything is expected).Seems like there is a problem with shrinking code at https://hackage.haskell.org/package/quickcheck-state-machine-0.4.2/docs/src/Test.StateMachine.Types.History.html#makeOperations . When error is thrown (I don't know reason yet) - file handle gets closed (probably because error is propagated).
We noticed a problem in a more complicated test which involves talking to the db where test framework started failing with:
The problem wasn't as obvious as in the smaller example but clues are the same:
semantics
but because db handle was closed there was sqlite error msgI will paste the rest of the code here: