diff --git a/cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs b/cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs index 84375b0f4de..65fcfe0691e 100644 --- a/cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs +++ b/cabal-install-solver/src/Distribution/Solver/Types/ProjectConfigPath.hs @@ -30,12 +30,16 @@ import Data.Coerce (coerce) import Data.List.NonEmpty ((<|)) import Network.URI (parseURI, parseAbsoluteURI) import System.Directory -import System.FilePath +import System.FilePath hiding (splitPath) +import qualified System.FilePath as FP (splitPath) +import qualified System.FilePath.Posix as Posix +import qualified System.FilePath.Windows as Windows import qualified Data.List.NonEmpty as NE import Distribution.Solver.Modular.Version (VR) import Distribution.Pretty (prettyShow) import Text.PrettyPrint import Distribution.Simple.Utils (ordNub) +import Distribution.System (OS(Windows), buildOS) -- | Path to a configuration file, either a singleton project root, or a longer -- list representing a path to an import. The path is a non-empty list that we @@ -58,6 +62,14 @@ newtype ProjectConfigPath = ProjectConfigPath (NonEmpty FilePath) -- The project itself, a single element root path, compared to any of the -- configuration paths it imports, should always sort first. Comparing one -- project root path against another is done lexically. +-- +-- For comparison purposes, path separators are normalized to the @buildOS@ +-- platform's path separator. +-- +-- >>> let abFwd = ProjectConfigPath $ "a/b.config" :| [] +-- >>> let abBwd = ProjectConfigPath $ "a\\b.config" :| [] +-- >>> compare abFwd abBwd +-- EQ instance Ord ProjectConfigPath where compare pa@(ProjectConfigPath (NE.toList -> as)) pb@(ProjectConfigPath (NE.toList -> bs)) = case (as, bs) of @@ -66,7 +78,7 @@ instance Ord ProjectConfigPath where -- this though, do a comparison anyway when both sides have length -- 1. The root path, the project itself, should always be the first -- path in a sorted listing. - ([a], [b]) -> compare a b + ([a], [b]) -> compare (splitPath a) (splitPath b) ([_], _) -> LT (_, [_]) -> GT @@ -80,6 +92,16 @@ instance Ord ProjectConfigPath where P.<> compare (length aPaths) (length bPaths) P.<> compare aPaths bPaths where + splitPath = FP.splitPath . normSep where + normSep p = + if buildOS == Windows + then + Windows.joinPath $ Windows.splitDirectories + [if Posix.isPathSeparator c then Windows.pathSeparator else c| c <- p] + else + Posix.joinPath $ Posix.splitDirectories + [if Windows.isPathSeparator c then Posix.pathSeparator else c| c <- p] + aPaths = splitPath <$> as bPaths = splitPath <$> bs aImporters = snd $ unconsProjectConfigPath pa diff --git a/cabal-testsuite/PackageTests/BuildDeps/InternalLibrary2/setup.test.hs b/cabal-testsuite/PackageTests/BuildDeps/InternalLibrary2/setup.test.hs index e36e33823d2..6b6da17f116 100644 --- a/cabal-testsuite/PackageTests/BuildDeps/InternalLibrary2/setup.test.hs +++ b/cabal-testsuite/PackageTests/BuildDeps/InternalLibrary2/setup.test.hs @@ -6,4 +6,4 @@ main = setupAndCabalTest . withPackageDb $ do assertEqual ("executable should have linked with the internal library") ("foo foo myLibFunc internal") - (concatOutput (resultOutput r)) + (lineBreaksToSpaces $ resultOutput r) diff --git a/cabal-testsuite/PackageTests/BuildDeps/InternalLibrary3/setup.test.hs b/cabal-testsuite/PackageTests/BuildDeps/InternalLibrary3/setup.test.hs index 549e8bf8bb4..ac05c394383 100644 --- a/cabal-testsuite/PackageTests/BuildDeps/InternalLibrary3/setup.test.hs +++ b/cabal-testsuite/PackageTests/BuildDeps/InternalLibrary3/setup.test.hs @@ -8,4 +8,4 @@ main = setupAndCabalTest . withPackageDb $ do assertEqual ("executable should have linked with the internal library") ("foo foo myLibFunc internal") - (concatOutput (resultOutput r)) + (lineBreaksToSpaces $ resultOutput r) diff --git a/cabal-testsuite/PackageTests/CheckSetup/setup.test.hs b/cabal-testsuite/PackageTests/CheckSetup/setup.test.hs index 96ed4395785..67997e7e21b 100644 --- a/cabal-testsuite/PackageTests/CheckSetup/setup.test.hs +++ b/cabal-testsuite/PackageTests/CheckSetup/setup.test.hs @@ -13,8 +13,12 @@ main = cabalTest $ do "The dependency 'setup-depends: 'base' does not specify " ++ "an upper bound on the version number" + -- Replace line breaks with spaces in the haystack so that we can search + -- for a string that wraps lines. + let lineBreakBlind = needleHaystack{txHaystack = txContainsId{txFwd = lineBreaksToSpaces}} + -- Asserts for the desired check messages after configure. - assertOutputContains libError1 checkResult - assertOutputContains libError2 checkResult + assertOn lineBreakBlind libError1 checkResult + assertOn lineBreakBlind libError2 checkResult return () diff --git a/cabal-testsuite/PackageTests/ConditionalAndImport/cabal.test.hs b/cabal-testsuite/PackageTests/ConditionalAndImport/cabal.test.hs index 67118d362c0..d0abb33de2d 100644 --- a/cabal-testsuite/PackageTests/ConditionalAndImport/cabal.test.hs +++ b/cabal-testsuite/PackageTests/ConditionalAndImport/cabal.test.hs @@ -1,7 +1,6 @@ import Test.Cabal.Prelude - -normalizeWindowsOutput :: String -> String -normalizeWindowsOutput = if isWindows then map (\x -> case x of '/' -> '\\'; _ -> x) else id +import Test.Cabal.OutputNormalizer +import Data.Function ((&)) main = cabalTest . withRepo "repo" . recordMode RecordMarked $ do let log = recordHeader . pure @@ -111,89 +110,65 @@ main = cabalTest . withRepo "repo" . recordMode RecordMarked $ do -- +-- hops/hops-9.config (no further imports so not cyclical) log "checking that imports work skipping into a subfolder and then back out again and again" hopping <- cabal' "v2-build" [ "--project-file=hops-0.project" ] - assertOutputContains "Configuration is affected by the following files" hopping - assertOutputContains "- hops-0.project" hopping - - assertOutputContains - (normalizeWindowsOutput "- hops-2.config \ - \ imported by: hops/hops-1.config \ - \ imported by: hops-0.project") - hopping - - assertOutputContains - (normalizeWindowsOutput "- hops-4.config \ - \ imported by: hops/hops-3.config \ - \ imported by: hops-2.config \ - \ imported by: hops/hops-1.config \ - \ imported by: hops-0.project") - hopping - - assertOutputContains - (normalizeWindowsOutput "- hops-6.config \ - \ imported by: hops/hops-5.config \ - \ imported by: hops-4.config \ - \ imported by: hops/hops-3.config \ - \ imported by: hops-2.config \ - \ imported by: hops/hops-1.config \ - \ imported by: hops-0.project") - hopping - - assertOutputContains - (normalizeWindowsOutput "- hops-8.config \ - \ imported by: hops/hops-7.config \ - \ imported by: hops-6.config \ - \ imported by: hops/hops-5.config \ - \ imported by: hops-4.config \ - \ imported by: hops/hops-3.config \ - \ imported by: hops-2.config \ - \ imported by: hops/hops-1.config \ - \ imported by: hops-0.project") - hopping - assertOutputContains - (normalizeWindowsOutput "- hops/hops-1.config \ - \ imported by: hops-0.project") - hopping - - assertOutputContains - (normalizeWindowsOutput "- hops/hops-3.config \ - \ imported by: hops-2.config \ - \ imported by: hops/hops-1.config \ - \ imported by: hops-0.project") - hopping - - assertOutputContains - (normalizeWindowsOutput "- hops/hops-5.config \ - \ imported by: hops-4.config \ - \ imported by: hops/hops-3.config \ - \ imported by: hops-2.config \ - \ imported by: hops/hops-1.config \ - \ imported by: hops-0.project") - hopping - - assertOutputContains - (normalizeWindowsOutput "- hops/hops-7.config \ - \ imported by: hops-6.config \ - \ imported by: hops/hops-5.config \ - \ imported by: hops-4.config \ - \ imported by: hops/hops-3.config \ - \ imported by: hops-2.config \ - \ imported by: hops/hops-1.config \ - \ imported by: hops-0.project") - hopping - - assertOutputContains - (normalizeWindowsOutput "- hops/hops-9.config \ - \ imported by: hops-8.config \ - \ imported by: hops/hops-7.config \ - \ imported by: hops-6.config \ - \ imported by: hops/hops-5.config \ - \ imported by: hops-4.config \ - \ imported by: hops/hops-3.config \ - \ imported by: hops-2.config \ - \ imported by: hops/hops-1.config \ - \ imported by: hops-0.project") - hopping + "Configuration is affected by the following files:\n\ + \- hops-0.project\n\ + \- hops-2.config\n\ + \ imported by: hops/hops-1.config\n\ + \ imported by: hops-0.project\n\ + \- hops-4.config\n\ + \ imported by: hops/hops-3.config\n\ + \ imported by: hops-2.config\n\ + \ imported by: hops/hops-1.config\n\ + \ imported by: hops-0.project\n\ + \- hops-6.config\n\ + \ imported by: hops/hops-5.config\n\ + \ imported by: hops-4.config\n\ + \ imported by: hops/hops-3.config\n\ + \ imported by: hops-2.config\n\ + \ imported by: hops/hops-1.config\n\ + \ imported by: hops-0.project\n\ + \- hops-8.config\n\ + \ imported by: hops/hops-7.config\n\ + \ imported by: hops-6.config\n\ + \ imported by: hops/hops-5.config\n\ + \ imported by: hops-4.config\n\ + \ imported by: hops/hops-3.config\n\ + \ imported by: hops-2.config\n\ + \ imported by: hops/hops-1.config\n\ + \ imported by: hops-0.project\n\ + \- hops/hops-1.config\n\ + \ imported by: hops-0.project\n\ + \- hops/hops-3.config\n\ + \ imported by: hops-2.config\n\ + \ imported by: hops/hops-1.config\n\ + \ imported by: hops-0.project\n\ + \- hops/hops-5.config\n\ + \ imported by: hops-4.config\n\ + \ imported by: hops/hops-3.config\n\ + \ imported by: hops-2.config\n\ + \ imported by: hops/hops-1.config\n\ + \ imported by: hops-0.project\n\ + \- hops/hops-7.config\n\ + \ imported by: hops-6.config\n\ + \ imported by: hops/hops-5.config\n\ + \ imported by: hops-4.config\n\ + \ imported by: hops/hops-3.config\n\ + \ imported by: hops-2.config\n\ + \ imported by: hops/hops-1.config\n\ + \ imported by: hops-0.project\n\ + \- hops/hops-9.config\n\ + \ imported by: hops-8.config\n\ + \ imported by: hops/hops-7.config\n\ + \ imported by: hops-6.config\n\ + \ imported by: hops/hops-5.config\n\ + \ imported by: hops-4.config\n\ + \ imported by: hops/hops-3.config\n\ + \ imported by: hops-2.config\n\ + \ imported by: hops/hops-1.config\n\ + \ imported by: hops-0.project" + & normalizeWindowsOutput + & flip (assertOn multilineNeedleHaystack) hopping -- The project is named oops as it is like hops but has conflicting constraints. -- +-- oops-0.project @@ -208,22 +183,25 @@ main = cabalTest . withRepo "repo" . recordMode RecordMarked $ do -- +-- oops/oops-9.config (has conflicting constraints) log "checking conflicting constraints skipping into a subfolder and then back out again and again" oopsing <- fails $ cabal' "v2-build" [ "all", "--project-file=oops-0.project" ] - assertOutputContains "rejecting: hashable-1.4.2.0" oopsing - assertOutputContains "rejecting: hashable-1.4.3.0" oopsing - assertOutputContains "(constraint from oops-0.project requires ==1.4.3.0)" oopsing - assertOutputContains - (normalizeWindowsOutput " (constraint from oops/oops-9.config requires ==1.4.2.0) \ - \ imported by: oops-8.config \ - \ imported by: oops/oops-7.config \ - \ imported by: oops-6.config \ - \ imported by: oops/oops-5.config \ - \ imported by: oops-4.config \ - \ imported by: oops/oops-3.config \ - \ imported by: oops-2.config \ - \ imported by: oops/oops-1.config \ - \ imported by: oops-0.project") - oopsing + "Could not resolve dependencies:\n\ + \[__0] trying: oops-0.1 (user goal)\n\ + \[__1] next goal: hashable (dependency of oops)\n\ + \[__1] rejecting: hashable-1.4.3.0\n\ + \ (constraint from oops/oops-9.config requires ==1.4.2.0)\n\ + \ imported by: oops-8.config\n\ + \ imported by: oops/oops-7.config\n\ + \ imported by: oops-6.config\n\ + \ imported by: oops/oops-5.config\n\ + \ imported by: oops-4.config\n\ + \ imported by: oops/oops-3.config\n\ + \ imported by: oops-2.config\n\ + \ imported by: oops/oops-1.config\n\ + \ imported by: oops-0.project\n\ + \[__1] rejecting: hashable-1.4.2.0\n\ + \ (constraint from oops-0.project requires ==1.4.3.0)" + & normalizeWindowsOutput + & flip (assertOn multilineNeedleHaystack) oopsing -- The project is named yops as it is like hops but with y's for forks. -- +-- yops-0.project @@ -264,13 +242,14 @@ main = cabalTest . withRepo "repo" . recordMode RecordMarked $ do log "checking that missing package message lists configuration provenance" missing <- fails $ cabal' "v2-build" [ "--project-file=cabal-missing-package.project" ] - assertOutputContains - (normalizeWindowsOutput "When using configuration from: \ - \ - cabal-missing-package.project \ - \ - missing/pkgs.config \ - \ - missing/pkgs/default.config \ - \The following errors occurred: \ - \ - The package location 'pkg-doesnt-exist' does not exist.") - missing + + "When using configuration from:\n\ + \ - cabal-missing-package.project\n\ + \ - missing/pkgs.config\n\ + \ - missing/pkgs/default.config\n\ + \The following errors occurred:\n\ + \ - The package location 'pkg-doesnt-exist' does not exist." + & normalizeWindowsOutput + & flip (assertOn multilineNeedleHaystack) missing return () diff --git a/cabal-testsuite/PackageTests/NewBuild/T4288/cabal.test.hs b/cabal-testsuite/PackageTests/NewBuild/T4288/cabal.test.hs index 3e3b8de853e..3313f596546 100644 --- a/cabal-testsuite/PackageTests/NewBuild/T4288/cabal.test.hs +++ b/cabal-testsuite/PackageTests/NewBuild/T4288/cabal.test.hs @@ -1,4 +1,5 @@ import Test.Cabal.Prelude +import Data.Function ((&)) -- This test is similar to the simplified example in issue #4288. The package's -- setup script only depends on base and setup-helper. setup-helper exposes a @@ -10,8 +11,7 @@ main = cabalTest $ do skipUnless "no v2-build compatible boot-Cabal" =<< hasNewBuildCompatBootCabal r <- recordMode DoNotRecord $ cabal' "v2-build" ["T4288"] assertOutputContains "This is setup-helper-1.0." r - assertOutputContains - ("In order, the following will be built: " - ++ " - setup-helper-1.0 (lib:setup-helper) (first run) " - ++ " - T4288-1.0 (lib:T4288) (first run)") - r + "In order, the following will be built:\n\ + \ - setup-helper-1.0 (lib:setup-helper) (first run)\n\ + \ - T4288-1.0 (lib:T4288) (first run)" + & flip (assertOn multilineNeedleHaystack) r diff --git a/cabal-testsuite/PackageTests/ProjectImport/DedupUsingConfigFromComplex/cabal.test.hs b/cabal-testsuite/PackageTests/ProjectImport/DedupUsingConfigFromComplex/cabal.test.hs index e354b356d7f..0c65ff68c60 100644 --- a/cabal-testsuite/PackageTests/ProjectImport/DedupUsingConfigFromComplex/cabal.test.hs +++ b/cabal-testsuite/PackageTests/ProjectImport/DedupUsingConfigFromComplex/cabal.test.hs @@ -1,4 +1,5 @@ import Test.Cabal.Prelude +import Data.Function ((&)) main = cabalTest . recordMode RecordMarked $ do let log = recordHeader . pure @@ -31,12 +32,12 @@ main = cabalTest . recordMode RecordMarked $ do out log "checking that package directories and locations are reported in order" - assertOutputContains - "The following errors occurred: \ - \ - The package directory 'no-pkg-1' does not contain any .cabal file. \ - \ - The package location 'no-pkg-2-dir' does not exist. \ - \ - The package directory 'no-pkg-3' does not contain any .cabal file. \ - \ - The package location 'no-pkg-4-dir' does not exist." - out + + "The following errors occurred:\n\ + \ - The package directory 'no-pkg-1' does not contain any .cabal file.\n\ + \ - The package location 'no-pkg-2-dir' does not exist.\n\ + \ - The package directory 'no-pkg-3' does not contain any .cabal file.\n\ + \ - The package location 'no-pkg-4-dir' does not exist." + & flip (assertOn multilineNeedleHaystack) out return () diff --git a/cabal-testsuite/PackageTests/ProjectImport/ParseErrorProvenance/cabal.out b/cabal-testsuite/PackageTests/ProjectImport/ParseErrorProvenance/cabal.out new file mode 100644 index 00000000000..7a5d5d5c6f0 --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/ParseErrorProvenance/cabal.out @@ -0,0 +1,10 @@ +# cabal v2-build +Warning: /else.project, else.project: Unrecognized section '_' on line 3 +# Multiline string marking: +# ^When using configuration from:$ +# ^ - else.project$ +# ^ - dir-else/else.config$ +# ^The following errors occurred:$ +# ^ - The package location 'no-pkg-here' does not exist.$ +# Pseudo multiline string marking: +# ^When using configuration from: - else.project - dir-else/else.configThe following errors occurred: - The package location 'no-pkg-here' does not exist.$ diff --git a/cabal-testsuite/PackageTests/ProjectImport/ParseErrorProvenance/cabal.test.hs b/cabal-testsuite/PackageTests/ProjectImport/ParseErrorProvenance/cabal.test.hs new file mode 100644 index 00000000000..f4b48678158 --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/ParseErrorProvenance/cabal.test.hs @@ -0,0 +1,29 @@ +import Test.Cabal.Prelude + +main = cabalTest . recordMode RecordMarked $ do + let log = recordHeader . pure + + let expectMulti = + "When using configuration from:\n\ + \ - else.project\n\ + \ - dir-else/else.config\n\ + \The following errors occurred:\n\ + \ - The package location 'no-pkg-here' does not exist." + + outElse <- fails $ cabal' "v2-build" [ "all", "--dry-run", "--project-file=else.project" ] + + let expectSingle = filter (/= '\n') expectMulti + + log "Multiline string marking:" + mapM_ log (lines . decodeLfMarkLines $ encodeLf expectMulti) + + log "Pseudo multiline string marking:" + mapM_ log (lines . decodeLfMarkLines $ encodeLf expectSingle) + + assertOn multilineNeedleHaystack expectMulti outElse + assertOn multilineNeedleHaystack{expectNeedleInHaystack = False} expectSingle outElse + + assertOutputDoesNotContain expectMulti outElse + assertOutputDoesNotContain expectSingle outElse + + return () diff --git a/cabal-testsuite/PackageTests/ProjectImport/ParseErrorProvenance/dir-else/else.config b/cabal-testsuite/PackageTests/ProjectImport/ParseErrorProvenance/dir-else/else.config new file mode 100644 index 00000000000..f9c44e63d5b --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/ParseErrorProvenance/dir-else/else.config @@ -0,0 +1,4 @@ +if false +else + _ + packages: no-pkg-here diff --git a/cabal-testsuite/PackageTests/ProjectImport/ParseErrorProvenance/else.project b/cabal-testsuite/PackageTests/ProjectImport/ParseErrorProvenance/else.project new file mode 100644 index 00000000000..959c40f5660 --- /dev/null +++ b/cabal-testsuite/PackageTests/ProjectImport/ParseErrorProvenance/else.project @@ -0,0 +1 @@ +import: dir-else/else.config diff --git a/cabal-testsuite/PackageTests/Regression/T5409/use-different-versions-of-dependency-for-library-and-build-tool.test.hs b/cabal-testsuite/PackageTests/Regression/T5409/use-different-versions-of-dependency-for-library-and-build-tool.test.hs index 2a3eb3c093c..4bbb8b91a9b 100644 --- a/cabal-testsuite/PackageTests/Regression/T5409/use-different-versions-of-dependency-for-library-and-build-tool.test.hs +++ b/cabal-testsuite/PackageTests/Regression/T5409/use-different-versions-of-dependency-for-library-and-build-tool.test.hs @@ -1,4 +1,5 @@ import Test.Cabal.Prelude +import Data.Function ((&)) -- The local package, pkg-1.0, depends on build-tool-pkg-1 as a library and -- build-tool-pkg-2 as a build-tool. This test checks that cabal uses the @@ -16,16 +17,13 @@ main = cabalTest $ withShorterPathForNewBuildStore $ do r1 <- recordMode DoNotRecord $ cabal' "v2-build" ["pkg:my-exe"] - let msg = concat - [ "In order, the following will be built:" - , " - build-tool-pkg-1 (lib) (requires build)" - , " - build-tool-pkg-2 (lib) (requires build)" - , " - build-tool-pkg-2 (exe:build-tool-exe) (requires build)" - , " - pkg-1.0 (exe:my-exe) (first run)" - ] + "In order, the following will be built:\n\ + \ - build-tool-pkg-1 (lib) (requires build)\n\ + \ - build-tool-pkg-2 (lib) (requires build)\n\ + \ - build-tool-pkg-2 (exe:build-tool-exe) (requires build)\n\ + \ - pkg-1.0 (exe:my-exe) (first run)" + & flip (assertOn multilineNeedleHaystack) r1 - assertOutputContains msg r1 withPlan $ do r2 <- runPlanExe' "pkg" "my-exe" [] - assertOutputContains - "build-tool library version: 1, build-tool exe version: 2" r2 + assertOn multilineNeedleHaystack "build-tool library version: 1,\nbuild-tool exe version: 2" r2 diff --git a/cabal-testsuite/src/Test/Cabal/Prelude.hs b/cabal-testsuite/src/Test/Cabal/Prelude.hs index d8cee954d83..656bd41e95b 100644 --- a/cabal-testsuite/src/Test/Cabal/Prelude.hs +++ b/cabal-testsuite/src/Test/Cabal/Prelude.hs @@ -1,8 +1,10 @@ -{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NondecreasingIndentation #-} -{-# LANGUAGE FlexibleContexts #-} -{-# LANGUAGE CPP #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE ViewPatterns #-} -- | Generally useful definitions that we expect most test scripts -- to use. @@ -795,19 +797,68 @@ recordMode mode = withReaderT (\env -> env { testRecordUserMode = Just mode }) -assertOutputContains :: MonadIO m => WithCallStack (String -> Result -> m ()) -assertOutputContains needle result = +-- | Transformations for the search strings and the text to search in. +data TxContains = + TxContains + { + -- | Reverse conversion for display, applied to the forward converted value. + txBwd :: (String -> String), + -- | Forward conversion for comparison. + txFwd :: (String -> String) + } + +txContainsId :: TxContains +txContainsId = TxContains id id + +-- | Conversions of the needle and haystack strings, the seach string and the +-- text to search in. +data NeedleHaystack = + NeedleHaystack + { + expectNeedleInHaystack :: Bool, + displayHaystack :: Bool, + txNeedle :: TxContains, + txHaystack :: TxContains + } + +-- | Symmetric needle and haystack functions, the same conversion for each going +-- forward and the same coversion for each going backward. +symNeedleHaystack :: (String -> String) -> (String -> String) -> NeedleHaystack +symNeedleHaystack bwd fwd = let tx = TxContains bwd fwd in NeedleHaystack True False tx tx + +multilineNeedleHaystack :: NeedleHaystack +multilineNeedleHaystack = symNeedleHaystack decodeLfMarkLines encodeLf + +-- | Needle and haystack functions that do not change the strings. Set up for +-- finding the needle in the haystack and not displaying the line-delimited +-- haystack. +needleHaystack :: NeedleHaystack +needleHaystack = NeedleHaystack True False txContainsId txContainsId + +assertOn :: MonadIO m => WithCallStack (NeedleHaystack -> String -> Result -> m ()) +assertOn NeedleHaystack{..} (txFwd txNeedle -> needle) (txFwd txHaystack. resultOutput -> output) = withFrozenCallStack $ - unless (needle `isInfixOf` (concatOutput output)) $ - assertFailure $ " expected: " ++ needle - where output = resultOutput result + if expectNeedleInHaystack + then unless (needle `isInfixOf` output) + $ assertFailure $ "expected:\n" ++ (txBwd txNeedle needle) ++ + if displayHaystack + then "\nin output:\n" ++ (txBwd txHaystack output) + else "" + else when (needle `isInfixOf` output) + $ assertFailure $ "unexpected:\n" ++ (txBwd txNeedle needle) ++ + if displayHaystack + then "\nin output:\n" ++ (txBwd txHaystack output) + else "" + +assertOutputContains :: MonadIO m => WithCallStack (String -> Result -> m ()) +assertOutputContains = assertOn needleHaystack{txHaystack = TxContains{txBwd = decodeLfMarkLines, txFwd = encodeLf}} assertOutputDoesNotContain :: MonadIO m => WithCallStack (String -> Result -> m ()) -assertOutputDoesNotContain needle result = - withFrozenCallStack $ - when (needle `isInfixOf` (concatOutput output)) $ - assertFailure $ "unexpected: " ++ needle - where output = resultOutput result +assertOutputDoesNotContain = assertOn + needleHaystack + { expectNeedleInHaystack = False + , txHaystack = TxContains{txBwd = decodeLfMarkLines, txFwd = encodeLf} + } assertFindInFile :: MonadIO m => WithCallStack (String -> FilePath -> m ()) assertFindInFile needle path = @@ -862,8 +913,64 @@ assertNoFileContains paths needle = assertFileDoesNotContain path needle -- | Replace line breaks with spaces, correctly handling "\r\n". -concatOutput :: String -> String -concatOutput = unwords . lines . filter ((/=) '\r') +-- +-- >>> lineBreaksToSpaces "foo\nbar\r\nbaz" +-- "foo bar baz" +-- +-- >>> lineBreaksToSpaces "foo\nbar\r\nbaz\n" +-- "foo bar baz" +-- +-- >>> lineBreaksToSpaces "\nfoo\nbar\r\nbaz\n" +-- " foo bar baz" +lineBreaksToSpaces :: String -> String +lineBreaksToSpaces = unwords . lines . filter ((/=) '\r') + +-- | Replace line breaks with , correctly handling "\r\n". +-- +-- >>> encodeLf "foo\nbar\r\nbaz" +-- "foobarbaz" +-- +-- >>> encodeLf "foo\nbar\r\nbaz\n" +-- "foobarbaz" +-- +-- >>> encodeLf "\nfoo\nbar\r\nbaz\n" +-- "foobarbaz" +encodeLf :: String -> String +encodeLf = + (\s -> if "" `isPrefixOf` s then drop 5 s else s) + . concat + . (fmap ("" ++)) + . lines + . filter ((/=) '\r') + +-- | Replace markers with line breaks and wrap lines with ^ and $ markers +-- for the start and end. +-- +-- >>> decodeLfMarkLines "foobarbaz" +-- "^foo$\n^bar$\n^baz$\n" +-- +-- >>> decodeLfMarkLines "foobarbaz" +-- "^foo$\n^bar$\n^baz$\n" +decodeLfMarkLines:: String -> String +decodeLfMarkLines output = + (\xs -> case reverse $ lines xs of + [] -> xs + [line0] -> line0 ++ "$" + lineN : ys -> + let lineN' = lineN ++ "$" + in unlines $ reverse (lineN' : ys)) + . unlines + . (fmap ('^' :)) + . lines + . (\s -> if "" `isPrefixOf` s then drop 5 s else s) + $ foldr + (\c acc -> c : + if ("" `isPrefixOf` acc) + then "$\n" ++ drop 5 acc + else acc + ) + "" + output -- | The directory where script build artifacts are expected to be cached getScriptCacheDirectory :: FilePath -> TestM FilePath @@ -1093,6 +1200,9 @@ flakyIfCI ticket m = do flakyIfWindows :: IssueID -> TestM a -> TestM a flakyIfWindows ticket m = flakyIf isWindows ticket m +normalizeWindowsOutput :: String -> String +normalizeWindowsOutput = if isWindows then map (\x -> case x of '/' -> '\\'; _ -> x) else id + getOpenFilesLimit :: TestM (Maybe Integer) #ifdef mingw32_HOST_OS -- No MS-specified limit, was determined experimentally on Windows 10 Pro x64, diff --git a/changelog.d/pr-10646 b/changelog.d/pr-10646 new file mode 100644 index 00000000000..ab1366afaa9 --- /dev/null +++ b/changelog.d/pr-10646 @@ -0,0 +1,34 @@ +--- +synopsis: Deduplicate path separator duplicates +packages: [cabal-install-solver] +prs: 10646 +issues: 10645 +--- + +The "using configuration from" message no longer has duplicates on Windows when +the `cabal.project` uses forward slashes for its imports but the message reports +imports with backslashes. + +```diff +$ cat cabal.project +import: dir-a/b.config + +$ cabal build all --dry-run +... +When using configuration from: +- - dir-a/b.config + - dir-a\b.config + - cabal.project +``` + +## Ord ProjectConfigPath Instance Changes + +For comparison purposes, path separators are normalized to the @buildOS@ +platform's path separator. + +```haskell +-- >>> let abFwd = ProjectConfigPath $ "a/b.config" :| [] +-- >>> let abBwd = ProjectConfigPath $ "a\\b.config" :| [] +-- >>> compare abFwd abBwd +-- EQ +```