Skip to content

Commit 87e6aff

Browse files
committed
Improve equivalence tests
Better generator distribution.
1 parent 4dd36ad commit 87e6aff

File tree

3 files changed

+605
-568
lines changed

3 files changed

+605
-568
lines changed

filepath.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ test-suite filepath-equivalent-tests
133133
Legacy.System.FilePath.Posix
134134
Legacy.System.FilePath.Windows
135135
TestUtil
136+
Gen
136137

137138
build-depends:
138139
, base
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
{-# LANGUAGE OverlappingInstances #-}
2+
{-# LANGUAGE FlexibleInstances #-}
3+
{-# LANGUAGE DeriveGeneric #-}
4+
{-# LANGUAGE DerivingVia, TypeOperators #-}
5+
{-# LANGUAGE ScopedTypeVariables #-}
6+
{-# LANGUAGE UndecidableInstances #-}
7+
{-# LANGUAGE DeriveAnyClass #-}
8+
{-# LANGUAGE DataKinds #-}
9+
10+
module Gen where
11+
12+
import System.FilePath
13+
import Data.List.NonEmpty (NonEmpty(..))
14+
import GHC.Generics
15+
import Generic.Random
16+
import Generics.Deriving.Show
17+
import Prelude as P
18+
import Test.Tasty.QuickCheck hiding ((==>))
19+
20+
import qualified Data.List.NonEmpty as NE
21+
22+
23+
class AltShow a where
24+
altShow :: a -> String
25+
26+
instance {-# OVERLAPPABLE #-} Show a => AltShow a where
27+
altShow = show
28+
29+
instance {-# OVERLAPS #-} AltShow String where
30+
altShow = id
31+
32+
instance {-# OVERLAPPABLE #-} AltShow a => AltShow (Maybe a) where
33+
altShow Nothing = ""
34+
altShow (Just a) = altShow a
35+
36+
37+
newtype WindowsFilePaths = WindowsFilePaths { unWindowsFilePaths :: [WindowsFilePath] }
38+
deriving (Show, Eq, Ord, Generic)
39+
40+
-- filepath = namespace *"\" namespace-tail
41+
-- / UNC
42+
-- / [ disk ] *"\" relative-path
43+
-- / disk *"\"
44+
data WindowsFilePath = NS NameSpace [Separator] NSTail
45+
| UNC UNCShare
46+
| N (Maybe Char) [Separator] (Maybe RelFilePath)
47+
-- ^ This differs from the grammar, because we allow
48+
-- empty paths
49+
| PotentiallyInvalid FilePath
50+
-- ^ this branch is added purely for the tests
51+
deriving (GShow, Eq, Ord, Generic)
52+
deriving Arbitrary via (GenericArbitraryRec '[6, 2, 2, 1] `AndShrinking` WindowsFilePath)
53+
54+
instance Show WindowsFilePath where
55+
show wf = gshow wf ++ " (" ++ altShow wf ++ ")"
56+
57+
instance AltShow WindowsFilePath where
58+
altShow (NS ns seps nstail) = altShow ns ++ altShow seps ++ altShow nstail
59+
altShow (UNC unc) = altShow unc
60+
altShow (N mdisk seps mfrp) = maybe [] (:[]) mdisk ++ (altShow seps ++ altShow mfrp)
61+
altShow (PotentiallyInvalid fp) = fp
62+
63+
64+
-- namespace-tail = ( disk 1*"\" relative-path ; C:foo\bar is not valid
65+
-- ; namespaced paths are all absolute
66+
-- / disk *"\"
67+
-- / relative-path
68+
-- )
69+
data NSTail = NST1 Char (NonEmpty Separator) RelFilePath
70+
| NST2 Char [Separator]
71+
| NST3 RelFilePath
72+
deriving (GShow, Show, Eq, Ord, Generic)
73+
deriving Arbitrary via (GenericArbitraryRec '[1, 1, 1] `AndShrinking` NSTail)
74+
75+
instance AltShow NSTail where
76+
altShow (NST1 disk seps relfp) = disk:':':(altShow seps ++ altShow relfp)
77+
altShow (NST2 disk seps) = disk:':':altShow seps
78+
altShow (NST3 relfp) = altShow relfp
79+
80+
81+
-- UNC = "\\" 1*pchar "\" 1*pchar [ 1*"\" [ relative-path ] ]
82+
data UNCShare = UNCShare Separator Separator
83+
NonEmptyString
84+
(NonEmpty Separator)
85+
NonEmptyString
86+
(Maybe (NonEmpty Separator, Maybe RelFilePath))
87+
deriving (GShow, Show, Eq, Ord, Generic)
88+
deriving Arbitrary via (GenericArbitraryRec '[1] `AndShrinking` UNCShare)
89+
90+
instance AltShow UNCShare where
91+
altShow (UNCShare sep1 sep2 fp1 seps fp2 mrfp) = altShow sep1 ++ altShow sep2 ++ altShow fp1 ++ altShow seps ++ altShow fp2 ++ maybe "" (\(a, b) -> altShow a ++ maybe "" altShow b) mrfp
92+
93+
newtype NonEmptyString = NonEmptyString (NonEmpty Char)
94+
deriving (GShow, Show, Eq, Ord, Generic)
95+
deriving Arbitrary via (GenericArbitraryRec '[1] `AndShrinking` NonEmptyString)
96+
97+
instance Semigroup NonEmptyString where
98+
(<>) (NonEmptyString ne) (NonEmptyString ne') = NonEmptyString (ne <> ne')
99+
100+
instance AltShow NonEmptyString where
101+
altShow (NonEmptyString ns) = NE.toList ns
102+
103+
104+
-- | Windows API Namespaces
105+
--
106+
-- https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#namespaces
107+
-- https://support.microsoft.com/en-us/topic/70b92942-a643-2f2d-2ac6-aad8acad49fb
108+
-- https://superuser.com/a/1096784/854039
109+
-- https://reverseengineering.stackexchange.com/a/15178
110+
-- https://stackoverflow.com/a/25099634
111+
--
112+
-- namespace = file-namespace / device-namespace / nt-namespace
113+
-- file-namespace = "\" "\" "?" "\"
114+
-- device-namespace = "\" "\" "." "\"
115+
-- nt-namespace = "\" "?" "?" "\"
116+
data NameSpace = FileNameSpace
117+
| DeviceNameSpace
118+
| NTNameSpace
119+
deriving (GShow, Show, Eq, Ord, Generic)
120+
deriving Arbitrary via (GenericArbitraryRec '[3, 1, 1] `AndShrinking` NameSpace)
121+
122+
instance AltShow NameSpace where
123+
altShow FileNameSpace = "\\\\?\\"
124+
altShow DeviceNameSpace = "\\\\.\\"
125+
altShow NTNameSpace = "\\??\\"
126+
127+
128+
data Separator = UnixSep
129+
| WindowsSep
130+
deriving (GShow, Show, Eq, Ord, Generic)
131+
deriving Arbitrary via (GenericArbitraryRec '[1, 1] `AndShrinking` Separator)
132+
133+
instance AltShow Separator where
134+
altShow UnixSep = "/"
135+
altShow WindowsSep = "\\"
136+
137+
instance {-# OVERLAPS #-} AltShow (NonEmpty Separator) where
138+
altShow ne = mconcat $ NE.toList (altShow <$> ne)
139+
140+
instance {-# OVERLAPS #-} AltShow [Separator] where
141+
altShow [] = ""
142+
altShow ne = altShow (NE.fromList ne)
143+
144+
-- relative-path = 1*(path-name 1*"\") [ file-name ] / file-name
145+
data RelFilePath = Rel1 (NonEmpty (NonEmptyString, NonEmpty Separator)) (Maybe FileName)
146+
| Rel2 FileName
147+
deriving (GShow, Show, Eq, Ord, Generic)
148+
deriving Arbitrary via (GenericArbitraryRec '[2, 1] `AndShrinking` RelFilePath)
149+
150+
instance AltShow RelFilePath where
151+
altShow (Rel1 ns mf) = (mconcat $ NE.toList $ fmap (\(a, b) -> altShow a ++ altShow b) ns) ++ altShow mf
152+
altShow (Rel2 fn) = altShow fn
153+
154+
-- file-name = 1*pchar [ stream ]
155+
data FileName = FileName NonEmptyString (Maybe DataStream)
156+
deriving (GShow, Show, Eq, Ord, Generic)
157+
158+
instance Arbitrary FileName where
159+
arbitrary = do
160+
ns <- arbitrary
161+
ds <- arbitrary
162+
i <- chooseInt (0, 100)
163+
if i >= 50
164+
then do
165+
ns' <- arbitrary
166+
pure $ FileName (ns <> NonEmptyString ('.':|[]) <> ns') ds
167+
else pure $ FileName ns ds
168+
shrink = genericShrink
169+
170+
171+
instance Arbitrary (Maybe DataStream) where
172+
arbitrary = genericArbitraryRec (1 % 1 % ())
173+
shrink = genericShrink
174+
175+
instance AltShow FileName where
176+
altShow (FileName ns ds) = altShow ns ++ altShow ds
177+
178+
-- stream = ":" 1*schar [ ":" 1*schar ] / ":" ":" 1*schar
179+
data DataStream = DS1 NonEmptyString (Maybe NonEmptyString)
180+
| DS2 NonEmptyString -- ::datatype
181+
deriving (GShow, Show, Eq, Ord, Generic)
182+
deriving Arbitrary via (GenericArbitraryRec '[1, 1] `AndShrinking` DataStream)
183+
184+
instance AltShow DataStream where
185+
altShow (DS1 ns Nothing) = ":" ++ altShow ns
186+
altShow (DS1 ns (Just ns2)) = ":" ++ altShow ns ++ ":" ++ altShow ns2
187+
altShow (DS2 ns) = "::" ++ altShow ns
188+
189+
instance Arbitrary WindowsFilePaths where
190+
arbitrary = WindowsFilePaths <$> listOf' arbitrary
191+
shrink = genericShrink
192+
193+
instance Arbitrary [Separator] where
194+
arbitrary = listOf' arbitrary
195+
shrink = genericShrink
196+
197+
instance Arbitrary a => Arbitrary (NonEmpty a) where
198+
arbitrary = NE.fromList <$> listOf1' arbitrary
199+
shrink = genericShrink
200+

0 commit comments

Comments
 (0)