Skip to content

Commit 5bf7f02

Browse files
committed
Fix output for non-Unicode locales
1 parent 1f8c8ed commit 5bf7f02

3 files changed

Lines changed: 90 additions & 18 deletions

File tree

src/Test/StrictCheck.hs

Lines changed: 72 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,13 @@ import Generics.SOP hiding (Shape)
7878
import Test.QuickCheck as Exported hiding (Args, Result, function)
7979
import qualified Test.QuickCheck as QC
8080

81+
import Data.Char (ord)
82+
import Data.Function (on)
8183
import Data.List
8284
import Data.Maybe
8385
import Data.IORef
86+
import GHC.IO.Encoding (textEncodingName)
87+
import qualified System.IO as IO
8488
import Type.Reflection
8589

8690
-- | The default comparison of demands: exact equality
@@ -295,8 +299,16 @@ strictCheckSpecExact spec function =
295299
(putStrLn . head . lines) (output result)
296300
case maybeExample of
297301
Nothing -> return ()
298-
Just example ->
299-
putStrLn (Prelude.uncurry displayCounterSpec example)
302+
Just example -> do
303+
unicode <- doesStdoutAcceptUnicode
304+
putStrLn (Prelude.uncurry (displayCounterSpec unicode) example)
305+
306+
doesStdoutAcceptUnicode :: IO Bool
307+
doesStdoutAcceptUnicode = do
308+
encoding <- IO.hGetEncoding IO.stdout
309+
case encoding of
310+
Nothing -> pure False
311+
Just enc -> pure (any (((==) `on` textEncodingName) enc) [IO.utf8, IO.utf8_bom, IO.utf16, IO.utf16le, IO.utf16be, IO.utf32, IO.utf32le, IO.utf32be])
300312

301313
------------------------------------------------------------
302314
-- An Evaluation is what we generate when StrictCheck-ing --
@@ -400,53 +412,94 @@ shrinkEvalWith
400412
T -> Nothing
401413
E pos -> Just pos
402414

415+
-- | If 'False' (unicode output is disabled), replace all non-ASCII characters with '?'.
416+
sanitize :: Bool -> String -> String
417+
sanitize True = id
418+
sanitize False = fmap (\c -> if ord c < 128 then c else '?')
403419

404420
-- | Render a counter-example to a specification (that is, an 'Evaluation'
405-
-- paired with some expected input demands it doesn't match) as a Unicode
406-
-- box-drawing sketch
421+
-- paired with some expected input demands it doesn't match) as a
422+
-- box-drawing sketch (in Unicode or ASCII depending on whether the
423+
-- first argument is 'True' or 'False')
407424
displayCounterSpec
408425
:: forall args result.
409426
(Shaped result, All Shaped args)
410-
=> Evaluation args result
427+
=> Bool -- ^ 'True' to enable prettier Unicode output
428+
-> Evaluation args result
411429
-> NP Demand args
412430
-> String
413-
displayCounterSpec (Evaluation inputs inputsD resultD) predictedInputsD =
414-
beside inputBox (" " : "───" : repeat " ") resultBox
431+
displayCounterSpec unicode (Evaluation inputs inputsD resultD) predictedInputsD =
432+
sanitize unicode $
433+
beside inputBox (" " : threeDashes : repeat " ") resultBox
415434
++ (flip replicate ' ' $
416435
(2 `max` (subtract 2 $ (lineMax [inputString] `div` 2))))
417-
++ "🡓 🡓 🡓\n"
436+
++ threeArrows
418437
++ beside
419438
actualBox
420-
(" " : " " : " ═╱═ " : repeat " ")
439+
(" " : " " : notEqual : repeat " ")
421440
predictedBox
422441
where
423-
inputBox =
442+
threeDashes | unicode = "───"
443+
| otherwise = "---"
444+
threeArrows | unicode = "🡓 🡓 🡓\n"
445+
| otherwise = "v v v\n"
446+
notEqual | unicode = " ═╱═ "
447+
| otherwise = " =/= "
448+
inputBox
449+
| unicode =
424450
box "" '' ""
425451
"" inputHeader ""
426452
"" '' ""
427453
"" inputString ""
428454
"" '' ""
429-
430-
resultBox =
455+
| otherwise =
456+
box "+" '-' "+"
457+
"|" inputHeader "+"
458+
"+" '-' "+"
459+
"|" inputString "|"
460+
"+" '-' "+"
461+
462+
resultBox
463+
| unicode =
431464
box "" '' ""
432465
"" resultHeader ""
433466
"" '' ""
434467
"" resultString ""
435468
"" '' ""
436-
437-
actualBox =
469+
| otherwise =
470+
box "+" '-' "+"
471+
"+" resultHeader "|"
472+
"+" '-' "+"
473+
"|" resultString "|"
474+
"+" '-' "+"
475+
476+
actualBox
477+
| unicode =
438478
box "" '' ""
439479
"" actualHeader ""
440480
"" '' ""
441481
"" actualDemandString ""
442482
"" '' ""
443-
444-
predictedBox =
483+
| otherwise =
484+
box "+" '-' "+"
485+
"|" actualHeader "|"
486+
"+" '-' "+"
487+
"|" actualDemandString "|"
488+
"+" '-' "+"
489+
490+
predictedBox
491+
| unicode =
445492
box "" '' ""
446493
"" predictedHeader ""
447494
"" '' ""
448495
"" predictedDemandString ""
449496
"" '' ""
497+
| otherwise =
498+
box "+" '-' "+"
499+
"|" predictedHeader "|"
500+
"+" '-' "+"
501+
"|" predictedDemandString "|"
502+
"+" '-' "+"
450503

451504
inputHeader = " Input" ++ plural
452505
resultHeader = " Demand on result"
@@ -511,4 +564,6 @@ displayCounterSpec (Evaluation inputs inputsD resultD) predictedInputsD =
511564
showNPWith' :: forall ys. All c ys => NP g ys -> String
512565
showNPWith' Nil = ""
513566
showNPWith' (y :* ys) =
514-
"" ++ display y ++ "\n" ++ showNPWith' ys
567+
bullet ++ display y ++ "\n" ++ showNPWith' ys
568+
bullet | unicode = ""
569+
| otherwise = " * "

src/Test/StrictCheck/Examples/Lists.hs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ length_spec =
2020
Spec $ \predict _ xs ->
2121
predict (xs $> thunk)
2222

23+
-- | An incorrect specification for 'length' (to test the pretty printer)
24+
bad_length_spec :: Spec '[[a]] Int
25+
bad_length_spec =
26+
Spec $ \predict _ xs ->
27+
predict (take 1 xs $> thunk)
28+
2329
-- | A naive specification for 'take', which is wrong
2430
take_spec_too_easy :: Spec '[Int, [a]] [a]
2531
take_spec_too_easy =

tests/Specs.hs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import Test.StrictCheck
66
import Test.StrictCheck.Examples.Lists
77
import Test.StrictCheck.Examples.Map
88

9+
import Control.Monad (when)
10+
import GHC.IO.Encoding (textEncodingName)
11+
import System.IO
12+
913
runSpecs :: IO ()
1014
runSpecs = do
1115
putStrLn "Checking length_spec..."
@@ -35,4 +39,11 @@ runSpecs = do
3539
iterSolution_spec
3640
iterSolutionWithKey >>= print
3741

38-
return ()
42+
putStrLn "Checking bad_length_spec (failure is expected!)..."
43+
strictCheckSpecExact bad_length_spec (length :: [Int] -> Int)
44+
45+
enc <- hGetEncoding stdout
46+
when (fmap textEncodingName enc == Just "UTF-8") $ do
47+
hSetEncoding stdout latin1
48+
putStrLn "Checking bad_length_spec without Unicode output (failure is expected!)..."
49+
strictCheckSpecExact bad_length_spec (length :: [Int] -> Int)

0 commit comments

Comments
 (0)