Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions hackage-security/src/Hackage/Security/Util/Path.hs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ module Hackage.Security.Util.Path (
, fromURIPath
, uriPath
, modifyUriPath
-- * Internals
, mkPathNative
, unPathNative
-- * Re-exports
, IOMode(..)
, BufferMode(..)
Expand Down Expand Up @@ -118,10 +121,17 @@ newtype Path a = Path FilePath -- always a Posix style path internally
deriving (Show, Eq, Ord)

mkPathNative :: FilePath -> Path a
mkPathNative = Path . FP.Posix.joinPath . FP.Native.splitDirectories
mkPathNative = Path . canonicalizePathSeparator

unPathNative :: Path a -> FilePath
unPathNative (Path fp) = FP.Native.joinPath . FP.Posix.splitDirectories $ fp
unPathNative (Path fp) = fp

canonicalizePathSeparator :: FilePath -> FilePath
canonicalizePathSeparator = map (replaceSeparator)
where
replaceSeparator c
| FP.Native.isPathSeparator c = '/'
| otherwise = c

mkPathPosix :: FilePath -> Path a
mkPathPosix = Path
Expand Down
13 changes: 12 additions & 1 deletion hackage-security/tests/TestSuite.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Data.Time ( UTCTime, getCurrentTime )
import Network.URI ( URI, parseURI )
import Test.Tasty ( defaultMain, testGroup, TestTree )
import Test.Tasty.HUnit ( testCase, (@?=), assertEqual, assertFailure, Assertion )
import Test.Tasty.QuickCheck ( testProperty )
import Test.Tasty.QuickCheck ( testProperty, Property, (===), property )
import System.IO.Temp (withSystemTempDirectory)
import qualified Codec.Archive.Tar.Entry as Tar
import qualified Data.ByteString.Lazy.Char8 as BS
Expand Down Expand Up @@ -72,6 +72,9 @@ tests = testGroup "hackage-security" [
, testProperty "prop_canonical_pretty" JSON.prop_canonical_pretty
, testProperty "prop_aeson_canonical" JSON.prop_aeson_canonical
]
, testGroup "Path" [
testProperty "Hackage.Security.Util.Path.mkPathNative" prop_mkPathNative
]
]

{-------------------------------------------------------------------------------
Expand Down Expand Up @@ -547,3 +550,11 @@ checkExpiry = Just `fmap` getCurrentTime
mkPackageName :: String -> PackageName
mkPackageName = PackageName
#endif

{-------------------------------------------------------------------------------
Path tests
-------------------------------------------------------------------------------}

prop_mkPathNative :: Property
prop_mkPathNative = property $ \(fp :: FilePath) -> (mkPathNative . unPathNative . mkPathNative) fp === mkPathNative fp
Copy link
Member

Choose a reason for hiding this comment

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

Since FilePath = String, how likely is an interesting test generated here?

Maybe it would be good to add a few unit tests.

Copy link
Member Author

Choose a reason for hiding this comment

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

Filepath has a windows path BNF that's used to generate interesting output, but that would be some copy pasta: https://github.com/haskell/filepath/blob/master/tests/filepath-equivalent-tests/Gen.hs#L38

Copy link
Member

Choose a reason for hiding this comment

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

Do you think this generator would be worth publishing in a package we could use here?
That would be the preferred solution; copy-paste tends to multiply the maintenance effort.

If you consider this all feature-creep, you could also equip the property with a comment stating that this is the intended mathematical property but it is likely not established by the test because the coverage is very poor with the vanilla generator for FilePath = String.

Copy link
Member

@andreasabel andreasabel Feb 5, 2026

Choose a reason for hiding this comment

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

To not have this PR fizzle out, I merged it and pushed a disclaimer: 36a5fbf

My original intent was to add the disclaimer as an extra commit to this PR, but the PR wasn't set up in a way to allow contributions from the maintainers.


Loading