ConfigFile 1.0.5 → 1.0.6
raw patch · 7 files changed
+431/−4 lines, 7 filesdep ~basePVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependency ranges changed: base
API changes (from Hackage documentation)
- Data.ConfigFile: add_section :: (MonadError CPError m) => ConfigParser -> SectionSpec -> m ConfigParser
+ Data.ConfigFile: add_section :: MonadError CPError m => ConfigParser -> SectionSpec -> m ConfigParser
- Data.ConfigFile: interpolatingAccess :: (MonadError CPError m) => Int -> ConfigParser -> SectionSpec -> OptionSpec -> m String
+ Data.ConfigFile: interpolatingAccess :: MonadError CPError m => Int -> ConfigParser -> SectionSpec -> OptionSpec -> m String
- Data.ConfigFile: items :: (MonadError CPError m) => ConfigParser -> SectionSpec -> m [(OptionSpec, String)]
+ Data.ConfigFile: items :: MonadError CPError m => ConfigParser -> SectionSpec -> m [(OptionSpec, String)]
- Data.ConfigFile: options :: (MonadError CPError m) => ConfigParser -> SectionSpec -> m [OptionSpec]
+ Data.ConfigFile: options :: MonadError CPError m => ConfigParser -> SectionSpec -> m [OptionSpec]
- Data.ConfigFile: readfile :: (MonadError CPError m) => ConfigParser -> FilePath -> IO (m ConfigParser)
+ Data.ConfigFile: readfile :: MonadError CPError m => ConfigParser -> FilePath -> IO (m ConfigParser)
- Data.ConfigFile: readhandle :: (MonadError CPError m) => ConfigParser -> Handle -> IO (m ConfigParser)
+ Data.ConfigFile: readhandle :: MonadError CPError m => ConfigParser -> Handle -> IO (m ConfigParser)
- Data.ConfigFile: readstring :: (MonadError CPError m) => ConfigParser -> String -> m ConfigParser
+ Data.ConfigFile: readstring :: MonadError CPError m => ConfigParser -> String -> m ConfigParser
- Data.ConfigFile: remove_option :: (MonadError CPError m) => ConfigParser -> SectionSpec -> OptionSpec -> m ConfigParser
+ Data.ConfigFile: remove_option :: MonadError CPError m => ConfigParser -> SectionSpec -> OptionSpec -> m ConfigParser
- Data.ConfigFile: remove_section :: (MonadError CPError m) => ConfigParser -> SectionSpec -> m ConfigParser
+ Data.ConfigFile: remove_section :: MonadError CPError m => ConfigParser -> SectionSpec -> m ConfigParser
- Data.ConfigFile: set :: (MonadError CPError m) => ConfigParser -> SectionSpec -> OptionSpec -> String -> m ConfigParser
+ Data.ConfigFile: set :: MonadError CPError m => ConfigParser -> SectionSpec -> OptionSpec -> String -> m ConfigParser
- Data.ConfigFile: simpleAccess :: (MonadError CPError m) => ConfigParser -> SectionSpec -> OptionSpec -> m String
+ Data.ConfigFile: simpleAccess :: MonadError CPError m => ConfigParser -> SectionSpec -> OptionSpec -> m String
- Data.ConfigFile.Parser: parse_file :: (MonadError CPError m) => FilePath -> IO (m ParseOutput)
+ Data.ConfigFile.Parser: parse_file :: MonadError CPError m => FilePath -> IO (m ParseOutput)
- Data.ConfigFile.Parser: parse_handle :: (MonadError CPError m) => Handle -> IO (m ParseOutput)
+ Data.ConfigFile.Parser: parse_handle :: MonadError CPError m => Handle -> IO (m ParseOutput)
- Data.ConfigFile.Parser: parse_string :: (MonadError CPError m) => String -> m ParseOutput
+ Data.ConfigFile.Parser: parse_string :: MonadError CPError m => String -> m ParseOutput
Files
- ConfigFile.cabal +8/−4
- Makefile +36/−0
- README +4/−0
- testsrc/ConfigParser/Maintest.hs +249/−0
- testsrc/ConfigParser/Parsertest.hs +89/−0
- testsrc/ConfigParser/test.cfg +15/−0
- testsrc/Tests.hs +30/−0
ConfigFile.cabal view
@@ -1,11 +1,14 @@ Name: ConfigFile-Version: 1.0.5+Version: 1.0.6 License: LGPL Maintainer: John Goerzen <jgoerzen@complete.org> Author: John Goerzen-Copyright: Copyright (c) 2004-2008 John Goerzen+Copyright: Copyright (c) 2004-2010 John Goerzen license-file: COPYRIGHT-extra-source-files: COPYING+extra-source-files: COPYING,+ README,+ Makefile,+ testsrc/ConfigParser/test.cfg Category: Parsing homepage: http://software.complete.org/configfile synopsis: Configuration file reading & writing@@ -39,7 +42,7 @@ Extensions: ExistentialQuantification, OverlappingInstances, UndecidableInstances, TypeSynonymInstances, FlexibleContexts, FlexibleInstances- Build-Depends: parsec, base,+ Build-Depends: parsec, base < 5, haskell98, mtl, MissingH>=1.0.0, containers GHC-Options: -O2 -Wall @@ -51,6 +54,7 @@ Buildable: False Main-Is: runtests.hs HS-Source-Dirs: testsrc, src, .+ Other-Modules: Tests, ConfigParser.Maintest, ConfigParser.Parsertest Extensions: ExistentialQuantification, OverlappingInstances, UndecidableInstances, CPP, TypeSynonymInstances, FlexibleContexts, FlexibleInstances
+ Makefile view
@@ -0,0 +1,36 @@+all: setup+ @echo "Please use Cabal to build this package; not make."+ ./setup configure+ ./setup build++setup: Setup.hs+ ghc --make -package Cabal -o setup Setup.hs++install: setup+ ./setup install++clean:+ -runghc Setup.hs clean+ -rm -rf html `find . -name "*.o"` `find . -name "*.hi" | grep -v debian` \+ `find . -name "*~" | grep -v debian` *.a setup dist testsrc/runtests \+ local-pkg doctmp+ -rm -rf testtmp/* testtmp*++.PHONY: test+test: test-ghc test-hugs+ @echo ""+ @echo "All tests pass."++test-hugs: setup+ @echo " ****** Running hugs tests"+ ./setup configure -f buildtests --hugs --extra-include-dirs=/usr/lib/hugs/include+ ./setup build+ runhugs -98 +o -P$(PWD)/dist/scratch:$(PWD)/dist/scratch/programs/runtests: \+ dist/scratch/programs/runtests/Main.hs++test-ghc: setup+ @echo " ****** Building GHC tests"+ ./setup configure -f buildtests+ ./setup build+ @echo " ****** Running GHC tests"+ ./dist/build/runtests/runtests
+ README view
@@ -0,0 +1,4 @@+Welcome to ConfigFile.++For more information, visit http://software.complete.org/configfile+
+ testsrc/ConfigParser/Maintest.hs view
@@ -0,0 +1,249 @@+{- arch-tag: ConfigParser tests main file+Copyright (C) 2004-2006 John Goerzen <jgoerzen@complete.org>++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 2 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, write to the Free Software+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA+-}++module ConfigParser.Maintest(tests) where+import Test.HUnit+import Data.ConfigFile+import Data.Either.Utils+import System.IO.PlafCompat(nullFileName)+import Test.HUnit.Tools+import Control.Exception+import System.IO++nullfile = openFile nullFileName ReadWriteMode+testfile = "testsrc/ConfigParser/test.cfg"+p inp = forceEither $ readstring emptyCP inp+f msg inp exp conv = TestLabel msg $ TestCase $ assertEqual "" (Right exp) (conv (p inp))++-- f2s, f2b are useful for matching Left return values+f2s :: String -> Either CPError String -> Either CPError String -> Test+f2s = f2+f2b :: String -> Either CPError Bool -> Either CPError Bool -> Test+f2b = f2++f2 msg exp res = TestLabel msg $ TestCase $ assertEqual "" exp res+f3 msg inp exp conv = TestLabel msg $ TestCase $ assertEqual "" exp (conv (p inp))++instance Show ConfigParser where+ show x = show (content x)++instance Eq ConfigParser where+ x == y = (content x) == (content y)++test_basic =+ [+ f3 "empty doc, no sections" "" [] sections,+ f3 "one empty line" "\n" [] sections,+ f3 "comment line only" "#foo bar" [] sections,+ f3 "comment line with \\n" "#foo bar\n" [] sections,+ f3 "one empty sect" "[emptysect]" ["emptysect"] sections,+ f3 "one empty sect w comment" "#foo bar\n[emptysect]\n" ["emptysect"]+ sections,+ f3 "assure comments not processed"+ "# [nonexistant]\n[emptysect]\n" ["emptysect"] sections,+ f3 "1 empty s w/comments"+ "#fo\n[Cemptysect]\n#asdf boo\n \n # fnonexistantg"+ ["Cemptysect"] sections,+ f3 "1 empty s, comments, EOL"+ "[emptysect]\n# [nonexistant]\n" ["emptysect"] sections,+ TestLabel "1 sec w/option" $ TestCase $+ do let cp = p "[sect1]\nfoo: bar\n"+ ["sect1"] @=? sections cp+ (Right "bar") @=? get cp "sect1" "foo"+ , f "comments in option text"+ "[s1]\no1: v1#v2\n"+ "v1#v2" (\cp -> get cp "s1" "o1")+ , TestLabel "mult options" $ TestCase $+ do let cp = p "\n#foo\n[sect1]\n\n#iiii \no1: v1\no2: v2\no3: v3"+ Right ["o1", "o2", "o3"] @=? options cp "sect1"+ ["sect1"] @=? sections cp+ Right "v2" @=? get cp "sect1" "o2"+ , TestLabel "sectionless option" $ TestCase $+ do let cp = p "v1: o1\n[sect1]\nv2: o2"+ Right "o1" @=? get cp "sect1" "v1"+ Right "o2" @=? get cp "sect1" "v2"+ Right "o1" @=? get cp "DEFAULT" "v1"+ , f3 "extensions to string" + "[sect1]\nfoo: bar\nbaz: l1\n l2\n l3\n# c\nquux: asdf"+ "[sect1]\nbaz: l1\n l2\n l3\nfoo: bar\nquux: asdf\n\n"+ to_string+ ]++test_defaults = + let cp = p "def: ault\n[sect1]\nfoo: bar\nbaz: quuz\nint: 2\nfloat: 3\nbool: yes\n[sect4]\ndef: different" in+ [+ f2 "default item" (Right "ault") (get cp "sect1" "def")+ ,f2 "normal item" (Right "bar") (get cp "sect1" "foo")+ ,f2s "no option" (Left (NoOption "abc", "get (sect1/abc)")) + (get cp "sect1" "abc")+ ,f2s "no section" (Left (NoSection "sect2", "get (sect2/foo)")) + (get cp "sect2" "foo")+ ,f2 "default from bad sect" (Right "ault") (get cp "sect2" "def")+ ,f2 "overriding default" (Right "different") (get cp "sect4" "def")+ -- not in haskell: ,f2 "using default feature"+ -- default int+ -- default float+ -- default bool+ ]++test_nodefault =+ let cp = (p "def: ault\n[sect1]\nfoo: bar\nbaz: quuz\nint: 2\nfloat: 3\nbool: yes\n[sect4]\ndef: different"){usedefault = False} in+ [+ f2s "default item" (Left (NoOption "def", "get (sect1/def)")) + (get cp "sect1" "def")+ ,f2 "normal item" (Right "bar") (get cp "sect1" "foo")+ ,f2s "no option" (Left (NoOption "abc", "get (sect1/abc)")) + (get cp "sect1" "abc")+ ,f2s "no section" (Left (NoSection "sect2", "get (sect2/foo)")) + (get cp "sect2" "foo")+ ,f2s "default bad sect" (Left (NoSection "sect2", "get (sect2/def)"))+ (get cp "sect2" "def")+ ,f2 "overriding default" (Right "different") (get cp "sect4" "def")+ -- not in haskell: ,f2 "using default feature"+ -- default int+ -- default float+ -- default bool+ ]++test_instances = + let cp = p "[x]\na: true\nb: 1\nbad: never"+ in [f2 "bool 1st" (Right True) (get cp "x" "a"),+ f2 "bool 1nd" (Right True) (get cp "x" "b"),+ f2b "bad bool" (Left (ParseError "couldn't parse bool never from (x/bad)", "getbool")) (get cp "x" "bad"),+ f2 "number" (Right (1::Int)) (get cp "x" "b")+ ]++test_merge =+ let cp = p "test: foo"+ cp2 = p "test: bar"+ in [f2 "merge1" (cp) (merge emptyCP cp)+ ,f2 "merge2" (cp) (merge cp emptyCP)+ ,f2 "merge3" (cp2) (merge cp cp2)+ ,f2 "merge4" (cp) (merge cp2 cp)]++test_remove = + let cp = forceEither $ readstring emptyCP "def:ault\n[sect1]\ns1o1: v1\ns1o2:v2\n[sect2]\ns2o1: v1\ns2o2: v2\n[sect3]"+ in [+ f2 "setup" ["sect1", "sect2", "sect3"] (sections cp)+ ,f2 "remove 1st s" (Right ["sect2", "sect3"])+ (do x <- remove_section cp "sect1"+ return $ sections x+ )+ ,f2 "remove 2nd s" (Right ["sect1", "sect3"])+ (do x <- remove_section cp "sect2"+ return $ sections x+ )+ ,f2 "remove 3rd s" (Right ["sect1", "sect2"])+ (do x <- remove_section cp "sect3"+ return $ sections x+ )+ ,f2 "error handling s" (Left (NoSection "sect4", "remove_section"))+ (remove_section cp "sect4")+ ,f2 "remove an option" (Right (["sect1", "sect2", "sect3"], ["s1o2"]))+ (do x <- remove_option cp "sect1" "s1o1"+ y <- options x "sect1"+ return (sections x, y)+ )+ ,f2 "option err 1" (Left (NoSection "sect4", + "remove_option (sect4/s4o1)"))+ (remove_option cp "sect4" "s4o1")+ ,f2 "option err 2" (Left (NoOption "s1o3",+ "remove_option (sect1/s1o3)"))+ (remove_option cp "sect1" "s1o3")+ ]+ + + + ++test_ex_nomonad = + do + fh <- nullfile+ val <- readfile emptyCP testfile+ let cp = forceEither val+ hPutStr fh "Your setting is:"+ hPutStr fh $ forceEither $ get cp "file1" "location"++test_ex_errormonad = + [ + TestLabel "chaining1" $ TestCase $ + (Right ["opt1", "opt2"]) @=? + do let cp = emptyCP+ cp <- add_section cp "sect1"+ cp <- set cp "sect1" "opt1" "foo"+ cp <- set cp "sect1" "opt2" "bar"+ options cp "sect1"+ ,TestLabel "chaining2" $ TestCase $ + (Left (NoSection "sect2", "set (sect2/opt2)")) @=? + do let cp = emptyCP+ cp <- add_section cp "sect1"+ cp <- set cp "sect1" "opt1" "foo"+ cp <- set cp "sect2" "opt2" "bar"+ options cp "sect1"+ ,TestLabel "chaining3" $ TestCase $ + ["opt1", "opt2"] @=? (+ forceEither $ do let cp = emptyCP+ cp <- add_section cp "sect1"+ cp <- set cp "sect1" "opt1" "foo"+ cp <- set cp "sect1" "opt2" "bar"+ options cp "sect1"+ )+ ]++test_interp = + let interpdoc = "[DEFAULT]\narch = i386\n\n[builder]\n" +++ "filename = test_%(arch)s.c\n" +++ "dir = /usr/src/%(filename)s\n" +++ "percent = 5%%\n" +++ "bad = /usr/src/%(nonexistent)s\n" +++ "recursive = foo%(recursive)s\n" +++ "syn1 = foo%()s\n" +++ "syn2 = foo%(asdf)\n" +++ "syn3 = foo%s\n" +++ "syn4 = %\n"+ cp = (forceEither $ (readstring emptyCP interpdoc)){ accessfunc = interpolatingAccess 5}+ in+ [+ f2 "basic" (Right "i386") (get cp "DEFAULT" "arch")+ ,f2 "filename" (Right "test_i386.c") (get cp "builder" "filename")+ ,f2 "dir" (Right "/usr/src/test_i386.c") (get cp "builder" "dir")+ ,f2 "percents" (Right "5%") (get cp "builder" "percent")+ ,f2s "error" (Left (InterpolationError "unresolvable interpolation reference to \"nonexistent\"", "interpolatingAccess")) (get cp "builder" "bad")+ ,f2s "recursive" (Left (InterpolationError "maximum interpolation depth exceeded", "interpolatingAccess"))+ (get cp "builder" "recursive")+ ,f2s "syn1" (Left (InterpolationError "\"builder/syn1\" (line 1, column 6):\nunexpected \")\"\nexpecting interpolation name","interpolatingAccess"))+ (get cp "builder" "syn1")+ ,f2s "syn2" (Left (InterpolationError "\"builder/syn2\" (line 1, column 10):\nunexpected end of input\nexpecting \")s\"","interpolatingAccess"))+ (get cp "builder" "syn2")+ ,f2s "syn3" (Left (InterpolationError "\"builder/syn3\" (line 1, column 4):\nunexpected \"s\"\nexpecting \"%(\"","interpolatingAccess"))+ (get cp "builder" "syn3")+ ,f2s "syn4" (Left (InterpolationError "\"builder/syn4\" (line 1, column 1):\nunexpected end of input\nexpecting \"%(\"","interpolatingAccess"))+ (get cp "builder" "syn4")+ ]+++tests = TestList [TestLabel "test_basic" (TestList test_basic),+ TestLabel "test_defaults" (TestList test_defaults),+ TestLabel "test_nodefault" (TestList test_nodefault),+ TestLabel "test_remove" (TestList test_remove),+ TestLabel "test_ex_nomonad" (TestCase test_ex_nomonad),+ TestLabel "test_ex_errormonad" (TestList test_ex_errormonad),+ TestLabel "test_interp" (TestList test_interp),+ TestLabel "test_instances" (TestList test_instances),+ TestLabel "test_merge" (TestList test_merge)]+
+ testsrc/ConfigParser/Parsertest.hs view
@@ -0,0 +1,89 @@+{- arch-tag: ConfigParser parser tests main file+Copyright (C) 2004 John Goerzen <jgoerzen@complete.org>++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 2 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, write to the Free Software+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA+-}++module ConfigParser.Parsertest(tests) where+import Test.HUnit+import Data.ConfigFile.Parser+import Data.ConfigFile.Types+import Test.HUnit.Tools+import Control.Exception++test_basic =+ let f msg inp exp = TestLabel msg $ TestCase $ assertEqual "" (Right exp) (parse_string inp) in+ [+ f "empty string" "" []++ ,f "one empty line" "\n" []+ -- These two should go to OCaml+ ,f "one empty comment" "#" []+ ,f "one empty comment eol" "#\n" []+ ,f "one comment line" "#foo bar" []+ ,f "one comment line with eol" "#foo bar\n" []+ ,f "one empty section" "[emptysect]" [("emptysect", [])]+ ,f "one empty section w/eol" "[emptysect]\n" [("emptysect", [])]+ ,f "comment and empty sect noeol" "#foo bar\n[emptysect]"+ [("emptysect", [])]+ ,f "comment and empty sect" "#foo bar\n[emptysect]\n" [("emptysect", [])]+ ,f "comments2" "# [nonexistant]\n[emptysect]\n" [("emptysect", [])]+ ,f "comments3" "#fo\n[Cemptysect]\n#asdf boo\n \n # fnonexistantg"+ [("Cemptysect", [])]+ ,f "comments4" "[emptysect]\n# [nonexistant]\n" [("emptysect", [])]+ ,f "simple section" "[sect1]\nfoo: bar\n" [("sect1", [("foo", "bar")])]+ ,f "comments5" "\n#foo\n[sect1]\n\n#iiii \no1: v1\no2: v2\no3: v3"+ [("sect1", [("o1", "v1"), ("o2", "v2"), ("o3", "v3")])]+ ,f "comments5ext" "\n#foo\n[sect1]\n\n#iiii \no1: v1\no2: v2\n o3: v3"+ [("sect1", [("o1", "v1"), ("o2", "v2\no3: v3")])]+ ,f "comments5eol" "\n#foo\n[sect1]\n\n#iiii \no1: v1\no2: v2\no3: v3\n"+ [("sect1", [("o1", "v1"), ("o2", "v2"), ("o3", "v3")])]++ ,f "default1" "v1: o1\n[sect1]\nv2: o2" [("DEFAULT", [("v1", "o1")]),+ ("sect1", [("v2", "o2")])]+ ,f "simple default" "foo: bar" [("DEFAULT", [("foo", "bar")])]+ ]++test_asserts =+ let f msg inp exp = TestLabel msg $ TestCase $ exp @=? parse_string inp in+ [+ f "e test1" "#foo\nthis is bad data"+ (Left (ParseError "\"(string)\" (line 2, column 1):\nunexpected \"t\"\nexpecting end of input, whitespace, start of comment, empty line, start of section or option separator", "lexer"))+ ,f "e test2" "[sect1]\n#iiiiii \n extensionline\n#foo"+ (Left (ParseError "\"(string)\" (line 4, column 1):\nunexpected EXTENSIONLINE \"extensionline\"","parser"))+ ]+ +{-+ ++ assertRaises "e test1" (ErrorCall "Lexer: \"(string)\" (line 1, column 5):\nunexpected \"\\n\"\nexpecting Option separator")+ ([] @=? parse_string "#foo\nthis is bad data")++ assertRaises "e test2" (ErrorCall "Lexer: \"(string)\" (line 2, column 9):\nunexpected \"\\n\"\nexpecting Option separator")+ ([] @=? parse_string "[sect1]\n#iiiiii \n extensionline\n#foo")+-}++test_extensionlines =+ let f inp exp = (Right exp) @=? parse_string inp in+ do+ f "[sect1]\nfoo: bar\nbaz: l1\n l2\n l3\n# c\nquux: asdf"+ [("sect1", [("foo", "bar"),+ ("baz", "l1\nl2\nl3"),+ ("quux", "asdf")])]++tests = TestList [TestLabel "test_basic" (TestList test_basic),+ TestLabel "test_asserts" (TestList test_asserts),+ TestLabel "test_extensionlines" (TestCase test_extensionlines)+ ]
+ testsrc/ConfigParser/test.cfg view
@@ -0,0 +1,15 @@+# arch-tag: test file for ConfigParser tests+# Default options+[DEFAULT]+hostname: localhost +# Options for the first file+[file1]+location: /usr/local+user: Fred+uid: 1000+optionaltext: Hello, this entire string is included +[file2]+location: /opt+user: Fred+uid: 1001 +hostname: somewhere
+ testsrc/Tests.hs view
@@ -0,0 +1,30 @@+{- arch-tag: Tests main file+Copyright (C) 2004 John Goerzen <jgoerzen@complete.org>++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 2 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, write to the Free Software+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA+-}++module Tests(tests) where+import Test.HUnit+import qualified ConfigParser.Parsertest+import qualified ConfigParser.Maintest++test1 = TestCase ("x" @=? "x")++tests = TestList [TestLabel "test1" test1,+ TestLabel "ConfigParser.RunParser" ConfigParser.Parsertest.tests,+ TestLabel "ConfigParser.Main" ConfigParser.Maintest.tests]++