Skip to content

Commit 2146ea1

Browse files
authored
Merge pull request #4122 from grayjay/memory-usage-tests
Add test suite with two basic tests for solver space leaks.
2 parents e496b09 + 1129ad3 commit 2146ea1

File tree

9 files changed

+167
-10
lines changed

9 files changed

+167
-10
lines changed

appveyor.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,4 @@ build_script:
4444
- ..\cabal test integration-tests --show-details=streaming --test-option=--pattern=!exec --test-option=--hide-successes
4545
- ..\cabal test integration-tests2 --show-details=streaming --test-option=--hide-successes
4646
- ..\cabal test solver-quickcheck --show-details=streaming --test-option=--hide-successes --test-option=--quickcheck-tests=1000
47+
- ..\cabal test memory-usage-tests --show-details=streaming

cabal-install/Distribution/Solver/Modular/Explore.hs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
{-# LANGUAGE BangPatterns #-}
12
{-# LANGUAGE ScopedTypeVariables #-}
23
module Distribution.Solver.Modular.Explore
34
( backjump
@@ -60,7 +61,7 @@ backjump (EnableBackjumping enableBj) var initial xs =
6061
| otherwise = f (csAcc `CS.union` cs) cm'
6162

6263
logBackjump :: ConflictSet QPN -> ConflictMap -> ConflictSetLog a
63-
logBackjump cs cm = failWith (Failure cs Backjump) (cs, updateCM initial cm)
64+
logBackjump cs !cm = failWith (Failure cs Backjump) (cs, updateCM initial cm)
6465
-- 'intial' instead of 'cs' here ---^
6566
-- since we do not want to double-count the
6667
-- additionally accumulated conflicts.
@@ -118,11 +119,9 @@ exploreLog enableBj (CountConflicts countConflicts) t = cata go t M.empty
118119

119120
go :: TreeF Assignment QGoalReason (ConflictMap -> ConflictSetLog (Assignment, RevDepMap))
120121
-> (ConflictMap -> ConflictSetLog (Assignment, RevDepMap))
121-
go (FailF c fr) = \ cm -> let failure = failWith (Failure c fr)
122-
in if countConflicts
123-
then failure (c, updateCM c cm)
124-
else failure (c, cm)
125-
go (DoneF rdm a) = \ _ -> succeedWith Success (a, rdm)
122+
go (FailF c fr) = \ !cm -> failWith (Failure c fr)
123+
(c, updateCM c cm)
124+
go (DoneF rdm a) = \ _ -> succeedWith Success (a, rdm)
126125
go (PChoiceF qpn gr ts) =
127126
backjump enableBj (P qpn) (avoidSet (P qpn) gr) $ -- try children in order,
128127
W.mapWithKey -- when descending ...

cabal-install/Distribution/Solver/Types/Progress.hs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
{-# LANGUAGE DeriveFunctor #-}
21
module Distribution.Solver.Types.Progress
32
( Progress(..)
43
, foldProgress
@@ -14,7 +13,14 @@ import Distribution.Client.Compat.Prelude hiding (fail)
1413
data Progress step fail done = Step step (Progress step fail done)
1514
| Fail fail
1615
| Done done
17-
deriving (Functor)
16+
17+
-- This Functor instance works around a bug in GHC 7.6.3.
18+
-- See https://ghc.haskell.org/trac/ghc/ticket/7436#comment:6.
19+
-- The derived functor instance caused a space leak in the solver.
20+
instance Functor (Progress step fail) where
21+
fmap f (Step s p) = Step s (fmap f p)
22+
fmap _ (Fail x) = Fail x
23+
fmap f (Done r) = Done (f r)
1824

1925
-- | Consume a 'Progress' calculation. Much like 'foldr' for lists but with two
2026
-- base cases, one for a final result and one for failure.

cabal-install/cabal-install.cabal

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,74 @@ Test-Suite unit-tests
550550

551551
default-language: Haskell2010
552552

553+
-- Tests to run with a limited stack and heap size
554+
Test-Suite memory-usage-tests
555+
type: exitcode-stdio-1.0
556+
main-is: MemoryUsageTests.hs
557+
hs-source-dirs: tests, .
558+
ghc-options: -Wall -fwarn-tabs "-with-rtsopts=-M4M -K1K"
559+
other-modules:
560+
UnitTests.Distribution.Solver.Modular.DSL
561+
UnitTests.Distribution.Solver.Modular.DSL.TestCaseUtils
562+
UnitTests.Distribution.Solver.Modular.MemoryUsage
563+
UnitTests.Options
564+
build-depends:
565+
base,
566+
async,
567+
array,
568+
bytestring,
569+
Cabal,
570+
containers,
571+
deepseq,
572+
mtl,
573+
pretty,
574+
process,
575+
directory,
576+
filepath,
577+
hashable,
578+
stm,
579+
tar,
580+
time,
581+
HTTP,
582+
zlib,
583+
binary,
584+
random,
585+
hackage-security,
586+
tagged,
587+
tasty,
588+
tasty-hunit
589+
590+
if flag(old-directory)
591+
build-depends: old-time
592+
593+
if flag(network-uri)
594+
build-depends: network-uri >= 2.6, network >= 2.6
595+
else
596+
build-depends: network-uri < 2.6, network < 2.6
597+
598+
if impl(ghc < 7.6)
599+
build-depends: ghc-prim >= 0.2 && < 0.3
600+
601+
if os(windows)
602+
build-depends: Win32
603+
else
604+
build-depends: unix
605+
606+
ghc-options: -fno-ignore-asserts
607+
608+
if !(arch(arm) && impl(ghc < 7.6))
609+
ghc-options: -threaded
610+
611+
if flag(debug-conflict-sets)
612+
cpp-options: -DDEBUG_CONFLICT_SETS
613+
build-depends: base >= 4.8
614+
615+
if flag(debug-tracetree)
616+
cpp-options: -DDEBUG_TRACETREE
617+
build-depends: tracetree >= 0.1 && < 0.2
618+
619+
default-language: Haskell2010
620+
553621
-- Slow solver tests
554622
Test-Suite solver-quickcheck
555623
type: exitcode-stdio-1.0
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module Main where
2+
3+
import Test.Tasty
4+
5+
import qualified UnitTests.Distribution.Solver.Modular.MemoryUsage
6+
7+
tests :: TestTree
8+
tests =
9+
testGroup "Memory Usage"
10+
[ testGroup "UnitTests.Distribution.Solver.Modular.MemoryUsage"
11+
UnitTests.Distribution.Solver.Modular.MemoryUsage.tests
12+
]
13+
14+
main :: IO ()
15+
main = defaultMain tests

cabal-install/tests/UnitTests/Distribution/Solver/Modular/DSL.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ module UnitTests.Distribution.Solver.Modular.DSL (
1212
, ExampleVersionRange
1313
, ExamplePkgVersion
1414
, ExamplePkgName
15+
, ExampleFlagName
1516
, ExampleAvailable(..)
1617
, ExampleInstalled(..)
1718
, ExampleQualifier(..)

cabal-install/tests/UnitTests/Distribution/Solver/Modular/DSL/TestCaseUtils.hs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ module UnitTests.Distribution.Solver.Modular.DSL.TestCaseUtils (
44
SolverTest
55
, SolverResult(..)
66
, independentGoals
7+
, disableBackjumping
78
, goalOrder
89
, preferences
910
, enableAllTests
@@ -39,6 +40,10 @@ import UnitTests.Options
3940
independentGoals :: SolverTest -> SolverTest
4041
independentGoals test = test { testIndepGoals = IndependentGoals True }
4142

43+
disableBackjumping :: SolverTest -> SolverTest
44+
disableBackjumping test =
45+
test { testEnableBackjumping = EnableBackjumping False }
46+
4247
goalOrder :: [ExampleVar] -> SolverTest -> SolverTest
4348
goalOrder order test = test { testGoalOrder = Just order }
4449

@@ -57,6 +62,7 @@ data SolverTest = SolverTest {
5762
, testTargets :: [String]
5863
, testResult :: SolverResult
5964
, testIndepGoals :: IndependentGoals
65+
, testEnableBackjumping :: EnableBackjumping
6066
, testGoalOrder :: Maybe [ExampleVar]
6167
, testSoftConstraints :: [ExPreference]
6268
, testDb :: ExampleDb
@@ -145,6 +151,7 @@ mkTestExtLangPC exts langs pkgConfigDb db label targets result = SolverTest {
145151
, testTargets = targets
146152
, testResult = result
147153
, testIndepGoals = IndependentGoals False
154+
, testEnableBackjumping = EnableBackjumping True
148155
, testGoalOrder = Nothing
149156
, testSoftConstraints = []
150157
, testDb = db
@@ -160,7 +167,7 @@ runTest SolverTest{..} = askOption $ \(OptionShowSolverLog showSolverLog) ->
160167
let progress = exResolve testDb testSupportedExts
161168
testSupportedLangs testPkgConfigDb testTargets
162169
Modular Nothing testIndepGoals (ReorderGoals False)
163-
(EnableBackjumping True) testGoalOrder testSoftConstraints
170+
testEnableBackjumping testGoalOrder testSoftConstraints
164171
testEnableAllTests
165172
printMsg msg = if showSolverLog
166173
then putStrLn msg
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
-- | Tests for detecting space leaks in the dependency solver.
2+
module UnitTests.Distribution.Solver.Modular.MemoryUsage (tests) where
3+
4+
import Test.Tasty (TestTree)
5+
6+
import UnitTests.Distribution.Solver.Modular.DSL
7+
import UnitTests.Distribution.Solver.Modular.DSL.TestCaseUtils
8+
9+
tests :: [TestTree]
10+
tests = [
11+
runTest $ basicTest "basic space leak test"
12+
, runTest $ flagsTest "package with many flags"
13+
]
14+
15+
-- | This test solves for n packages that each have two versions. Backjumping
16+
-- is disabled, so the solver must explore a search tree of size 2^n. It should
17+
-- fail if memory usage is proportional to the size of the tree.
18+
basicTest :: String -> SolverTest
19+
basicTest name =
20+
disableBackjumping $ mkTest pkgs name ["target"] anySolverFailure
21+
where
22+
n :: Int
23+
n = 18
24+
25+
pkgs :: ExampleDb
26+
pkgs = map Right $
27+
[ exAv "target" 1 [ExAny $ pkgName 1]]
28+
++ [ exAv (pkgName i) v [ExAny $ pkgName (i + 1)]
29+
| i <- [1..n], v <- [1, 2]]
30+
31+
pkgName :: Int -> ExamplePkgName
32+
pkgName x = "pkg-" ++ show x
33+
34+
-- | This test is similar to 'basicTest', except that it has one package with n
35+
-- flags, flag-1 through flag-n. The solver assigns flags in order, so it
36+
-- doesn't discover the unknown dependencies under flag-n until it has assigned
37+
-- all of the flags. It has to explore the whole search tree.
38+
flagsTest :: String -> SolverTest
39+
flagsTest name =
40+
disableBackjumping $
41+
goalOrder orderedFlags $ mkTest pkgs name ["pkg"] anySolverFailure
42+
where
43+
n :: Int
44+
n = 16
45+
46+
pkgs :: ExampleDb
47+
pkgs = [Right $ exAv "pkg" 1 $
48+
[exFlag (flagName n) [ExAny "unknown1"] [ExAny "unknown2"]]
49+
50+
-- The remaining flags have no effect:
51+
++ [exFlag (flagName i) [] [] | i <- [1..n - 1]]
52+
]
53+
54+
flagName :: Int -> ExampleFlagName
55+
flagName x = "flag-" ++ show x
56+
57+
orderedFlags :: [ExampleVar]
58+
orderedFlags = [F None "pkg" (flagName i) | i <- [1..n]]

travis-script.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,8 @@ timed cabal new-build cabal-install:cabal \
113113
cabal-install:integration-tests \
114114
cabal-install:integration-tests2 \
115115
cabal-install:unit-tests \
116-
cabal-install:solver-quickcheck
116+
cabal-install:solver-quickcheck \
117+
cabal-install:memory-usage-tests
117118

118119
# The integration-tests2 need the hackage index, and need it in the secure
119120
# format, which is not necessarily the default format of the bootstrap cabal.
@@ -125,6 +126,7 @@ timed ${CABAL_INSTALL_BDIR}/build/cabal/cabal update
125126
(cd cabal-install && timed ${CABAL_INSTALL_BDIR}/build/solver-quickcheck/solver-quickcheck $TEST_OPTIONS --quickcheck-tests=1000) || exit $?
126127
(cd cabal-install && timed ${CABAL_INSTALL_BDIR}/build/integration-tests/integration-tests $TEST_OPTIONS) || exit $?
127128
(cd cabal-install && timed ${CABAL_INSTALL_BDIR}/build/integration-tests2/integration-tests2 $TEST_OPTIONS) || exit $?
129+
(cd cabal-install && timed ${CABAL_INSTALL_BDIR}/build/memory-usage-tests/memory-usage-tests $TEST_OPTIONS) || exit $?
128130

129131
# Haddock
130132
(cd cabal-install && timed ${CABAL_INSTALL_SETUP} haddock --builddir=${CABAL_INSTALL_BDIR} ) || exit $?

0 commit comments

Comments
 (0)