d49bdcce6abe7708960a5478a8e1589998d18fa7
[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
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 "daisies": shape (), type "<i4">
55       13
56     <HDF5 dataset "name": shape (), type "|S1">
57 <BLANKLINE>
58     <HDF5 dataset "species": shape (), type "|S14">
59       Norwegian Blue
60     <HDF5 dataset "spouse": shape (), type "|S1">
61 <BLANKLINE>
62
63 If you want more details, you can dump with help strings.
64
65 >>> print c.dump(help=True)  # doctest: +REPORT_UDIFF, +NORMALIZE_WHITESPACE
66 name:                    (The parrot's name.  Default: .)
67 species: Norwegian Blue  (Type of parrot.  Default: Norwegian Blue.
68                           Choices: Norwegian Blue, Macaw)
69 alive: no                (The parrot is alive.  Default: no.  Choices: yes, no)
70 daisies: 13              (Number of daisies pushed up by the parrot.
71                           Default: 13.)
72 age: 1.3                 (Parrot age in years  Default: 1.3.)
73 bids: 5.4, 3.2, 1        (Prices offered for parrot.  Default: 5.4, 3.2, 1.)
74 spouse:                  (This parrot's significant other.  Default: .)
75 children:                (This parrot's children.  Default: .)
76
77 As you can see from the `age` setting, settings also support `None`,
78 even if they have numeric types.
79
80 Cleanup our temporary config file.
81
82 >>> os.remove(filename)
83 """
84
85 import os as _os
86 import sys as _sys
87 import tempfile as _tempfile
88
89 from . import LOG as _LOG
90 from . import config as _config
91 from .storage import FileStorage as _FileStorage
92 from .storage.hdf5 import HDF5_Storage
93 from .storage.yaml import YAML_Storage
94
95
96 class TestConfig (_config.Config):
97     "Test all the setting types for the h5config module"
98     settings = [
99         _config.Setting(
100             name='name',
101             help="The parrot's name."),
102         _config.ChoiceSetting(
103             name='species',
104             help='Type of parrot.',
105             default=0,
106             choices=[('Norwegian Blue', 0), ('Macaw', 1)]),
107         _config.BooleanSetting(
108             name='alive',
109             help='The parrot is alive.',
110             default=False),
111         _config.IntegerSetting(
112             name='daisies',
113             help="Number of daisies pushed up by the parrot.",
114             default=13),
115         _config.FloatSetting(
116             name='age',
117             help='Parrot age in years',
118             default=1.3),
119         _config.FloatListSetting(
120             name='bids',
121             help='Prices offered for parrot.',
122             default=[5.4, 3.2, 1]),
123         _config.ConfigSetting(
124             name='spouse',
125             help="This parrot's significant other."),
126         _config.ConfigListSetting(
127             name='children',
128             help="This parrot's children."),
129         ]
130
131 # must define self-references after completing the TestConfig class
132 for s in TestConfig.settings:
133     if s.name in ['spouse', 'children']:
134         s.config_class = TestConfig
135
136
137 def _alternative_test_config(name):
138     ret = TestConfig()
139     ret['name'] = name
140     return ret
141
142 _ALTERNATIVES = {  # alternative settings for testing
143     'name': 'Captain Flint',
144     'species': 1,
145     'alive': True,
146     'daisies': None,
147     'age': None,
148     'bids': [],
149     'spouse': _alternative_test_config(name='Lory'),
150     'children': [_alternative_test_config(name=n)
151                  for n in ['Washington Post', 'Eli Yale']],
152     }
153 # TODO: share children with spouse to test references
154
155 def test(storage=None):
156     if storage is None:
157         storage = [HDF5_Storage, YAML_Storage]
158         for s in storage:
159             test(storage=s)
160         return
161     _LOG.debug('testing {}'.format(storage))
162     _basic_tests(storage)
163     if issubclass(storage, _FileStorage):
164         _file_storage_tests(storage)
165
166 def _basic_tests(storage):
167     pass
168
169 def _file_storage_tests(storage):
170     fd,filename = _tempfile.mkstemp(
171         suffix='.'+storage.extension, prefix='h5config-')
172     _os.close(fd)
173     try:
174         c = TestConfig(storage=storage(filename=filename))
175         c.dump()
176         c.save()
177         c.load()
178         nd = list(_non_defaults(c))
179         assert not nd, nd
180         for key,value in _ALTERNATIVES.items():
181             c[key] = value
182         c.dump()
183         c.save()
184         na = dict(_non_alternatives(c))
185         assert not na, na
186         c.clear()
187         nd = list(_non_defaults(c))
188         assert not nd, nd
189         c.load()
190         na = dict(_non_alternatives(c))
191         assert not na, na
192     finally:
193         _os.remove(filename)
194
195 def _non_defaults(config):
196     for setting in TestConfig.settings:
197         value = config[setting.name]
198         if value != setting.default:
199             yield (setting.name, value)
200
201 def _non_alternatives(config, alternatives=None):
202     if alternatives is None:
203         alternatives = _ALTERNATIVES
204     for setting in TestConfig.settings:
205         value = config[setting.name]
206         alt = alternatives[setting.name]
207         if value != alt:
208             _LOG.error('{} value missmatch: {} vs {}'.format(
209                     setting.name, value, alt))
210             yield (setting.name, value)
211         elif type(value) != type(alt):
212             _LOG.error('{} type missmatch: {} vs {}'.format(
213                     setting.name, type(value), type(alt)))
214             yield (setting.name, value)