squeeze 1.0.3.0 → 1.0.4.0
raw patch · 14 files changed
+337/−197 lines, 14 filesdep ~factorydep ~toolshed
Dependency ranges changed: factory, toolshed
Files
- changelog +10/−1
- copyright +1/−1
- debian/DEBIAN/control +1/−1
- man/man1/squeeze.1 +26/−24
- squeeze.cabal +7/−5
- squeeze.spec +3/−3
- src/Main.hs +56/−72
- src/Squeeze/Control/Concurrent/DivideAndConquer.hs +3/−4
- src/Squeeze/Data/FileCombination.hs +54/−8
- src/Squeeze/Test/Data/FileCombinations.hs +49/−0
- src/Squeeze/Test/Performance.hs +18/−8
- src/Squeeze/Test/QC.hs +0/−70
- src/Squeeze/Test/QuickChecks.hs +39/−0
- src/Squeeze/Test/Squeeze.hs +70/−0
changelog view
@@ -67,9 +67,18 @@ * Added command-line flag 'includeEmpty', to allow one to include empty files & directories in any solution. 1.0.3.0 * Changed "Main" to replace calls to 'error' from inside the IO-monad, with 'Control.Monad.fail'.- * Changed "Main"; by adding a command-line option to define the seed for the random-generator used for performance-testing; to permitting 'includeEmpty' take an optional Boolean argument, rather than a mandatory one; to read 'Squeeze.Data.CommandOptions.getMinimumUsageRatio' as a 'Double', rather than a 'Rational'.+ * Changed "Main"; by adding a command-line option to define the seed for the random-generator used for performance-testing; to permit 'includeEmpty' to take an optional Boolean argument, rather than a mandatory one; to read 'Squeeze.Data.CommandOptions.getMinimumUsageRatio' as a 'Double', rather than a 'Rational'. * Renamed the accessors in "Squeeze.Data.CommandOptions", & removed 'bisectionRatio' & associated code. * Added "Squeeze.Control.Concurrent.DivideAndConquer", called from "Main" to utilise multiple CPU-cores where available. * Trapped command-line arguments to which garbage has been appended. * Changed "Data.CommandOptions" to use 'Distribution.Verbosity.Verbosity'.+1.0.4.0+ * Corrected the Copyright-statement in 'Main.main.optDescrList.printVersion'.+ * Renamed "Squeeze.Test.QC" to "Squeeze.Test.QuickChecks"; migrating its tests to a new module "Squeeze.Test.Squeeze", & added new tests 'prop_monotonicFileSize' & 'prop_uniqueFileNames', requiring package 'toolshed-0.14.0.0'; added module "Squeeze.Test.Data.FileCombinations".+ * Amended the log-messages printed by "Main".+ * Removed the command-line option 'graphPerformance', which printed only CPU-seconds rather than elapsed time, & therefore didn't correctly represent the multi-threaded implementation.+ * Removed the printing of CPU-seconds when not testing performance; this is better left to '/usr/bin/time'.+ * Compensated for the potential rejection of some virtual files after generation of a test-population.+ * Used new class 'Factory.Math.Probability.Distribution' & bumped required version to 'factory-0.2.1.0'.+ * Replaced the command-line option '--testPerformance', with '--testPerformanceContinuous' & '--testPerformanceDiscrete'.
copyright view
@@ -2,7 +2,7 @@ Dr. Alistair Ward <squeeze at functionalley dot eu>. Copyright:- Copyright (C) 2010-2011 Dr. Alistair Ward. All Rights Reserved.+ Copyright (C) 2010-2013 Dr. Alistair Ward. All Rights Reserved. Home-page: http://functionalley.eu
debian/DEBIAN/control view
@@ -1,5 +1,5 @@ Package: squeeze-Version: 1.0.3.0-1+Version: 1.0.4.0-1 Section: utils Priority: optional Architecture: i386
man/man1/squeeze.1 view
@@ -5,7 +5,8 @@ \fBsqueeze\fR [\fIOPTIONS\fR] [\fIFile-path\fR ...] .SH DESCRIPTION .PP-Finds the subset of the specified \fIFile-path\fRs, which fits into the specified space with least room to spare, whilst also meeting the minimum usage-requirements.+Finds the subset of the specified \fIFile-path\fRs, which fits into the specified space with least room to spare, whilst also meeting the minimum usage-requirements;+i.e. an instance of the "0-1 Knapsac problem". .PP Any directories amongst the specified \fIFile-path\fRs are treated as atomic units, & therefore only solutions which involve either all or none of the files contained, are returned. .PP@@ -14,13 +15,13 @@ .SH OPTIONS .SS "Selection" .TP-\fB-M\fR, \fB--maximumBytes=\fR\fIBytes\fR+\fB-M\fR \fIBytes\fR, \fB--maximumBytes=\fR\fIBytes\fR Defines the maximum available space, in bytes; defaulting to the space available on a DVD, i.e. \fB4700000000\fR bytes. .TP-\fB-m\fR, \fB--minimumUsageRatio='\fR\fIRational\fR\fB'\fR+\fB-m\fR \fIFloat\fR, \fB--minimumUsageRatio='\fR\fIFloat\fR\fB'\fR Defines the minimum acceptable usage-ratio (in the closed unit-interval [0,1]); defaulting to '\fB0.99\fR', i.e. 99% of \fBmaximumBytes\fR. .TP-\fB-z\fR\fIBool\fR, \fB--includeEmpty\fR[\fB=\fR\fIBool\fR]+\fB-z\fR[\fIBool\fR], \fB--includeEmpty\fR[\fB=\fR\fIBool\fR] When "\fBTrue\fR", any empty file or directory, can be a member of all solutions. .br The default value, in the absence of this option, is "\fBFalse\fR",@@ -30,38 +31,35 @@ .SS Test .TP \fB-q\fR, \fB--runQuickChecks\fR-Validates some invariant properties, using arbitrary data ... & then exits.+Tests the implementation, by validating some invariant properties using arbitrary data ... & then exits. .TP-\fB-r\fR\fIInt\fR, \fB--randomSeed\fR[\fB=\fR\fIInt\fR]+\fB-r\fR[\fIInt\fR], \fB--randomSeed\fR[\fB=\fR\fIInt\fR] This option takes an optional integral argument with which to seed the unique random-number generator used for all random operations. .br In the absence of this option, the random-number generator will be seeded unpredictably from the operating-system, but in the absence of only the integral argument, "\fB0\fR" will be inferred. .br-CAVEAT: it must be specified before either "\fBtestPerformance\fR" or "\fBgraphPerformance\fR".+CAVEAT: to be effective, this option must precede either "\fBtestPerformanceContinuous\fR" or "\fBtestPerformanceDiscrete\fR". .TP-\fB--testPerformance='(\fR\fIInteger\fR\fB,\fR \fBPoissonDistribution\fR \fIlambda\fR\fB)'\fR+\fB--testPerformanceContinuous='(\fR\fIInteger\fR\fB,\fR \fBLogNormalDistribution\fR \fIlocation\fR \fIscale^2\fR\fB)'\fR Measures the CPU-time required to find the best fit, for the specified number of randomly generated virtual files,-the size of which conform to the specified probability-distribution,-& the names of which reflect their size ... & then exits.+the size of which conform to the specified probability-distribution, & then exits. .br-NB: this test is performed on a single thread.+"\fIA Large-Scale Study of File-System Contents by John R. Douceur and William J. Bolosky\fR" concludes that for arbitrary file-types,+the frequency of file-sizes matches this distribution. .TP-\fB--graphPerformance='\fR\fBPoissonDistribution\fR \fIlambda\fR\fB'\fR+\fB--testPerformanceDiscrete='(\fR\fIInteger\fR\fB,\fR \fBPoissonDistribution\fR \fIlambda\fR\fB)'\fR Measures the CPU-time required to find the best fit,-for a linearly increasing number of randomly generated virtual files,-the size of which conform to the specified probability-distribution,-& the names of which reflect their size.-.br-NB: this test is performed on a single thread.+for the specified number of randomly generated virtual files,+the size of which conform to the specified probability-distribution, & then exits. .br-Doesn't normally terminate.+CAVEAT: the standard-deviation for this distribution is narrower than observed. .TP \fB--verbosity=\fR\fBSilent\fR|\fBNormal\fR|\fBVerbose\fR|\fBDeafening\fR Produces additional explanatory output where appropriate. .br-This option, if required, may need to precede other options.+CAVEAT: to be effective, this option must precede other options. .SS "Generic Program-information" .TP \fB-v\fR, \fB--version\fR@@ -107,9 +105,9 @@ We can improve on that result if we're prepared to split some of the artist-specific directories into individual albums. With the expection of more solutions from which to select, we also raise the bar for what is acceptible. .br-NB: from version \fB1.0.3.0\fR, the runtime can be passed a \fB-N\fR flag to request parallel execution on multiple cores.-Selecting this option will not only result in reduced execution-time (given appropriate hardware),-but as a side-effect will alter the selection of sub-optimal solutions to return.+NB: from version \fB1.0.3.0\fR, the runtime can be passed a \fB-N\fR flag to request parallel execution on multiple cores,+which as a side-effect, alters the selection of sub-optimal solutions to return.+Whether this option actually results in reduced execution-time, depends on the file-size frequency-distribution & on the available hardware. .IP .B squeeze -M 700000000 -m 0.99999 ArabStrap BobDylan/* JeffBuckley JohnMartyn JoniMitchell ReservoirDogsOST RichardThompson SethLakeman SusheelaRaman TeddyThompson Vangelis +RTS -N .nf@@ -137,7 +135,7 @@ .SS "REPORTING BUGS" Report bugs to \fBsqueeze\fR \fIat\fR \fBfunctionalley\fR \fIdot\fR \fBeu\fR .SH COPYRIGHT-Copyright \(co 2010 Dr. Alistair Ward+Copyright \(co 2010-2013 Dr. Alistair Ward .PP This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. .PP@@ -150,4 +148,8 @@ .IP \(bu Source-documentation is generated by \fBHaddock\fR, & is available in the distribution. .IP \(bu-.I http://www.haskell.org/haddock/+.B http://www.haskell.org/haddock/+.IP \(bu+.B http://en.wikipedia.org/wiki/Log-normal_distribution+.IP \(bu+.B http://en.wikipedia.org/wiki/Poisson_distribution
squeeze.cabal view
@@ -1,8 +1,8 @@ --Package-properties Name: squeeze-Version: 1.0.3.0+Version: 1.0.4.0 Cabal-Version: >= 1.6-Copyright: (C) 2010 Dr. Alistair Ward+Copyright: (C) 2010-2013 Dr. Alistair Ward License: GPL License-file: LICENSE Author: Dr. Alistair Ward@@ -36,19 +36,21 @@ Squeeze.Data.File Squeeze.Data.FileCombination Squeeze.Squeeze+ Squeeze.Test.Data.FileCombinations Squeeze.Test.Performance- Squeeze.Test.QC+ Squeeze.Test.QuickChecks+ Squeeze.Test.Squeeze Build-depends: base == 4.*, Cabal >= 1.6 && < 2, directory,- factory >= 0.1,+ factory >= 0.2.1.0, filepath, mtl, QuickCheck >= 2.4, random,- toolshed >= 0.13,+ toolshed >= 0.14, unix GHC-options: -Wall -O2
squeeze.spec view
@@ -1,4 +1,4 @@-# Copyright (C) 2012 Dr. Alistair Ward+# Copyright (C) 2012-2013 Dr. Alistair Ward # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by@@ -22,7 +22,7 @@ Summary: Finds the optimal subset of the specified files, to fit into a limited space, with minimal wastage Name: squeeze-Version: 1.0.3.0+Version: 1.0.4.0 Release: 1 License: GPLv3 #From '/usr/share/doc/packages/rpm/GROUPS'.@@ -61,6 +61,6 @@ %attr(0644, root, root) %doc %_mandir/man1/%name.1.gz %changelog-* Wed May 09 2012 Alistair Ward <regexchar at functionalley dot eu> 1.0.2.4-1+* Wed May 09 2012 Alistair Ward <squeeze at functionalley dot eu> 1.0.2.4-1 Removed "rm -rf -- '%buildroot'" from build, for security. Corrected "Paths" module, even though it's unused.
src/Main.hs view
@@ -1,6 +1,5 @@-{-# LANGUAGE CPP #-} {-- Copyright (C) 2010 Dr. Alistair Ward+ Copyright (C) 2010-2013 Dr. Alistair Ward This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by@@ -50,14 +49,13 @@ import qualified Squeeze.Data.FileCombination as Data.FileCombination import qualified Squeeze.Squeeze as Squeeze import qualified Squeeze.Test.Performance as Test.Performance-import qualified Squeeze.Test.QC as Test.QC+import qualified Squeeze.Test.QuickChecks as Test.QuickChecks import qualified System.Console.GetOpt as G import qualified System.Environment import qualified System.Exit import qualified System.FilePath import qualified System.IO import qualified System.IO.Error-import qualified Test.QuickCheck import qualified Text.Printf import qualified ToolShed.Defaultable import qualified ToolShed.SelfValidate@@ -89,9 +87,11 @@ bounded = fromInteger unbounded {- |- * Recursively bisects the task, distributing the parts to 'Squeeze.findBestFit', to utilise the available CPU-cores.+ * Recursively bisects the task, distributing the sub-tasks to 'Squeeze.findBestFit', to utilise the available CPU-cores. - * Recombines the part solutions to finds the single monotonically increasing list of file-combinations matching the specified criteria.+ * Recombines the part solutions to finds the single monotonically increasing list of file-combinations matching the original criteria.++ * CAVEAT: whilst the ultimate solution is similar, regardless of the specified number of CPU-cores available, the path leading to it typically differs. -} distribute :: RealFrac ratio@@ -110,22 +110,20 @@ distribute numCapabilities commandOptions fileSizeAndPathList = let distribute' = distribute $ numCapabilities `div` 2 --Partially apply.- (selectedFileSizeAndPath, remainingFileSizeAndPaths) = Data.List.minimumBy (Data.Ord.comparing Data.File.getSize) &&& filter (/= selectedFileSizeAndPath) $ fileSizeAndPathList --Balance the load on the two threads, by selecting the smallest file rather than the largest; this makes a big difference.+ (selectedFileSizeAndPath, remainingFileSizeAndPaths) = Data.List.minimumBy (Data.Ord.comparing Data.File.getSize) &&& filter (/= selectedFileSizeAndPath) $ fileSizeAndPathList --Balance the load on the two threads, by selecting the smallest file rather than the largest; this makes a big difference ! in do- Control.Monad.when (Data.CommandOptions.getVerbosity commandOptions == maxBound) . System.IO.hPutStrLn System.IO.stderr $ "Splitting task into those including & those excluding, " ++ show selectedFileSizeAndPath+ Control.Monad.when (Data.CommandOptions.getVerbosity commandOptions == maxBound) . System.IO.hPutStrLn System.IO.stderr $ show numCapabilities ++ " CPU-cores => bisecting task into those, with & without, " ++ show selectedFileSizeAndPath - fileCombinationsExcludingSelected <- distribute' commandOptions remainingFileSizeAndPaths --Recurse.+ fileCombinationsExcludingSelected <- distribute' commandOptions remainingFileSizeAndPaths --Recurse using all the original space, but a reduced list of files. fileCombinationsIncludingSelected <- map ( Data.FileCombination.prepend selectedFileSizeAndPath --Prepend to all file-combinations, the file we previously removed. ) <$> distribute' ( commandOptions { Data.CommandOptions.getMaximumBytes = Data.CommandOptions.getMaximumBytes commandOptions - Data.File.getSize selectedFileSizeAndPath- } --Reduce the maximum size; there's no need to account for the minimum, since this s defined as a ratio of the maximum.+ } --Reduce the maximum size; there's no need to account for the minimum, since this is defined as a ratio of the maximum. ) remainingFileSizeAndPaths --Recurse. - Control.Concurrent.DivideAndConquer.divideAndConquer (- Data.FileCombination.risingMerge $ Data.Ord.comparing Data.FileCombination.getAggregateFileSize- ) fileCombinationsExcludingSelected fileCombinationsIncludingSelected --Merge the part-solutions.+ Control.Concurrent.DivideAndConquer.divideAndConquer Data.FileCombination.risingMergeByAggregateFileSize fileCombinationsExcludingSelected fileCombinationsIncludingSelected --Merge the part-solutions. {- | * Parses the command-line arguments, to determine 'Data.CommandOptions.CommandOptions', which over-ride the default value.@@ -137,8 +135,6 @@ * Delegates the donkey-work to 'distribute'. Because this may take a long time, it prints the results in real time, rather than batching until the optimum has been determined.-- * If /verbose/ has been specified, prints the CPU-time used. -} main :: IO () main = do@@ -153,50 +149,51 @@ optDescrList :: [G.OptDescr CommandLineAction] optDescrList = [--- String [String] (G.ArgDescr CommandLineAction) String- G.Option "?" ["help"] (G.NoArg $ const printUsage) "Display this help, & then exit.",+-- String [String] (G.ArgDescr CommandLineAction) String+ G.Option "?" ["help"] (G.NoArg $ const printUsage) "Display this help, & then exit.", G.Option "" ["verbosity"] ( setVerbosity `G.ReqArg` show [minBound :: Distribution.Verbosity.Verbosity .. maxBound]- ) ("define the log-level; default '" ++ show (Data.CommandOptions.getVerbosity defaultCommandOptions) ++ "'. CAVEAT: to be effective, it must precede other options."+ ) ("Define the log-level; default '" ++ show (Data.CommandOptions.getVerbosity defaultCommandOptions) ++ "'. CAVEAT: to be effective, it must precede other options." ),- G.Option "v" ["version"] (G.NoArg $ const printVersion) "Print version-information, & then exit.",- G.Option "z" ["includeEmpty"] (setIncludeEmpty `G.OptArg` "<Bool>") ("Whether empty files & directories may be included in any solution; default '" ++ show (Data.CommandOptions.getIncludeEmpty defaultCommandOptions) ++ "'."),- G.Option "M" ["maximumBytes"] (setMaximumBytes `G.ReqArg` "<Int>") ("The maximum bytes of available space; default '" ++ show (Data.CommandOptions.getMaximumBytes defaultCommandOptions) ++ "'."),- G.Option "m" ["minimumUsageRatio"] (setMinimumUsageRatio `G.ReqArg` "<Float>") ("The minimum acceptable space usage-ratio; default '" ++ show (realToFrac $ Data.CommandOptions.getMinimumUsageRatio defaultCommandOptions :: Double) ++ "'."),- G.Option "q" ["runQuickChecks"] (G.NoArg runQuickChecks) "Run Quick-checks using arbitrary data, & then exit.",- G.Option "r" ["randomSeed"] (G.OptArg setRandomSeed "<Int>") ("Seed the random number-generator with the specified integer, to produce a repeatable pseudo-random sequence as required for performance-testing. If this option is unspecified then the seed is unpredictable, but if only its argument is unspecified then the seed defaults to '" ++ show defaultRandomSeed ++ "'. CAVEAT: to be effective, it must precede either 'testPerformance' or 'graphPerformance'."),- G.Option "" ["testPerformance"] (- testPerformance `G.ReqArg` "(<Int>, <Probability-distribution>)"- ) "Test the single-threaded performance, using the specified number of randomly generated virtual files, the size of which conform to the specified probability-distribution, & then exit",- G.Option "" ["graphPerformance"] (- graphPerformance `G.ReqArg` "<Probability-distribution>"- ) "Graph the single-threaded performance, against a linearly increasing number of randomly generated virtual files, the size of which conform to the specified probability-distribution. Doesn't normally terminate."+ G.Option "v" ["version"] (G.NoArg $ const printVersion) "Print version-information, & then exit.",+ G.Option "z" ["includeEmpty"] (setIncludeEmpty `G.OptArg` "<Bool>") ("Whether empty files & directories may be included in any solution; default '" ++ show (Data.CommandOptions.getIncludeEmpty defaultCommandOptions) ++ "'."),+ G.Option "M" ["maximumBytes"] (setMaximumBytes `G.ReqArg` "<Int>") ("The maximum bytes of available space; default '" ++ show (Data.CommandOptions.getMaximumBytes defaultCommandOptions) ++ "'."),+ G.Option "m" ["minimumUsageRatio"] (setMinimumUsageRatio `G.ReqArg` "<Float>") ("The minimum acceptable space usage-ratio; default '" ++ show (realToFrac $ Data.CommandOptions.getMinimumUsageRatio defaultCommandOptions :: Double) ++ "'."),+ G.Option "q" ["runQuickChecks"] (G.NoArg runQuickChecks) "Run Quick-checks using arbitrary data, & then exit.",+ G.Option "r" ["randomSeed"] (G.OptArg setRandomSeed "<Int>") ("Seed the random number-generator with the specified integer, to produce a repeatable pseudo-random sequence as required for performance-testing. If this option is unspecified then the seed is unpredictable, but if only its argument is unspecified then the seed defaults to '" ++ show defaultRandomSeed ++ "'. CAVEAT: to be effective, it must precede either 'testPerformanceContinuous' or 'testPerformanceDiscrete'."),+ G.Option "" ["testPerformanceContinuous"] (+ testPerformanceContinuous `G.ReqArg` "(<Int>, <Continuous Distribution>)"+ ) "Measure the CPU-seconds required for the specified number of randomly generated virtual files, the size of which conform to the specified continuous probability-distribution; & then exit.",+ G.Option "" ["testPerformanceDiscrete"] (+ testPerformanceDiscrete `G.ReqArg` "(<Int>, <Discrete Distribution>)"+ ) "Measure the CPU-seconds required for the specified number of randomly generated virtual files, the size of which conform to the specified discrete probability-distribution; & then exit." ] where- setMaximumBytes, setMinimumUsageRatio, setVerbosity, testPerformance, graphPerformance :: String -> CommandLineAction+ setMaximumBytes, setMinimumUsageRatio, setVerbosity, testPerformanceContinuous, testPerformanceDiscrete :: String -> CommandLineAction setMaximumBytes arg commandOptions = return {-to IO-monad-} commandOptions { Data.CommandOptions.getMaximumBytes = readCommandArg arg } setMinimumUsageRatio arg commandOptions = return {-to IO-monad-} commandOptions { Data.CommandOptions.getMinimumUsageRatio = realToFrac (readCommandArg arg :: Double) } setVerbosity arg commandOptions = return {-to IO-monad-} commandOptions { Data.CommandOptions.getVerbosity = readCommandArg arg } - testPerformance arg commandOptions+ testPerformanceContinuous arg commandOptions | not $ ToolShed.SelfValidate.isValid commandOptions = fail $ ToolShed.SelfValidate.getFirstError commandOptions | otherwise = do- ToolShed.System.TimeAction.printCPUSeconds $ Test.Performance.run commandOptions fileCount probabilityDistribution >>= mapM_ print+ ToolShed.System.TimeAction.printCPUSeconds $ Test.Performance.run commandOptions fileCount probabilityDistribution >>= mapM_ print {-force evaluation-} System.Exit.exitWith System.Exit.ExitSuccess where fileCount :: Int- probabilityDistribution :: Factory.Math.Probability.DiscreteDistribution Double+ probabilityDistribution :: Factory.Math.Probability.ContinuousDistribution Double (fileCount, probabilityDistribution) = readCommandArg arg - graphPerformance arg commandOptions+ testPerformanceDiscrete arg commandOptions | not $ ToolShed.SelfValidate.isValid commandOptions = fail $ ToolShed.SelfValidate.getFirstError commandOptions | otherwise = do- mapM_ (\fileCount -> ToolShed.System.TimeAction.printCPUSeconds $ Test.Performance.run commandOptions fileCount probabilityDistribution >>= mapM_ print) [1 ..]+ ToolShed.System.TimeAction.printCPUSeconds $ Test.Performance.run commandOptions fileCount probabilityDistribution >>= mapM_ print {-force evaluation-} - System.Exit.exitWith $ System.Exit.ExitFailure 1+ System.Exit.exitWith System.Exit.ExitSuccess where+ fileCount :: Int probabilityDistribution :: Factory.Math.Probability.DiscreteDistribution Double- probabilityDistribution = readCommandArg arg+ (fileCount, probabilityDistribution) = readCommandArg arg setIncludeEmpty, setRandomSeed :: Maybe String -> CommandLineAction setIncludeEmpty arg commandOptions = return {-to IO-monad-} commandOptions { Data.CommandOptions.getIncludeEmpty = Data.Maybe.maybe True readCommandArg arg }@@ -205,20 +202,10 @@ runQuickChecks :: (Num f, Ord f, Show f) => Data.CommandOptions.CommandOptions f -> IO (Data.CommandOptions.CommandOptions f) runQuickChecks commandOptions | not $ ToolShed.SelfValidate.isValid commandOptions = fail $ ToolShed.SelfValidate.getFirstError commandOptions- | otherwise = do- Test.QC.quickChecks $ if Data.CommandOptions.getVerbosity commandOptions > Distribution.Verbosity.normal- then-#if MIN_VERSION_QuickCheck(2,4,0)- Test.QuickCheck.verboseCheck-#else- fail "'Test.QuickCheck.verboseCheck' is only available as of 'QuickCheck-2.4'."-#endif- else Test.QuickCheck.quickCheck-- System.Exit.exitWith System.Exit.ExitSuccess+ | otherwise = Test.QuickChecks.run >> System.Exit.exitWith System.Exit.ExitSuccess printVersion, printUsage :: IO (Data.CommandOptions.CommandOptions f)- printVersion = Text.Printf.printf "%s\n\n%s %s.\n%s.\n%s.\n%s %s.\n" packageName "Copyright (C) 2010" author "This program comes with ABSOLUTELY NO WARRANTY" "This is free software, & you are welcome to redistribute it under certain conditions" "Written by" author >> System.Exit.exitWith System.Exit.ExitSuccess where+ printVersion = Text.Printf.printf "%s\n\n%s %s.\n%s.\n%s.\n%s %s.\n" packageName "Copyright (C) 2010-2013" author "This program comes with ABSOLUTELY NO WARRANTY" "This is free software, & you are welcome to redistribute it under certain conditions" "Written by" author >> System.Exit.exitWith System.Exit.ExitSuccess where packageIdentifier :: Distribution.Package.PackageIdentifier packageIdentifier = Distribution.Package.PackageIdentifier { Distribution.Package.pkgName = Distribution.Package.PackageName progName, --CAVEAT: coincidentally.@@ -229,14 +216,16 @@ packageName = Distribution.Text.display packageIdentifier author = "Dr. Alistair Ward" - printUsage = Text.Printf.hPrintf System.IO.stderr "Usage:\t%s %s\n\nEBNF argument-format:\n\t%-23s = %s;\n\t%-23s = %s;\n\t%-23s = %s;\n\t%-23s = %s;\n\t%-23s = %s;\n\nE.g.\n\t%s\n\t%s\n" (+ printUsage = Text.Printf.hPrintf System.IO.stderr "Usage:\t%s %s\n\nEBNF argument-format:\n\t%-22s = %s;\n\t%-22s = %s;\n\t%-22s = %s;\n\t%-22s = %s;\n\t%-22s = %s;\n\t%-22s = %s;\n\t%-22s = %s;\n\t%-22s = %s;\n\t%-22s = %s;\n\nE.g.\n\t%s\n\t%s\n\t%s\n" ( G.usageInfo progName optDescrList- ) "[<File-path> ...]" "Bool" "\"True\" | \"False\"\t(* Case-sensitive *)" "Int" "[0-9]+" "File-path" (+ ) "[<File-path> ...]" "Bool" "\"True\" | \"False\"\t(* case-sensitive *)" "ContinuousDistribution" "LogNormalDistribution location scale^2" "DiscreteDistribution" "PoissonDistribution lambda" "File-path" ( "File-name ('" ++ [System.FilePath.pathSeparator] ++ "' File-name)*"- ) "ProbabilityDistribution" "PoissonDistribution Int\t(* Defines both mean & variance *)" "Float" "Int ('.' Int)?" (- progName ++ " -M 700000000 *.ogg +RTS -N\t#Find the best-fit for the globbed file-names, into the space available on a CD, using multiple CPU-cores where available."+ ) "Float" "Int ('.' Int)?" "Int" "[0-9]+" "lambda" "Float\t(* the mean & variance of the distribution *)" "location" "Float\t(* the mean of the log of the distribution *)" "scale^2" "Float\t(* the variance of the log of the distribution *)" (+ progName ++ " --verbosity=Verbose -M 700000000 *.ogg +RTS -N\t#Find the best-fit for the globbed file-names, into the space available on a CD, using multiple CPU-cores where available." ) (- progName ++ " --verbosity=Verbose -r --testPerformance='(100, PoissonDistribution 1000000000)'\t#Test performance."+ progName ++ " -r --testPerformanceContinuous='(100, LogNormalDistribution 19.4 0.6)'\t#Test performance."+ ) (+ progName ++ " -r --testPerformanceDiscrete='(100, PoissonDistribution 4e8)'\t#Test performance." ) >> System.Exit.exitWith System.Exit.ExitSuccess args <- System.Environment.getArgs@@ -251,13 +240,11 @@ if not $ ToolShed.SelfValidate.isValid commandOptions then fail $ ToolShed.SelfValidate.getFirstError commandOptions else if null nonOptions- then fail "No file-paths specified"- else (- if Data.CommandOptions.getVerbosity commandOptions > Distribution.Verbosity.normal- then ToolShed.System.TimeAction.printCPUSeconds- else id- ) $ do- filePaths <- if "-" `elem` nonOptions+ then fail "zero file-paths specified"+ else let+ standardInputProxy = "-"+ in do+ filePaths <- if standardInputProxy `elem` nonOptions then let getFilePaths :: IO Data.File.FilePathList getFilePaths = do@@ -267,10 +254,10 @@ then return {-to IO-monad-} [] else {-more to read-} (:) <$> getLine <*> getFilePaths {-recurse-} in do- filePaths <- (filter (/= "-") nonOptions ++) <$> getFilePaths+ filePaths <- (filter (/= standardInputProxy) nonOptions ++) <$> getFilePaths if null filePaths- then fail "No file-paths"+ then fail "zero file-paths" else return filePaths else {-real fileNames-} return {-to IO-monad-} nonOptions @@ -280,17 +267,14 @@ Control.Monad.when (Data.CommandOptions.getVerbosity commandOptions > minBound) . System.IO.hPutStrLn System.IO.stderr $ Data.List.intercalate "\n" logFile if null acceptedFileSizeAndPathList- then fail "There are zero suitable files"+ then fail "there are zero suitable files" else let aggregateSize = Data.File.aggregateSize acceptedFileSizeAndPathList minimumBytes = Data.CommandOptions.deriveMinimumBytes commandOptions in if aggregateSize < minimumBytes- then fail $ "The aggregate size of all suitable files, is insufficient; " ++ show (aggregateSize, minimumBytes)- else do- numCapabilities <- Control.Concurrent.getNumCapabilities --Find how many CPU-cores are available.-- Control.Monad.when (Data.CommandOptions.getVerbosity commandOptions == maxBound) . System.IO.hPutStrLn System.IO.stderr $ show numCapabilities ++ " available CPU-cores"-- mapM_ print {-lazy evaluation-} =<< distribute numCapabilities commandOptions acceptedFileSizeAndPathList+ then fail $ "the aggregate size of all suitable files, is insufficient; " ++ show (aggregateSize, minimumBytes)+ else Control.Concurrent.getNumCapabilities >>= (+ \numCapabilities -> distribute numCapabilities commandOptions acceptedFileSizeAndPathList+ ) >>= mapM_ print {-lazy evaluation-} (_, _, errors) -> System.IO.Error.ioError . System.IO.Error.userError $ concatMap init {-chop-} errors
src/Squeeze/Control/Concurrent/DivideAndConquer.hs view
@@ -53,15 +53,15 @@ * Any attempt to read beyond the sentinel, will block forever since the writer is out of data. -} readListFromChan :: TerminatedChannel a -> IO [a]-readListFromChan chan = System.IO.Unsafe.unsafeInterleaveIO {-read on demand; i.e. lazy-} $ Control.Concurrent.Chan.readChan chan >>= Data.Maybe.maybe (+readListFromChan chan = System.IO.Unsafe.unsafeInterleaveIO {-read lazily, on demand-} $ Control.Concurrent.Chan.readChan chan >>= Data.Maybe.maybe ( return {-to IO-monad-} [] ) ( \x -> (x :) <$> readListFromChan chan {-recurse-} ) --- | Writes the two unevaluated lists to separate concurrent channels, reads them both & merges the results into a single list, evaluating in parallel, lazily on demand.+-- | Writes the two unevaluated lists to separate concurrent channels, lazily reads them both (resulting in parallel evaluation) & merges them into a single results-list. divideAndConquer- :: ([a] -> [a] -> [a]) -- ^ Merge-function, which reads from the two channels, to produce a results-list.+ :: ([a] -> [a] -> [a]) -- ^ Merge-function, which reads from two channels, to produce a third. -> [a] -- ^ Data for first channel. -> [a] -- ^ Data for second channel. -> IO [a]@@ -70,7 +70,6 @@ chan1 <- Control.Concurrent.Chan.newChan Control.Monad.void . Control.Concurrent.forkIO $ writeListToChan chan0 l- Control.Monad.void . Control.Concurrent.forkIO $ writeListToChan chan1 r merge <$> readListFromChan chan0 <*> readListFromChan chan1
src/Squeeze/Data/FileCombination.hs view
@@ -25,23 +25,29 @@ -- ** Data-types FileCombination( -- MkFileCombination,- getAggregateFileSize--- getFilePathList+ getAggregateFileSize,+ getFilePathList ), -- * Constants nullFileCombination, -- * Functions+ comparingAggregateFileSize, prepend, risingFilter,- risingMerge,+-- risingMerge,+ risingMergeByAggregateFileSize, -- ** Constructors+ mkFileCombination, singleton, -- ** Predicates hasSizeBy ) where import qualified Data.List+import qualified Data.Ord import qualified Squeeze.Data.File as Data.File+import qualified ToolShed.Data.Foldable+import qualified ToolShed.SelfValidate -- | Declare a list of files qualified by its aggregate size. data FileCombination = MkFileCombination {@@ -52,18 +58,42 @@ instance Show FileCombination where showsPrec _ fileCombination = shows (getAggregateFileSize fileCombination) . showChar '\t' . shows (Data.List.sort $ getFilePathList fileCombination) +instance ToolShed.SelfValidate.SelfValidator FileCombination where+ getErrors fileCombination = ToolShed.SelfValidate.extractErrors [+ (+ getAggregateFileSize fileCombination < 0,+ "aggregate size must be positive; " ++ show fileCombination+ ), (+ getAggregateFileSize fileCombination /= 0 && null (getFilePathList fileCombination),+ "when zero files are specified, the aggregate size must also be zero; " ++ show fileCombination+ ), let+ duplicateFilePaths = map head . filter ((> 1) . length) . ToolShed.Data.Foldable.gather $ getFilePathList fileCombination+ in (+ not $ null duplicateFilePaths,+ "duplicate file-paths have been specified; " ++ show duplicateFilePaths+ )+ ]+ -- | A constant empty instance. nullFileCombination :: FileCombination nullFileCombination = MkFileCombination 0 [] +-- | Smart constructor.+mkFileCombination :: Data.File.FileSize -> Data.File.FilePathList -> FileCombination+mkFileCombination fileSize filePathList+ | ToolShed.SelfValidate.isValid fileCombination = fileCombination+ | otherwise = error $ "Squeeze.Data.FileCombination.mkFileCombination:\t" ++ ToolShed.SelfValidate.getFirstError fileCombination+ where+ fileCombination = MkFileCombination fileSize filePathList+ -- | Construct a 'FileCombination' from a single 'Data.File.FileSizeAndPath'. singleton :: Data.File.FileSizeAndPath -> FileCombination-singleton (fileSize, filePath) = MkFileCombination fileSize [filePath]+singleton (fileSize, filePath) = mkFileCombination fileSize [filePath] {- | * Prepend a 'Data.File.FileSizeAndPath' to an existing 'FileCombination'. - * CAVEAT: performance hot-spot.+ * CAVEAT: performance hot-spot, so it by-passes the checks made by 'mkFileCombination'. -} {-# INLINE prepend #-} prepend@@ -99,7 +129,7 @@ * Merges two lists of monotonically increasing values, into a single monotonically increasing list, by dropping values which compare less than results already found. * CAVEAT: both lists must produce an element, in order to determine which is selected.- As a result, if one list produces a value, it can't be returned until the other does (which make take a long time), even if ultimately the first is then selected.+ As a result, though one list produces a value, it can't be returned until the other does (which make take a long time), even if ultimately the first is then selected. -} risingMerge :: (FileCombination -> FileCombination -> Ordering) -- ^ Comparator used to select the best file-combination from the heads of the two list supplied.@@ -107,8 +137,10 @@ -> [FileCombination] -- ^ A list of monotonically increasing file-combinations. -> [FileCombination] risingMerge cmp = slave nullFileCombination where- slave bar [] r = dropWhile ((== LT) . (`cmp` bar)) r- slave bar l [] = dropWhile ((== LT) . (`cmp` bar)) l+ lessThan bar = (== LT) . (`cmp` bar)++ slave bar [] r = dropWhile (lessThan bar) r+ slave bar l [] = dropWhile (lessThan bar) l slave _ (x : xs) (y : ys) | o == GT = x : slave x xs ys | o == LT = y : slave y xs ys@@ -116,3 +148,17 @@ where o = x `cmp` y +-- Compares two /file-combination/s by their aggregate file-size.+comparingAggregateFileSize :: FileCombination -> FileCombination -> Ordering+comparingAggregateFileSize = Data.Ord.comparing getAggregateFileSize++{- |+ Merges two lists of monotonically increasing lists of file-combinations,+ into a single monotonically increasing list,+ by dropping values which have a smaller aggregate size than results already found.+-}+risingMergeByAggregateFileSize+ :: [FileCombination] -- ^ A list of monotonically increasing file-combinations.+ -> [FileCombination] -- ^ A list of monotonically increasing file-combinations.+ -> [FileCombination]+risingMergeByAggregateFileSize = risingMerge comparingAggregateFileSize
+ src/Squeeze/Test/Data/FileCombinations.hs view
@@ -0,0 +1,49 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}+{-+ Copyright (C) 2010 Dr. Alistair Ward++ This program is free software: you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation, either version 3 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program. If not, see <http://www.gnu.org/licenses/>.+-}+{- |+ [@AUTHOR@] Dr. Alistair Ward++ [@DESCRIPTION@] Defines tests to dynamically verify the module "Squeeze.Data.FileCombinations" against arbitrary data.+-}++module Squeeze.Test.Data.FileCombinations(+-- * Functions+ quickChecks+) where++import Control.Applicative((<$>))+import Control.Arrow((&&&))+import qualified Data.List+import qualified Squeeze.Data.FileCombination as Data.FileCombination+import qualified Test.QuickCheck++instance Test.QuickCheck.Arbitrary Data.FileCombination.FileCombination where+ arbitrary = do+ filePathList <- Data.List.nub <$> (Test.QuickCheck.elements [0 .. 99] >>= Test.QuickCheck.vector)+ aggregateFileSize <- Test.QuickCheck.elements [0 .. Data.List.genericLength filePathList] --Assume files of size either zero or one byte.++ return $ Data.FileCombination.mkFileCombination aggregateFileSize filePathList++-- | Defines invariant properties.+quickChecks :: IO ()+quickChecks = Test.QuickCheck.quickCheck `mapM_` [prop_risingFilter, prop_risingFilter', prop_risingMergeByAggregateFileSize] where+ prop_risingFilter, prop_risingFilter', prop_risingMergeByAggregateFileSize :: [Data.FileCombination.FileCombination] -> Test.QuickCheck.Property+ prop_risingFilter = Test.QuickCheck.label "prop_risingFilter" . uncurry (==) . (id &&& Data.List.sort) . map Data.FileCombination.getAggregateFileSize . Data.FileCombination.risingFilter 0+ prop_risingFilter' = Test.QuickCheck.label "prop_risingFilter'" . uncurry (==) . (id &&& Data.FileCombination.risingFilter 0) . Data.List.sortBy Data.FileCombination.comparingAggregateFileSize++ prop_risingMergeByAggregateFileSize = Test.QuickCheck.label "prop_risingMergeByAggregateFileSize" . uncurry (==) . (id &&& map head . Data.List.group . uncurry Data.FileCombination.risingMergeByAggregateFileSize . (id &&& id)) . Data.FileCombination.risingFilter 0
src/Squeeze/Test/Performance.hs view
@@ -25,7 +25,6 @@ run ) where -import Control.Arrow((&&&)) import qualified Control.Monad import qualified Control.Monad.Writer import qualified Data.List@@ -39,23 +38,34 @@ import qualified System.Random {- |- * Find the best-fit, into the available space, using a set composed from the specified number of /virtual/ files.+ * Find the best-fit, into the available space, using a set composed from the specified number of virtual files. - * The files have a random size, conforming to the requested distribution.+ * The files have a random size, conforming to the specified distribution. - * The generated file-name, arbitrarily reflects its size.+ * The generated file-name, is merely a counter.++ * CAVEAT: fewer files than requested may be used, because depending on the requested distribution, some may overflow the maximum permissible size. -}-run :: (RealFrac ratio, RealFloat f, Show f, System.Random.Random f)+run :: (Factory.Math.Probability.Distribution distribution, RealFrac ratio) => Data.CommandOptions.CommandOptions ratio- -> Int -- ^ The number of /virtual/ files to randomly generate.- -> Factory.Math.Probability.DiscreteDistribution f+ -> Int -- ^ The number of virtual files to randomly generate.+ -> distribution -> IO [Data.FileCombination.FileCombination] run commandOptions fileCount probabilityDistribution = do randomGen <- Data.Maybe.maybe System.Random.getStdGen {-use the global random-number generator-} ( return {-to IO-monad-} . System.Random.mkStdGen --Seed the random-number generator as specified. ) $ Data.CommandOptions.getMaybeRandomSeed commandOptions --Select a random-number generator. - let (acceptedFileSizeAndPathList, logFile) = Control.Monad.Writer.runWriter . Data.File.selectSuitableFileSizes (Data.CommandOptions.isWithinSizeBounds commandOptions) . Data.List.nub . map (id &&& show) $ Factory.Math.Probability.generateDiscretePopulation fileCount probabilityDistribution randomGen+ let+ (acceptedFileSizeAndPathList, logFile) = Control.Monad.Writer.runWriter . Data.File.selectSuitableFileSizes (+ Data.CommandOptions.isWithinSizeBounds commandOptions --This may reduce the requested number of files, but compensating distorts the requested distribution.+ ) . (+ `zip` map show [0 :: Int ..] --Construct a Data.File.FileSizeAndPath, by using a counter as the file-name.+ ) . take fileCount . filter (+ /= 0 --Though a valid file-size, it doesn't work well when finding valid combinations.+ ) . map (+ ceiling :: Double -> Integer --File-sizes are integral.+ ) $ Factory.Math.Probability.generatePopulation probabilityDistribution randomGen Control.Monad.when (Data.CommandOptions.getVerbosity commandOptions > minBound) . System.IO.hPutStrLn System.IO.stderr $ Data.List.intercalate "\n" logFile
− src/Squeeze/Test/QC.hs
@@ -1,70 +0,0 @@-{-- Copyright (C) 2010 Dr. Alistair Ward-- This program is free software: you can redistribute it and/or modify- it under the terms of the GNU General Public License as published by- the Free Software Foundation, either version 3 of the License, or- (at your option) any later version.-- This program is distributed in the hope that it will be useful,- but WITHOUT ANY WARRANTY; without even the implied warranty of- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the- GNU General Public License for more details.-- You should have received a copy of the GNU General Public License- along with this program. If not, see <http://www.gnu.org/licenses/>.--}-{- |- [@AUTHOR@] Dr. Alistair Ward-- [@DESCRIPTION@] Defines tests to dynamically verify the module "Squeeze.Squeeze" against arbitrary data.--}--module Squeeze.Test.QC(--- * Types--- ** Type-synonyms--- Testable,--- * Functions- quickChecks-) where--import qualified Control.Arrow-import Control.Arrow((&&&))-import qualified Data.List-import qualified Factory.Data.Interval-import qualified Squeeze.Data.File as Data.File-import qualified Squeeze.Data.FileCombination as Data.FileCombination-import qualified Squeeze.Squeeze as Squeeze-import qualified Test.QuickCheck--type Testable = [Data.File.FileSizeAndPath] -> Test.QuickCheck.Property---- | Defines invariant properties, which must hold for any 'FileSizeAndPath'.-quickChecks :: (Testable -> IO ()) -> IO ()-quickChecks = (`mapM_` [prop_totalCombinations, prop_bounds, prop_all, prop_monotonic]) where- translate :: [Data.File.FileSizeAndPath] -> [Data.File.FileSizeAndPath]- translate = Data.List.nub . map (Control.Arrow.first abs)-- prop_totalCombinations, prop_bounds, prop_all :: Testable- prop_totalCombinations fileSizeAndPathList = Test.QuickCheck.label "prop_totalCombinations" $ (length . Data.List.nub . Squeeze.findCombinations (0, fromIntegral (maxBound :: Int)) $ Data.File.orderBySize l) == 2 ^ length l where- l = take 10 {-arbitrarily-} $ translate fileSizeAndPathList --CAVEAT: may be shorter than requested.-- prop_bounds fileSizeAndPathList = Test.QuickCheck.label "prop_bounds" . all ((`Factory.Data.Interval.elem'` fileSizeBounds) . Data.FileCombination.getAggregateFileSize) . Squeeze.findCombinations fileSizeBounds $ Data.File.orderBySize l where- l = take 16 {-arbitrarily-} $ translate fileSizeAndPathList --CAVEAT: may be shorter than requested.-- fileSizeBounds :: Factory.Data.Interval.Interval Data.File.FileSize- fileSizeBounds = (minimumBytes, maximumBytes) where- maximumBytes, minimumBytes :: Data.File.FileSize- maximumBytes = round $ fromIntegral (Data.File.aggregateSize l) / (4.0 :: Double) --Arbitrarily.- minimumBytes = maximumBytes `div` 2 --Arbitrarily.-- prop_all fileSizeAndPathList = Test.QuickCheck.label "prop_all" . (== bytes) . last . map Data.FileCombination.getAggregateFileSize $ Squeeze.findBestFit (Factory.Data.Interval.precisely bytes) l where- l = take 16 {-arbitrarily-} $ translate fileSizeAndPathList --CAVEAT: may be shorter than requested.-- bytes :: Data.File.FileSize- bytes = Data.File.aggregateSize l-- prop_monotonic :: Testable- prop_monotonic fileSizeAndPathList = Test.QuickCheck.label "prop_monotonic" . uncurry (==) . (id &&& Data.List.sort) . map Data.FileCombination.getAggregateFileSize $ Squeeze.findBestFit (0, fromIntegral (maxBound :: Int)) l where- l = take 16 {-arbitrarily-} $ translate fileSizeAndPathList --CAVEAT: may be shorter than requested.-
+ src/Squeeze/Test/QuickChecks.hs view
@@ -0,0 +1,39 @@+{-+ Copyright (C) 2010 Dr. Alistair Ward++ This program is free software: you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation, either version 3 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program. If not, see <http://www.gnu.org/licenses/>.+-}+{- |+ [@AUTHOR@] Dr. Alistair Ward++ [@DESCRIPTION@] Defines tests to dynamically verify the application against arbitrary data.+-}++module Squeeze.Test.QuickChecks(+-- * Functions+ run+) where++import qualified Control.Arrow+import qualified Squeeze.Test.Data.FileCombinations as Test.Data.FileCombinations+import qualified Squeeze.Test.Squeeze as Test.Squeeze++-- | Runs checks on invariant properties.+run :: IO ()+run = mapM_ (+ uncurry (>>) . Control.Arrow.first putStrLn+ ) [+ ("Data.FileCombinations", Test.Data.FileCombinations.quickChecks),+ ("Squeeze", Test.Squeeze.quickChecks)+ ]
+ src/Squeeze/Test/Squeeze.hs view
@@ -0,0 +1,70 @@+{-+ Copyright (C) 2010 Dr. Alistair Ward++ This program is free software: you can redistribute it and/or modify+ it under the terms of the GNU General Public License as published by+ the Free Software Foundation, either version 3 of the License, or+ (at your option) any later version.++ This program is distributed in the hope that it will be useful,+ but WITHOUT ANY WARRANTY; without even the implied warranty of+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the+ GNU General Public License for more details.++ You should have received a copy of the GNU General Public License+ along with this program. If not, see <http://www.gnu.org/licenses/>.+-}+{- |+ [@AUTHOR@] Dr. Alistair Ward++ [@DESCRIPTION@] Defines tests to dynamically verify the module "Squeeze.Squeeze" against arbitrary data.+-}++module Squeeze.Test.Squeeze(+-- * Functions+ quickChecks+) where++import Control.Arrow((&&&))+import qualified Data.List+import qualified Factory.Data.Interval+import qualified Squeeze.Data.File as Data.File+import qualified Squeeze.Data.FileCombination as Data.FileCombination+import qualified Squeeze.Squeeze as Squeeze+import qualified Test.QuickCheck+import qualified ToolShed.Data.Foldable+import qualified ToolShed.Data.List++-- | Defines invariant properties.+quickChecks :: IO ()+quickChecks = Test.QuickCheck.quickCheck `mapM_` [prop_totalCombinations, prop_fileSizeBounds, prop_allFiles, prop_monotonicFileSize, prop_uniqueFileNames] where+ mkFileSizeAndPathList :: (Integral i, Show i) => [i] -> [Data.File.FileSizeAndPath]+ mkFileSizeAndPathList = take 12 . Data.List.nubBy (ToolShed.Data.List.equalityBy snd) . map (toInteger . abs &&& show) --CAVEAT: may be shorter than requested.++ prop_totalCombinations, prop_fileSizeBounds, prop_allFiles, prop_monotonicFileSize, prop_uniqueFileNames :: [Integer] -> Test.QuickCheck.Property+ prop_totalCombinations integers = Test.QuickCheck.label "prop_totalCombinations" $ (length . Data.List.nub . Squeeze.findCombinations (0, fromIntegral (maxBound :: Int)) $ Data.File.orderBySize fileSizeAndPathList) == 2 ^ length fileSizeAndPathList where+ fileSizeAndPathList = mkFileSizeAndPathList integers++ prop_fileSizeBounds integers = Test.QuickCheck.label "prop_fileSizeBounds" . all ((`Factory.Data.Interval.elem'` fileSizeBounds) . Data.FileCombination.getAggregateFileSize) . Squeeze.findCombinations fileSizeBounds $ Data.File.orderBySize fileSizeAndPathList where+ fileSizeAndPathList = mkFileSizeAndPathList integers++ fileSizeBounds :: Factory.Data.Interval.Interval Data.File.FileSize+ fileSizeBounds = (minimumBytes, maximumBytes) where+ maximumBytes, minimumBytes :: Data.File.FileSize+ maximumBytes = round $ fromIntegral (Data.File.aggregateSize fileSizeAndPathList) / (4.0 :: Double) --Arbitrarily.+ minimumBytes = maximumBytes `div` 2 --Arbitrarily.++ prop_allFiles integers = Test.QuickCheck.label "prop_allFiles" . (== aggregateSize) . last . map Data.FileCombination.getAggregateFileSize $ Squeeze.findBestFit (Factory.Data.Interval.precisely aggregateSize) fileSizeAndPathList where+ fileSizeAndPathList = mkFileSizeAndPathList integers++ aggregateSize :: Data.File.FileSize+ aggregateSize = Data.File.aggregateSize fileSizeAndPathList++ prop_monotonicFileSize integers = Test.QuickCheck.label "prop_monotonicFileSize" . uncurry (==) . (id &&& Data.List.sort) . map Data.FileCombination.getAggregateFileSize $ Squeeze.findBestFit (0, fromIntegral (maxBound :: Int)) fileSizeAndPathList where+ fileSizeAndPathList = mkFileSizeAndPathList integers++ prop_uniqueFileNames integers = Test.QuickCheck.label "prop_uniqueFileNames" . all (+ all ((== 1) . length) . ToolShed.Data.Foldable.gather . Data.FileCombination.getFilePathList+ ) $ Squeeze.findBestFit (0, fromIntegral (maxBound :: Int)) fileSizeAndPathList where+ fileSizeAndPathList = mkFileSizeAndPathList integers+