Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

pretty-print HUnitFailure exceptions #63

Merged
merged 11 commits into from
Sep 5, 2022

Conversation

sfindeisen
Copy link
Contributor

@sfindeisen sfindeisen commented Aug 27, 2022

Implements pretty printing of HUnitFailure exceptions by removing the dependency on HUnit-approx.

Fixes #60

-- The wildcard branch below prints:
-- Hello from exceptionHandler! Exception is: HUnitFailure (Just (SrcLoc {srcLocPackage = "horde-ad-0.1.0.0-inplace-testLibrary", srcLocModule = "TestCommonEqEpsilon", srcLocFile = "test/common/TestCommonEqEpsilon.hs", srcLocStartLine = 118, srcLocStartCol = 5, srcLocEndLine = 118, srcLocEndCol = 16})) (Reason "expected: 0.8991\n but got: 0.7424999999999999\n (maximum margin of error: 1.0e-6)") of type: HUnitFailure
-- BUT we cannot match e with HUnitFailure x y because it doesn't compile:
-- HUnitFailure x y -> putStrLn $ "We got HUnitFailure!"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compile error is:

test/common/TestCommonEqEpsilon.hs:49:5: error:
    • Couldn't match expected type ‘e’ with actual type ‘HUnitFailure’
      ‘e’ is a rigid type variable bound by
        a pattern with constructor:
          SomeException :: forall e. Exception e => e -> SomeException,
        in an equation for ‘exceptionHandler’
        at test/common/TestCommonEqEpsilon.hs:44:19-33
    • In the pattern: HUnitFailure x y
      In a case alternative:
          HUnitFailure x y -> putStrLn $ "We got HUnitFailure!"
      In a stmt of a 'do' block:
        case e of
          HUnitFailure x y -> putStrLn $ "We got HUnitFailure!"
          _ -> putStrLn
                 $ "Hello from exceptionHandler! Exception is: "
                     ++ (show e) ++ " of type: " ++ (show $ typeOf e)
    • Relevant bindings include
        e :: e (bound at test/common/TestCommonEqEpsilon.hs:44:33)
   |
49 |     HUnitFailure x y -> putStrLn $ "We got HUnitFailure!"
   |

and I thought: this is perhaps due to a missing instance, i.e. instance Exception HUnitFailure. So I tried:

import Test.Tasty.HUnit.Orig ()

(where that instance is declared) but then I got:

test/common/TestCommonEqEpsilon.hs:15:1: error:
    Could not load module ‘Test.Tasty.HUnit.Orig’
    it is a hidden module in the package ‘tasty-hunit-0.10.0.3’
    it is a hidden module in the package ‘tasty-hunit-0.10.0.3’
    Use -v (or `:set -v` in ghci) to see a list of the files searched for.
   |
15 | import           Test.Tasty.HUnit.Orig ()
   |

and if I just declare the instance: instance Exception HUnitFailure then I get:

test/common/TestCommonEqEpsilon.hs:19:10: error:
    Duplicate instance declarations:
      instance Exception HUnitFailure
        -- Defined at test/common/TestCommonEqEpsilon.hs:19:10
      instance [safe] Exception HUnitFailure
        -- Defined in ‘tasty-hunit-0.10.0.3:Test.Tasty.HUnit.Orig’
   |
19 | instance Exception HUnitFailure
   |

Is there a way to pattern match on SomeException (HUnitFailure) then?...

What is [safe]?

BTW I just discovered here:

-- | This is the code copied from the original hunit package (v. 1.2.5.2).
-- with minor modifications

but this is probably irrelevant.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this now explained by there being two copies of HUnitFailure?

Copy link
Contributor Author

@sfindeisen sfindeisen Aug 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No.

It is a well-known issue: UnkindPartition/tasty#327 (comment)

EDIT: maybe. Let me see...

EDIT: yes. Let me implement a fix...

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SomeException fails because, with this quantification in place, you need to choose a type e, which decides at what point of the exception hierarchy you want to be. You choose with https://downloads.haskell.org/ghc/latest/docs/libraries/base-4.17.0.0/Control-Exception.html#v:fromException (or rather, with the type that the context implies in the application of fromException). In response you get an exception if, indeed, at this point of the hierarchy there is any exception encapsulated inside that expression. I think, If you implied another type e, you could get a value of another type out (though internally it would be the same untyped value, but you'd get a different API for it).

it is a hidden module

Right, an internal hidden module.

What is [safe]?

No idea.

Is there a way to pattern match on SomeException (HUnitFailure) then?

I don't think so.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just managed to fix the whole thing without touching any exceptions at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SomeException fails because, with this quantification in place, you need to choose a type e, which decides at what point of the exception hierarchy you want to be. You choose with https://downloads.haskell.org/ghc/latest/docs/libraries/base-4.17.0.0/Control-Exception.html#v:fromException (or rather, with the type that the context implies in the application of fromException). In response you get an exception if, indeed, at this point of the hierarchy there is any exception encapsulated inside that expression. I think, If you implied another type e, you could get a value of another type out (though internally it would be the same untyped value, but you'd get a different API for it).

What do you say? That you cannot pattern match on SomeException (except the wildcard _ pattern), but you have to explicitly call fromException instead? Why?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Because it's 21st century and we have OOP or at least hierarchies or at least subtyping or at least the type tricks above that emulate subtyping. But seriously, is the way hierarchy is expressed surprising or the purpose unclear or what else?

Copy link
Owner

@Mikolaj Mikolaj Aug 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps the crucial bit is extensible hierarchies. You have a virtual datatype of all possible exceptions you can get in your application and any new dependency you link against can add a few cases to that "datatype". That can't be done with normal datatypes. OCaml extensible variant types might suffice, but Haskell's anonymous sums are probably not there yet.

@sfindeisen sfindeisen marked this pull request as ready for review August 31, 2022 09:07
-- because we do not want to depend on Test.HUnit.Approx . Instead, we're using an outdated copy of HUnit hardcoded into
-- Tasty (Test.Tasty.HUnit).
--
-- Copyright (c) 2014, Richard Eisenberg
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @goldfirere , I have stolen some of your code. Hope you're OK with that?... This is to get rid of dependency on HUnit-approx (Tasty contains its own copy of HUnit and if we have both, it leads to problems).

Copy link
Owner

@Mikolaj Mikolaj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Works for me. Please feel free to merge now in whichever of the 3 ways.

It's a pity we have to copy-paste code, but that's the price for the design decision in Tasty that makes them safe from PVP violations from re-exporting Hunit modules, IIUC (UnkindPartition/tasty#327).

@Mikolaj
Copy link
Owner

Mikolaj commented Aug 31, 2022

Quick! Before @goldfirere tells you this doesn't make any sense.

@Mikolaj
Copy link
Owner

Mikolaj commented Sep 2, 2022

Please feel free to merge now in whichever of the 3 ways.

I've pushed to master, so the 'Merge PR' method now requires a local rebase.

@sfindeisen
Copy link
Contributor Author

I've pushed to master, so the 'Merge PR' method now requires a local rebase.

Rebase does not exactly work: https://stackoverflow.com/questions/19016698/git-branch-diverged-after-rebase , ok to merge master in here instead?

@Mikolaj
Copy link
Owner

Mikolaj commented Sep 3, 2022

Rebase does not exactly work

This SO thread says that it works fine with push -f. And since you are not collaborating with anyone on this branch, push -f should be fine, shouldn't it? Or am I missing something? I've been rebasing for decades. Weren't you?

ok to merge master in here instead?

I'd prefer not to, because it complicates git history and it becomes harder to read and harder to bisect. Unless you want to squash afterwards, but if so, you don't need to merge at all.

@sfindeisen sfindeisen force-pushed the sfindeisen/60-HUnitFailure-catch branch from 5598815 to 0e31549 Compare September 3, 2022 09:19
@sfindeisen
Copy link
Contributor Author

I can't see why is push -f better than a merge commit, but here you have it.

@Mikolaj
Copy link
Owner

Mikolaj commented Sep 3, 2022

I can't see why is push -f better than a merge commit, but here you have it.

Thank you.

It's a matter of taste. Some people prefer merges to rebases. With many long-lived concurrent features branches, rebases become a nightmare, but with merges the history becomes a nightmare. It's a bit similar to "should programs be optimized for writing or for reading".

Do we agree, however, that the effect of push -f on a git history is different than of a merge commit and that, at scale, bisecting, reverting, etc., is harder with merges than with linear history?

@sfindeisen
Copy link
Contributor Author

Yes, sure. I would have less reservation towards push -f if there was a mechanism to verify that "you are not collaborating with anyone on this branch". Perhaps @goldfirere started hacking on it already?... Regarding bisecting, reverting etc. I've never encountered any problems with that in practice. Did you?

@Mikolaj
Copy link
Owner

Mikolaj commented Sep 3, 2022

Yes, sure. I would have less reservation towards push -f if there was a mechanism to verify that "you are not collaborating with anyone on this branch". Perhaps @goldfirere started hacking on it already?...

Fair enough.

Regarding bisecting, reverting etc. I've never encountered any problems with that in practice. Did you?

I did. With both, but most of all with reading and understanding the intention of PRs, because they are no longer self contained entities, but amalgams with whatever else they merge.

@sfindeisen sfindeisen removed the request for review from goldfirere September 5, 2022 07:34
@sfindeisen
Copy link
Contributor Author

I got rid of @goldfirere 's code, making this PR even smaller. OK to merge, @Mikolaj ?

@Mikolaj
Copy link
Owner

Mikolaj commented Sep 5, 2022

I got rid of @goldfirere 's code, making this PR even smaller. OK to merge, @Mikolaj ?

Well done. Please do.

@sfindeisen sfindeisen merged commit 53e1281 into master Sep 5, 2022
@Mikolaj
Copy link
Owner

Mikolaj commented Sep 5, 2022

@sfindeisen: Thanks a lot for the PR. This now complements #57 to become a comprehensive solution to UnkindPartition/tasty#337 that can be added on top of Tasty and that does not use HUnit nor any other non-base and non-tasty packages.

@sfindeisen
Copy link
Contributor Author

@Mikolaj thanks.

@sfindeisen sfindeisen deleted the sfindeisen/60-HUnitFailure-catch branch October 28, 2022 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

In approx tests, HUnitFailure is not caught and not pretty-printed
2 participants