add FileOption
[tiramisu.git] / test / test_parsing_group.py
1 # coding: utf-8
2 import autopath
3 from tiramisu.setting import groups, owners
4 from tiramisu.config import Config
5 from tiramisu.option import ChoiceOption, BoolOption, IntOption, \
6     StrOption, OptionDescription
7 from tiramisu.error import SlaveError
8
9 from py.test import raises
10
11
12 def make_description():
13     numero_etab = StrOption('numero_etab', "identifiant de l'établissement")
14     nom_machine = StrOption('nom_machine', "nom de la machine", default="eoleng")
15     nombre_interfaces = IntOption('nombre_interfaces', "nombre d'interfaces à activer",
16                                   default=1)
17     activer_proxy_client = BoolOption('activer_proxy_client', "utiliser un proxy",
18                                       default=False)
19     mode_conteneur_actif = BoolOption('mode_conteneur_actif', "le serveur est en mode conteneur",
20                                       default=False)
21     adresse_serveur_ntp = StrOption('serveur_ntp', "adresse serveur ntp", multi=True)
22     time_zone = ChoiceOption('time_zone', 'fuseau horaire du serveur',
23                              ('Paris', 'Londres'), 'Paris')
24
25     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé")
26     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau")
27
28     master = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
29     interface1 = OptionDescription('interface1', '', [master])
30     interface1.impl_set_group_type(groups.family)
31
32     general = OptionDescription('general', '', [numero_etab, nom_machine,
33                                 nombre_interfaces, activer_proxy_client,
34                                 mode_conteneur_actif, adresse_serveur_ntp,
35                                 time_zone])
36     general.impl_set_group_type(groups.family)
37     creole = OptionDescription('creole', 'first tiramisu configuration', [general, interface1])
38     descr = OptionDescription('baseconfig', 'baseconifgdescr', [creole])
39     return descr
40
41
42 def test_base_config():
43     descr = make_description()
44     config = Config(descr)
45     config.read_write()
46     assert config.creole.general.activer_proxy_client is False
47     assert config.creole.general.nom_machine == "eoleng"
48     assert config.find_first(byname='nom_machine', type_='value') == "eoleng"
49     result = {'general.numero_etab': None, 'general.nombre_interfaces': 1,
50               'general.serveur_ntp': [], 'interface1.ip_admin_eth0.ip_admin_eth0': None,
51               'general.mode_conteneur_actif': False, 'general.time_zone': 'Paris',
52               'interface1.ip_admin_eth0.netmask_admin_eth0': None, 'general.nom_machine':
53               'eoleng', 'general.activer_proxy_client': False}
54     assert config.creole.make_dict() == result
55     result = {'serveur_ntp': [], 'mode_conteneur_actif': False,
56               'ip_admin_eth0': None, 'time_zone': 'Paris', 'numero_etab': None,
57               'netmask_admin_eth0': None, 'nom_machine': 'eoleng', 'activer_proxy_client':
58               False, 'nombre_interfaces': 1}
59     assert config.creole.make_dict(flatten=True) == result
60
61
62 def test_make_dict_filter():
63     descr = make_description()
64     config = Config(descr)
65     config.read_write()
66     subresult = {'numero_etab': None, 'nombre_interfaces': 1,
67                  'serveur_ntp': [], 'mode_conteneur_actif': False,
68                  'time_zone': 'Paris', 'nom_machine': 'eoleng',
69                  'activer_proxy_client': False}
70     result = {}
71     for key, value in subresult.items():
72         result['general.' + key] = value
73     assert config.creole.make_dict(withoption='numero_etab') == result
74     raises(AttributeError, "config.creole.make_dict(withoption='numero_etab', withvalue='toto')")
75     assert config.creole.make_dict(withoption='numero_etab', withvalue=None) == result
76     assert config.creole.general.make_dict(withoption='numero_etab') == subresult
77
78
79 def test_get_group_type():
80     descr = make_description()
81     config = Config(descr)
82     config.read_write()
83     grp = config.unwrap_from_path('creole.general')
84     assert grp.impl_get_group_type() == groups.family
85     assert grp.impl_get_group_type() == 'family'
86     assert isinstance(grp.impl_get_group_type(), groups.GroupType)
87     raises(TypeError, 'grp.impl_set_group_type(groups.default)')
88
89
90 def test_iter_on_groups():
91     descr = make_description()
92     config = Config(descr)
93     config.read_write()
94     result = list(config.creole.iter_groups(group_type=groups.family))
95     group_names = [res[0] for res in result]
96     assert group_names == ['general', 'interface1']
97
98
99 def test_iter_on_empty_group():
100     config = Config(OptionDescription("name", "descr", []))
101     config.read_write()
102     result = list(config.iter_groups())
103     assert result == []
104     for i in config.iter_groups():
105         pass
106     for i in config:
107         pass
108     assert [] == list(config)
109
110
111 def test_iter_not_group():
112     config = Config(OptionDescription("name", "descr", []))
113     config.read_write()
114     raises(TypeError, "list(config.iter_groups(group_type='family'))")
115
116
117 def test_groups_with_master():
118     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
119     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
120     interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
121     interface1.impl_set_group_type(groups.master)
122     assert interface1.impl_get_group_type() == groups.master
123
124
125 def test_groups_with_master_in_config():
126     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
127     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
128     interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
129     interface1.impl_set_group_type(groups.master)
130     Config(interface1)
131     assert interface1.impl_get_group_type() == groups.master
132
133
134 def test_allowed_groups():
135     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
136     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
137     interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
138     raises(ValueError, "interface1.impl_set_group_type('toto')")
139
140
141 def test_master_not_valid_name():
142     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
143     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
144     invalid_group = OptionDescription('interface1', '', [ip_admin_eth0, netmask_admin_eth0])
145     raises(ValueError, "invalid_group.impl_set_group_type(groups.master)")
146
147
148 def test_sub_group_in_master_group():
149     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
150     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
151     subgroup = OptionDescription("subgroup", '', [])
152     invalid_group = OptionDescription('ip_admin_eth0', '', [subgroup, ip_admin_eth0, netmask_admin_eth0])
153     raises(ValueError, "invalid_group.impl_set_group_type(groups.master)")
154
155
156 def test_group_always_has_multis():
157     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
158     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau")
159     group = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
160     raises(ValueError, "group.impl_set_group_type(groups.master)")
161
162
163 #____________________________________________________________
164 def test_values_with_master_and_slaves():
165     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
166     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
167     interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
168     interface1.impl_set_group_type(groups.master)
169     maconfig = OptionDescription('toto', '', [interface1])
170     cfg = Config(maconfig)
171     cfg.read_write()
172     owner = cfg.cfgimpl_get_settings().getowner()
173     assert interface1.impl_get_group_type() == groups.master
174     assert cfg.getowner(ip_admin_eth0) == owners.default
175     assert cfg.getowner(netmask_admin_eth0) == owners.default
176     assert cfg.ip_admin_eth0.netmask_admin_eth0 == []
177     cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145")
178     assert cfg.ip_admin_eth0.ip_admin_eth0 == ["192.168.230.145"]
179     assert cfg.ip_admin_eth0.netmask_admin_eth0 == [None]
180     assert cfg.getowner(ip_admin_eth0) == owner
181     assert cfg.getowner(netmask_admin_eth0) == owners.default
182     cfg.ip_admin_eth0.ip_admin_eth0 = ["192.168.230.145", "192.168.230.147"]
183     raises(SlaveError, 'cfg.ip_admin_eth0.netmask_admin_eth0.append(None)')
184     raises(SlaveError, 'cfg.ip_admin_eth0.netmask_admin_eth0.pop(0)')
185
186
187 def test_reset_values_with_master_and_slaves():
188     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
189     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
190     interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
191     interface1.impl_set_group_type(groups.master)
192     maconfig = OptionDescription('toto', '', [interface1])
193     cfg = Config(maconfig)
194     cfg.read_write()
195     owner = cfg.cfgimpl_get_settings().getowner()
196     assert interface1.impl_get_group_type() == groups.master
197     assert cfg.getowner(ip_admin_eth0) == owners.default
198     assert cfg.getowner(netmask_admin_eth0) == owners.default
199     cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145")
200     assert cfg.getowner(ip_admin_eth0) == owner
201     assert cfg.getowner(netmask_admin_eth0) == owners.default
202     del(cfg.ip_admin_eth0.ip_admin_eth0)
203     assert cfg.getowner(ip_admin_eth0) == owners.default
204     assert cfg.getowner(netmask_admin_eth0) == owners.default
205     assert cfg.ip_admin_eth0.ip_admin_eth0 == []
206     assert cfg.ip_admin_eth0.netmask_admin_eth0 == []
207
208
209 def test_values_with_master_and_slaves_slave():
210     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
211     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
212     interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
213     interface1.impl_set_group_type(groups.master)
214     maconfig = OptionDescription('toto', '', [interface1])
215     cfg = Config(maconfig)
216     cfg.read_write()
217     assert cfg.ip_admin_eth0.netmask_admin_eth0 == []
218     raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0']")
219     cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145")
220     cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0']
221     cfg.ip_admin_eth0.netmask_admin_eth0[0] = '255.255.255.0'
222     raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0']")
223     raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = []")
224     del(cfg.ip_admin_eth0.netmask_admin_eth0)
225     cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0']
226     cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145")
227     assert cfg.ip_admin_eth0.netmask_admin_eth0 == ['255.255.255.0', None]
228     cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0']
229     raises(SlaveError, 'cfg.ip_admin_eth0.netmask_admin_eth0.pop(1)')
230
231
232 def test_values_with_master_and_slaves_master():
233     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
234     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
235     interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
236     interface1.impl_set_group_type(groups.master)
237     maconfig = OptionDescription('toto', '', [interface1])
238     cfg = Config(maconfig)
239     cfg.read_write()
240     cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145")
241     cfg.ip_admin_eth0.ip_admin_eth0 = ["192.168.230.145"]
242     cfg.ip_admin_eth0.ip_admin_eth0 = ["192.168.230.145", "192.168.230.145"]
243     cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0']
244     raises(SlaveError, 'cfg.ip_admin_eth0.ip_admin_eth0 = ["192.168.230.145"]')
245     assert cfg.ip_admin_eth0.netmask_admin_eth0 == ['255.255.255.0', '255.255.255.0']
246     cfg.ip_admin_eth0.ip_admin_eth0.pop(1)
247     assert cfg.ip_admin_eth0.ip_admin_eth0 == ["192.168.230.145"]
248     assert cfg.ip_admin_eth0.netmask_admin_eth0 == ['255.255.255.0']
249     del(cfg.ip_admin_eth0.ip_admin_eth0)
250     assert cfg.ip_admin_eth0.ip_admin_eth0 == []
251     assert cfg.ip_admin_eth0.netmask_admin_eth0 == []
252
253
254 def test_values_with_master_and_slaves_master_error():
255     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
256     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
257     interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
258     interface1.impl_set_group_type(groups.master)
259     maconfig = OptionDescription('toto', '', [interface1])
260     cfg = Config(maconfig)
261     cfg.read_write()
262     cfg.ip_admin_eth0.ip_admin_eth0 = ["192.168.230.145", "192.168.230.145"]
263     raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0']")
264     raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0', '255.255.255.0']")
265     cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0']
266     raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0']")
267     raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0 = ['255.255.255.0', '255.255.255.0', '255.255.255.0']")
268
269
270 def test_values_with_master_owner():
271     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
272     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
273     interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
274     interface1.impl_set_group_type(groups.master)
275     maconfig = OptionDescription('toto', '', [interface1])
276     cfg = Config(maconfig)
277     cfg.read_write()
278     owner = cfg.cfgimpl_get_settings().getowner()
279     assert cfg.getowner(ip_admin_eth0) == owners.default
280     assert cfg.getowner(netmask_admin_eth0) == owners.default
281     cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145")
282     assert cfg.getowner(ip_admin_eth0) == owner
283     assert cfg.getowner(netmask_admin_eth0) == owners.default
284     cfg.ip_admin_eth0.ip_admin_eth0.pop(0)
285     assert cfg.getowner(ip_admin_eth0) == owner
286     assert cfg.getowner(netmask_admin_eth0) == owners.default
287
288
289 def test_values_with_master_disabled():
290     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
291     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
292     interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
293     interface1.impl_set_group_type(groups.master)
294     maconfig = OptionDescription('toto', '', [interface1])
295     cfg = Config(maconfig)
296     cfg.read_write()
297     cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145")
298     cfg.ip_admin_eth0.ip_admin_eth0.pop(0)
299     cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145")
300     cfg.ip_admin_eth0.netmask_admin_eth0 = ["192.168.230.145"]
301     cfg.ip_admin_eth0.ip_admin_eth0.pop(0)
302     del(cfg.ip_admin_eth0.netmask_admin_eth0)
303     cfg.cfgimpl_get_settings()[netmask_admin_eth0].append('disabled')
304     cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145")
305     cfg.ip_admin_eth0.ip_admin_eth0.pop(0)
306
307     #delete with value in disabled var
308     cfg.cfgimpl_get_settings()[netmask_admin_eth0].remove('disabled')
309     cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145")
310     cfg.ip_admin_eth0.netmask_admin_eth0 = ["192.168.230.145"]
311     cfg.cfgimpl_get_settings()[netmask_admin_eth0].append('disabled')
312     cfg.ip_admin_eth0.ip_admin_eth0.pop(0)
313
314     #append with value in disabled var
315     cfg.cfgimpl_get_settings()[netmask_admin_eth0].remove('disabled')
316     cfg.ip_admin_eth0.ip_admin_eth0.append("192.168.230.145")
317     cfg.ip_admin_eth0.netmask_admin_eth0 = ["192.168.230.145"]
318     cfg.cfgimpl_get_settings()[netmask_admin_eth0].append('disabled')
319     cfg.ip_admin_eth0.ip_admin_eth0.append('192.168.230.43')
320
321
322 def test_multi_insert():
323     var = StrOption('var', '', ['ok'], multi=True)
324     od = OptionDescription('od', '', [var])
325     c = Config(od)
326     c.read_write()
327     assert c.var == ['ok']
328     assert c.getowner(var) == owners.default
329     c.var.insert(0, 'nok')
330     assert c.var == ['nok', 'ok']
331     assert c.getowner(var) != owners.default
332
333
334 def test_multi_insert_master():
335     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
336     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
337     interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
338     interface1.impl_set_group_type(groups.master)
339     maconfig = OptionDescription('toto', '', [interface1])
340     cfg = Config(maconfig)
341     cfg.read_write()
342     raises(SlaveError, "cfg.ip_admin_eth0.ip_admin_eth0.insert(0, 'nok')")
343     raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0.insert(0, 'nok')")
344
345
346 def test_multi_sort():
347     var = StrOption('var', '', ['ok', 'nok'], multi=True)
348     od = OptionDescription('od', '', [var])
349     c = Config(od)
350     c.read_write()
351     assert c.var == ['ok', 'nok']
352     assert c.getowner(var) == owners.default
353     c.var.sort()
354     assert c.var == ['nok', 'ok']
355     assert c.getowner(var) != owners.default
356
357
358 def test_multi_sort_master():
359     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
360     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
361     interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
362     interface1.impl_set_group_type(groups.master)
363     maconfig = OptionDescription('toto', '', [interface1])
364     cfg = Config(maconfig)
365     cfg.read_write()
366     raises(SlaveError, "cfg.ip_admin_eth0.ip_admin_eth0.sort()")
367     raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0.sort()")
368
369
370 def test_multi_reverse():
371     var = StrOption('var', '', ['ok', 'nok'], multi=True)
372     od = OptionDescription('od', '', [var])
373     c = Config(od)
374     c.read_write()
375     assert c.var == ['ok', 'nok']
376     assert c.getowner(var) == owners.default
377     c.var.reverse()
378     assert c.var == ['nok', 'ok']
379     assert c.getowner(var) != owners.default
380
381
382 def test_multi_reverse_master():
383     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
384     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
385     interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
386     interface1.impl_set_group_type(groups.master)
387     maconfig = OptionDescription('toto', '', [interface1])
388     cfg = Config(maconfig)
389     cfg.read_write()
390     raises(SlaveError, "cfg.ip_admin_eth0.ip_admin_eth0.reverse()")
391     raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0.reverse()")
392
393
394 def test_multi_extend():
395     var = StrOption('var', '', ['ok', 'nok'], multi=True)
396     od = OptionDescription('od', '', [var])
397     c = Config(od)
398     c.read_write()
399     assert c.var == ['ok', 'nok']
400     assert c.getowner(var) == owners.default
401     c.var.extend(['pok'])
402     assert c.var == ['ok', 'nok', 'pok']
403     assert c.getowner(var) != owners.default
404
405
406 def test_multi_extend_master():
407     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
408     netmask_admin_eth0 = StrOption('netmask_admin_eth0', "masque du sous-réseau", multi=True)
409     interface1 = OptionDescription('ip_admin_eth0', '', [ip_admin_eth0, netmask_admin_eth0])
410     interface1.impl_set_group_type(groups.master)
411     maconfig = OptionDescription('toto', '', [interface1])
412     cfg = Config(maconfig)
413     cfg.read_write()
414     raises(SlaveError, "cfg.ip_admin_eth0.ip_admin_eth0.extend(['ok'])")
415     raises(SlaveError, "cfg.ip_admin_eth0.netmask_admin_eth0.extend(['ok'])")
416
417
418 def test_multi_non_valid_value():
419     ip_admin_eth0 = StrOption('ip_admin_eth0', "ip réseau autorisé", multi=True)
420     maconfig = OptionDescription('toto', '', [ip_admin_eth0])
421     cfg = Config(maconfig)
422     cfg.read_write()
423     cfg.ip_admin_eth0 = ['a']
424     raises(ValueError, 'cfg.ip_admin_eth0[0] = 1')