Skip to content

Commit cbd0c5d

Browse files
Minimal test suite (#81)
* Minimal test suite - Add support for urls with file: schema; both absolute (file:/path) and relative (file:path) paths are supported. - Log curl invocation in case of failure - Rename fetchRemoteAsset to fetchURL - Add verbosity flag - Bump GHC to 9.4.7 - Bump flake inputs * Apply suggestions from code review Co-authored-by: Michael Peyton Jones <me@michaelpj.com> * Add short option '-v' for '--verbosity' * Whitespace * Add comment explaining why the dot * Rename withFixture to inTemporaryDirectoryWithFixture * Small refactor of PrepareSource * Rename TarballSource to URISource - Move sourceUrl to Foliage.Meta.packageVersionSourceToUri * Simplify inTemporaryDirectoryWithFixture * Document tar and cp flags * Reformat --------- Co-authored-by: Michael Peyton Jones <me@michaelpj.com>
1 parent 1c06741 commit cbd0c5d

File tree

19 files changed

+361
-137
lines changed

19 files changed

+361
-137
lines changed

.github/workflows/nix.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,5 @@ jobs:
4848
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
4949

5050
- run: nix build --accept-flake-config
51+
52+
- run: nix build --accept-flake-config .#checks.x86_64-linux.foliage:test:foliage-test-suite

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@ cabal.project.local~
3131
_cache
3232
_keys
3333
_repo
34-
_sources
34+
35+
# only at the root since we need to check-in testcases _sources
36+
./_sources

app/Foliage/CmdBuild.hs

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ import Data.ByteString.Lazy.Char8 qualified as BL
1515
import Data.List (sortOn)
1616
import Data.List.NonEmpty qualified as NE
1717
import Data.Maybe (fromMaybe)
18-
import Data.Text qualified as T
1918
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
2019
import Data.Traversable (for)
2120
import Development.Shake
2221
import Development.Shake.FilePath
2322
import Distribution.Package
2423
import Distribution.Pretty (prettyShow)
2524
import Distribution.Version
25+
import Foliage.FetchURL (addFetchURLRule)
2626
import Foliage.HackageSecurity hiding (ToJSON, toJSON)
2727
import Foliage.Meta
2828
import Foliage.Meta.Aeson ()
@@ -31,19 +31,17 @@ import Foliage.Pages
3131
import Foliage.PreparePackageVersion (PreparedPackageVersion (..), preparePackageVersion)
3232
import Foliage.PrepareSdist (addPrepareSdistRule)
3333
import Foliage.PrepareSource (addPrepareSourceRule)
34-
import Foliage.RemoteAsset (addFetchRemoteAssetRule)
3534
import Foliage.Shake
3635
import Foliage.Time qualified as Time
3736
import Hackage.Security.Util.Path (castRoot, toFilePath)
38-
import Network.URI (URI (uriPath, uriQuery, uriScheme), nullURI)
3937
import System.Directory (createDirectoryIfMissing)
4038

4139
cmdBuild :: BuildOptions -> IO ()
4240
cmdBuild buildOptions = do
4341
outputDirRoot <- makeAbsolute (fromFilePath (buildOptsOutputDir buildOptions))
4442
shake opts $
4543
do
46-
addFetchRemoteAssetRule cacheDir
44+
addFetchURLRule cacheDir
4745
addPrepareSourceRule (buildOptsInputDir buildOptions) cacheDir
4846
addPrepareSdistRule outputDirRoot
4947
phony "buildAction" (buildAction buildOptions)
@@ -53,7 +51,7 @@ cmdBuild buildOptions = do
5351
opts =
5452
shakeOptions
5553
{ shakeFiles = cacheDir
56-
, shakeVerbosity = Verbose
54+
, shakeVerbosity = buildOptsVerbosity buildOptions
5755
, shakeThreads = buildOptsNumThreads buildOptions
5856
}
5957

@@ -247,27 +245,12 @@ makeMetadataFile outputDir packageVersions = traced "writing metadata" $ do
247245
Aeson.object
248246
( [ "pkg-name" Aeson..= pkgName
249247
, "pkg-version" Aeson..= pkgVersion
250-
, "url" Aeson..= sourceUrl pkgVersionSource
248+
, "url" Aeson..= packageVersionSourceToUri pkgVersionSource
251249
]
252250
++ ["forced-version" Aeson..= True | pkgVersionForce]
253251
++ (case pkgTimestamp of Nothing -> []; Just t -> ["timestamp" Aeson..= t])
254252
)
255253

256-
sourceUrl :: PackageVersionSource -> URI
257-
sourceUrl (TarballSource uri Nothing) = uri
258-
sourceUrl (TarballSource uri (Just subdir)) = uri{uriQuery = "?dir=" ++ subdir}
259-
sourceUrl (GitHubSource repo rev Nothing) =
260-
nullURI
261-
{ uriScheme = "github:"
262-
, uriPath = T.unpack (unGitHubRepo repo) </> T.unpack (unGitHubRev rev)
263-
}
264-
sourceUrl (GitHubSource repo rev (Just subdir)) =
265-
nullURI
266-
{ uriScheme = "github:"
267-
, uriPath = T.unpack (unGitHubRepo repo) </> T.unpack (unGitHubRev rev)
268-
, uriQuery = "?dir=" ++ subdir
269-
}
270-
271254
getPackageVersions :: FilePath -> Action [PreparedPackageVersion]
272255
getPackageVersions inputDir = do
273256
metaFiles <- getDirectoryFiles inputDir ["*/*/meta.toml"]

app/Foliage/CmdImportIndex.hs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ importIndex f (Tar.Next e es) m =
6262
pure $
6363
Just $
6464
PackageVersionSpec
65-
{ packageVersionSource = TarballSource (pkgIdToHackageUrl pkgId) Nothing
65+
{ packageVersionSource = URISource (pkgIdToHackageUrl pkgId) Nothing
6666
, packageVersionTimestamp = Just time
6767
, packageVersionRevisions = []
6868
, packageVersionDeprecations = []

app/Foliage/RemoteAsset.hs renamed to app/Foliage/FetchURL.hs

Lines changed: 44 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@
22
{-# LANGUAGE DerivingVia #-}
33
{-# LANGUAGE TypeFamilies #-}
44

5-
module Foliage.RemoteAsset (
6-
fetchRemoteAsset,
7-
addFetchRemoteAssetRule,
5+
module Foliage.FetchURL (
6+
fetchURL,
7+
addFetchURLRule,
88
)
99
where
1010

@@ -24,23 +24,23 @@ import Network.URI.Orphans ()
2424
import System.Directory (createDirectoryIfMissing)
2525
import System.Exit (ExitCode (..))
2626

27-
newtype RemoteAsset = RemoteAsset URI
27+
newtype FetchURL = FetchURL URI
2828
deriving (Eq)
2929
deriving (Hashable, Binary, NFData) via URI
3030

31-
instance Show RemoteAsset where
32-
show (RemoteAsset uri) = "fetchRemoteAsset " ++ show uri
31+
instance Show FetchURL where
32+
show (FetchURL uri) = "fetchURL " ++ show uri
3333

34-
type instance RuleResult RemoteAsset = FilePath
34+
type instance RuleResult FetchURL = FilePath
3535

36-
fetchRemoteAsset :: URI -> Action FilePath
37-
fetchRemoteAsset = apply1 . RemoteAsset
36+
fetchURL :: URI -> Action FilePath
37+
fetchURL = apply1 . FetchURL
3838

39-
addFetchRemoteAssetRule :: FilePath -> Rules ()
40-
addFetchRemoteAssetRule cacheDir = addBuiltinRule noLint noIdentity run
39+
addFetchURLRule :: FilePath -> Rules ()
40+
addFetchURLRule cacheDir = addBuiltinRule noLint noIdentity run
4141
where
42-
run :: BuiltinRun RemoteAsset FilePath
43-
run (RemoteAsset uri) old _mode = do
42+
run :: BuiltinRun FetchURL FilePath
43+
run (FetchURL uri) old _mode = do
4444
unless (uriQuery uri == "") $
4545
error ("Query elements in URI are not supported: " <> show uri)
4646

@@ -68,36 +68,7 @@ addFetchRemoteAssetRule cacheDir = addBuiltinRule noLint noIdentity run
6868
runCurl :: URI -> String -> String -> Action ETag
6969
runCurl uri path etagFile = do
7070
(Exit exitCode, Stdout out) <-
71-
traced "curl" $
72-
cmd
73-
Shell
74-
[ "curl"
75-
, -- Silent or quiet mode. Do not show progress meter or error messages. Makes Curl mute.
76-
"--silent"
77-
, -- Fail fast with no output at all on server errors.
78-
"--fail"
79-
, -- If the server reports that the requested page has moved to a different location this
80-
-- option will make curl redo the request on the new place.
81-
-- NOTE: This is needed because github always replies with a redirect
82-
"--location"
83-
, -- This option makes a conditional HTTP request for the specific ETag read from the
84-
-- given file by sending a custom If-None-Match header using the stored ETag.
85-
-- For correct results, make sure that the specified file contains only a single line
86-
-- with the desired ETag. An empty file is parsed as an empty ETag.
87-
"--etag-compare"
88-
, etagFile
89-
, -- This option saves an HTTP ETag to the specified file. If no ETag is sent by the server,
90-
-- an empty file is created.
91-
"--etag-save"
92-
, etagFile
93-
, -- Write output to <file> instead of stdout.
94-
"--output"
95-
, path
96-
, "--write-out"
97-
, "%{json}"
98-
, -- URL to fetch
99-
show uri
100-
]
71+
traced "curl" $ cmd Shell curlInvocation
10172
case exitCode of
10273
ExitSuccess -> liftIO $ BS.readFile etagFile
10374
ExitFailure c -> do
@@ -112,7 +83,36 @@ runCurl uri path etagFile = do
11283
]
11384
-- We can consider displaying different messages based on some fields (e.g. response_code)
11485
Right CurlWriteOut{errormsg} ->
115-
error errormsg
86+
error $ unlines ["calling", unwords curlInvocation, "failed with", errormsg]
87+
where
88+
curlInvocation =
89+
[ "curl"
90+
, -- Silent or quiet mode. Do not show progress meter or error messages. Makes Curl mute.
91+
"--silent"
92+
, -- Fail fast with no output at all on server errors.
93+
"--fail"
94+
, -- If the server reports that the requested page has moved to a different location this
95+
-- option will make curl redo the request on the new place.
96+
-- NOTE: This is needed because github always replies with a redirect
97+
"--location"
98+
, -- This option makes a conditional HTTP request for the specific ETag read from the
99+
-- given file by sending a custom If-None-Match header using the stored ETag.
100+
-- For correct results, make sure that the specified file contains only a single line
101+
-- with the desired ETag. An empty file is parsed as an empty ETag.
102+
"--etag-compare"
103+
, etagFile
104+
, -- This option saves an HTTP ETag to the specified file. If no ETag is sent by the server,
105+
-- an empty file is created.
106+
"--etag-save"
107+
, etagFile
108+
, -- Write output to <file> instead of stdout.
109+
"--output"
110+
, path
111+
, "--write-out"
112+
, "%{json}"
113+
, -- URL to fetch
114+
show uri
115+
]
116116

117117
type ETag = BS.ByteString
118118

app/Foliage/Meta.hs

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ module Foliage.Meta (
2121
deprecationTimestamp,
2222
deprecationIsDeprecated,
2323
PackageVersionSource,
24-
pattern TarballSource,
24+
pattern URISource,
2525
pattern GitHubSource,
2626
GitHubRepo (..),
2727
GitHubRev (..),
2828
UTCTime,
2929
latestRevisionNumber,
30+
packageVersionSourceToUri,
3031
)
3132
where
3233

@@ -43,8 +44,9 @@ import Distribution.Aeson ()
4344
import Distribution.Types.Orphans ()
4445
import Foliage.Time (UTCTime)
4546
import GHC.Generics (Generic)
46-
import Network.URI (URI, parseURI)
47+
import Network.URI (URI (..), nullURI, parseURI)
4748
import Network.URI.Orphans ()
49+
import System.FilePath ((</>))
4850
import Toml (TomlCodec, (.=))
4951
import Toml qualified
5052

@@ -55,8 +57,8 @@ newtype GitHubRev = GitHubRev {unGitHubRev :: Text}
5557
deriving (Show, Eq, Binary, Hashable, NFData) via Text
5658

5759
data PackageVersionSource
58-
= TarballSource
59-
{ tarballSourceURI :: URI
60+
= URISource
61+
{ sourceURI :: URI
6062
, subdir :: Maybe String
6163
}
6264
| GitHubSource
@@ -67,13 +69,28 @@ data PackageVersionSource
6769
deriving (Show, Eq, Generic)
6870
deriving anyclass (Binary, Hashable, NFData)
6971

72+
packageVersionSourceToUri :: PackageVersionSource -> URI
73+
packageVersionSourceToUri (URISource uri Nothing) = uri
74+
packageVersionSourceToUri (URISource uri (Just subdir)) = uri{uriQuery = "?dir=" ++ subdir}
75+
packageVersionSourceToUri (GitHubSource repo rev Nothing) =
76+
nullURI
77+
{ uriScheme = "github:"
78+
, uriPath = T.unpack (unGitHubRepo repo) </> T.unpack (unGitHubRev rev)
79+
}
80+
packageVersionSourceToUri (GitHubSource repo rev (Just subdir)) =
81+
nullURI
82+
{ uriScheme = "github:"
83+
, uriPath = T.unpack (unGitHubRepo repo) </> T.unpack (unGitHubRev rev)
84+
, uriQuery = "?dir=" ++ subdir
85+
}
86+
7087
packageSourceCodec :: TomlCodec PackageVersionSource
7188
packageSourceCodec =
72-
Toml.dimatch matchTarballSource (uncurry TarballSource) tarballSourceCodec
89+
Toml.dimatch matchTarballSource (uncurry URISource) tarballSourceCodec
7390
<|> Toml.dimatch matchGitHubSource (\((repo, rev), mSubdir) -> GitHubSource repo rev mSubdir) githubSourceCodec
7491

75-
uri :: Toml.Key -> TomlCodec URI
76-
uri = Toml.textBy to from
92+
uriCodec :: Toml.Key -> TomlCodec URI
93+
uriCodec = Toml.textBy to from
7794
where
7895
to = T.pack . show
7996
from t = case parseURI (T.unpack t) of
@@ -83,11 +100,11 @@ uri = Toml.textBy to from
83100
tarballSourceCodec :: TomlCodec (URI, Maybe String)
84101
tarballSourceCodec =
85102
Toml.pair
86-
(uri "url")
103+
(uriCodec "url")
87104
(Toml.dioptional $ Toml.string "subdir")
88105

89106
matchTarballSource :: PackageVersionSource -> Maybe (URI, Maybe String)
90-
matchTarballSource (TarballSource url mSubdir) = Just (url, mSubdir)
107+
matchTarballSource (URISource url mSubdir) = Just (url, mSubdir)
91108
matchTarballSource _ = Nothing
92109

93110
gitHubRepo :: Toml.Key -> TomlCodec GitHubRepo

app/Foliage/Options.hs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,15 @@ module Foliage.Options (
1212
)
1313
where
1414

15+
import Data.Bifunctor (Bifunctor (..))
16+
import Data.Char qualified as Char
17+
import Data.List (uncons)
18+
import Development.Shake (Verbosity (..))
1519
import Development.Shake.Classes (Binary, Hashable, NFData)
1620
import Foliage.Time
1721
import GHC.Generics
1822
import Options.Applicative
23+
import Text.Read (readMaybe)
1924

2025
data Command
2126
= CreateKeys FilePath
@@ -54,6 +59,7 @@ data BuildOptions = BuildOptions
5459
, buildOptsOutputDir :: FilePath
5560
, buildOptsNumThreads :: Int
5661
, buildOptsWriteMetadata :: Bool
62+
, buildOptsVerbosity :: Verbosity
5763
}
5864

5965
buildCommand :: Parser Command
@@ -106,6 +112,15 @@ buildCommand =
106112
<> help "Write metadata in the output-directory"
107113
<> showDefault
108114
)
115+
<*> option
116+
(maybeReader (readMaybe . toUppercase))
117+
( short 'v'
118+
<> long "verbosity"
119+
<> metavar "VERBOSITY"
120+
<> help "What level of messages should be printed out [silent, error, warn, info, verbose, diagnostic]"
121+
<> showDefaultWith (toLowercase . show)
122+
<> value Info
123+
)
109124
)
110125
where
111126
signOpts =
@@ -141,7 +156,8 @@ newtype ImportIndexOptions = ImportIndexOptions
141156

142157
importIndexCommand :: Parser Command
143158
importIndexCommand =
144-
ImportIndex . ImportIndexOptions
159+
ImportIndex
160+
. ImportIndexOptions
145161
<$> optional
146162
( ImportFilter
147163
<$> strOption
@@ -157,3 +173,11 @@ importIndexCommand =
157173
)
158174
)
159175
)
176+
177+
toUppercase :: [Char] -> String
178+
toUppercase s =
179+
maybe "" (uncurry (:) . first Char.toUpper) (uncons s)
180+
181+
toLowercase :: [Char] -> String
182+
toLowercase s =
183+
maybe "" (uncurry (:) . first Char.toLower) (uncons s)

0 commit comments

Comments
 (0)