Add ListSetting and IntegerListSetting types.
[h5config.git] / h5config / test.py
1 # Copyright (C) 2011 W. Trevor King <wking@drexel.edu>
2 #
3 # This file is part of h5config.
4 #
5 # h5config is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation, either version 3 of the License, or (at your
8 # option) any later version.
9 #
10 # h5config is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 # General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License
16 # along with h5config.  If not, see <http://www.gnu.org/licenses/>.
17
18 """Define a test config object using all the setting types
19
20 The first time you a storage backend, the file it creates will
21 probably be empty or not exist.
22
23 >>> import os
24 >>> import tempfile
25 >>> from storage.hdf5 import pprint_HDF5, HDF5_Storage
26
27 >>> fd,filename = tempfile.mkstemp(suffix='.h5', prefix='h5config-')
28 >>> os.close(fd)
29
30 >>> c = TestConfig(storage=HDF5_Storage(filename=filename, group='/base'))
31 >>> c.load()
32
33 Loading will create a stub group group if it hadn't existed before.
34
35 >>> pprint_HDF5(filename)
36 /
37   /base
38
39 Saving fills in all the config values.
40
41 >>> c['syslog'] = True
42 >>> c.save()
43 >>> pprint_HDF5(filename)  # doctest: +REPORT_UDIFF, +ELLIPSIS
44 /
45   /base
46     <HDF5 dataset "age": shape (), type "<f8">
47       1.3
48     <HDF5 dataset "alive": shape (), type "|b1">
49       False
50     <HDF5 dataset "bids": shape (3,), type "<f8">
51       [ 5.4  3.2  1. ]
52     <HDF5 dataset "children": shape (), type "|S1">
53 <BLANKLINE>
54     <HDF5 dataset "claws": shape (2,), type "<i8">
55       [1 2]
56     <HDF5 dataset "daisies": shape (), type "<i...">
57       13
58     <HDF5 dataset "name": shape (), type "|S1">
59 <BLANKLINE>
60     <HDF5 dataset "species": shape (), type "|S14">
61       Norwegian Blue
62     <HDF5 dataset "spouse": shape (), type "|S1">
63 <BLANKLINE>
64     <HDF5 dataset "words": shape (2,), type "|S7">
65       ['cracker' 'wants']
66
67 If you want more details, you can dump with help strings.
68
69 >>> print c.dump(help=True)  # doctest: +REPORT_UDIFF, +NORMALIZE_WHITESPACE
70 name:                    (The parrot's name.  Default: .)
71 species: Norwegian Blue  (Type of parrot.  Default: Norwegian Blue.
72                           Choices: Norwegian Blue, Macaw)
73 alive: no                (The parrot is alive.  Default: no.  Choices: yes, no)
74 daisies: 13              (Number of daisies pushed up by the parrot.
75                           Default: 13.)
76 age: 1.3                 (Parrot age in years  Default: 1.3.)
77 words: cracker,wants     (Words known by the parrot.  Default: cracker,wants.)
78 claws: 1,2               (Claws on each foot.  Default: 1,2.)
79 bids: 5.4,3.2,1          (Prices offered for parrot.  Default: 5.4,3.2,1.)
80 spouse:                  (This parrot's significant other.  Default: .)
81 children:                (This parrot's children.  Default: .)
82
83 As you can see from the `age` setting, settings also support `None`,
84 even if they have numeric types.
85
86 Cleanup our temporary config file.
87
88 >>> os.remove(filename)
89 """
90
91 import os as _os
92 import sys as _sys
93 import tempfile as _tempfile
94
95 from . import LOG as _LOG
96 from . import config as _config
97 from .storage import FileStorage as _FileStorage
98 from .storage.hdf5 import HDF5_Storage
99 from .storage.yaml import YAML_Storage
100
101
102 class TestConfig (_config.Config):
103     "Test all the setting types for the h5config module"
104     settings = [
105         _config.Setting(
106             name='name',
107             help="The parrot's name."),
108         _config.ChoiceSetting(
109             name='species',
110             help='Type of parrot.',
111             default=0,
112             choices=[('Norwegian Blue', 0), ('Macaw', 1)]),
113         _config.BooleanSetting(
114             name='alive',
115             help='The parrot is alive.',
116             default=False),
117         _config.IntegerSetting(
118             name='daisies',
119             help="Number of daisies pushed up by the parrot.",
120             default=13),
121         _config.FloatSetting(
122             name='age',
123             help='Parrot age in years',
124             default=1.3),
125         _config.ListSetting(
126             name='words',
127             help='Words known by the parrot.',
128             default=['cracker', 'wants']),
129         _config.IntegerListSetting(
130             name='claws',
131             help='Claws on each foot.',
132             default=[1, 2]),
133         _config.FloatListSetting(
134             name='bids',
135             help='Prices offered for parrot.',
136             default=[5.4, 3.2, 1]),
137         _config.ConfigSetting(
138             name='spouse',
139             help="This parrot's significant other."),
140         _config.ConfigListSetting(
141             name='children',
142             help="This parrot's children."),
143         ]
144
145 # must define self-references after completing the TestConfig class
146 for s in TestConfig.settings:
147     if s.name in ['spouse', 'children']:
148         s.config_class = TestConfig
149
150
151 def _alternative_test_config(name):
152     ret = TestConfig()
153     ret['name'] = name
154     return ret
155
156 _ALTERNATIVES = {  # alternative settings for testing
157     'name': 'Captain Flint',
158     'species': 1,
159     'alive': True,
160     'daisies': None,
161     'age': None,
162     'words': ['arrrr', 'matey'],
163     'claws': [3, 0],
164     'bids': [],
165     'spouse': _alternative_test_config(name='Lory'),
166     'children': [_alternative_test_config(name=n)
167                  for n in ['Washington Post', 'Eli Yale']],
168     }
169 # TODO: share children with spouse to test references
170
171 def test(storage=None):
172     if storage is None:
173         storage = [HDF5_Storage, YAML_Storage]
174         for s in storage:
175             test(storage=s)
176         return
177     _LOG.debug('testing {}'.format(storage))
178     _basic_tests(storage)
179     if issubclass(storage, _FileStorage):
180         _file_storage_tests(storage)
181
182 def _basic_tests(storage):
183     pass
184
185 def _file_storage_tests(storage):
186     fd,filename = _tempfile.mkstemp(
187         suffix='.'+storage.extension, prefix='h5config-')
188     _os.close(fd)
189     try:
190         c = TestConfig(storage=storage(filename=filename))
191         c.dump()
192         c.save()
193         c.load()
194         nd = list(_non_defaults(c))
195         assert not nd, (storage, nd)
196         for key,value in _ALTERNATIVES.items():
197             c[key] = value
198         c.dump()
199         c.save()
200         na = dict(_non_alternatives(c))
201         assert not na, (storage, na)
202         c.clear()
203         nd = list(_non_defaults(c))
204         assert not nd, (storage, nd)
205         c.load()
206         na = dict(_non_alternatives(c))
207         assert not na, (storage, na)
208     finally:
209         _os.remove(filename)
210
211 def _non_defaults(config):
212     for setting in TestConfig.settings:
213         value = config[setting.name]
214         if value != setting.default:
215             yield (setting.name, value)
216
217 def _non_alternatives(config, alternatives=None):
218     if alternatives is None:
219         alternatives = _ALTERNATIVES
220     for setting in TestConfig.settings:
221         value = config[setting.name]
222         alt = alternatives[setting.name]
223         if value != alt:
224             _LOG.error('{} value missmatch: {} vs {}'.format(
225                     setting.name, value, alt))
226             yield (setting.name, value)
227         elif type(value) != type(alt):
228             _LOG.error('{} type missmatch: {} vs {}'.format(
229                     setting.name, type(value), type(alt)))
230             yield (setting.name, value)