packages feed

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 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'. 
@@ -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+