29 from cubicweb.devtools.testlib import CubicWebTC |
29 from cubicweb.devtools.testlib import CubicWebTC |
30 from cubicweb.schema import CubicWebSchemaLoader |
30 from cubicweb.schema import CubicWebSchemaLoader |
31 from cubicweb.server.sqlutils import SQL_PREFIX |
31 from cubicweb.server.sqlutils import SQL_PREFIX |
32 from cubicweb.server.migractions import * |
32 from cubicweb.server.migractions import * |
33 |
33 |
|
34 import cubicweb.devtools |
|
35 |
34 migrschema = None |
36 migrschema = None |
35 def tearDownModule(*args): |
37 def tearDownModule(*args): |
36 global migrschema |
38 global migrschema |
37 del migrschema |
39 del migrschema |
38 if hasattr(MigrationCommandsTC, 'origschema'): |
40 if hasattr(MigrationCommandsTC, 'origschema'): |
39 del MigrationCommandsTC.origschema |
41 del MigrationCommandsTC.origschema |
40 |
42 |
41 class MigrationCommandsTC(CubicWebTC): |
43 class MigrationCommandsTC(CubicWebTC): |
|
44 |
|
45 configcls = cubicweb.devtools.TestServerConfiguration |
42 |
46 |
43 tags = CubicWebTC.tags | Tags(('server', 'migration', 'migractions')) |
47 tags = CubicWebTC.tags | Tags(('server', 'migration', 'migractions')) |
44 |
48 |
45 @classmethod |
49 @classmethod |
46 def _init_repo(cls): |
50 def _init_repo(cls): |
71 CubicWebTC.tearDown(self) |
75 CubicWebTC.tearDown(self) |
72 self.repo.vreg['etypes'].clear_caches() |
76 self.repo.vreg['etypes'].clear_caches() |
73 |
77 |
74 def test_add_attribute_int(self): |
78 def test_add_attribute_int(self): |
75 self.assertFalse('whatever' in self.schema) |
79 self.assertFalse('whatever' in self.schema) |
76 self.request().create_entity('Note') |
80 self.session.create_entity('Note') |
77 self.commit() |
81 self.session.commit(free_cnxset=False) |
78 orderdict = dict(self.mh.rqlexec('Any RTN, O WHERE X name "Note", RDEF from_entity X, ' |
82 orderdict = dict(self.mh.rqlexec('Any RTN, O WHERE X name "Note", RDEF from_entity X, ' |
79 'RDEF relation_type RT, RDEF ordernum O, RT name RTN')) |
83 'RDEF relation_type RT, RDEF ordernum O, RT name RTN')) |
80 self.mh.cmd_add_attribute('Note', 'whatever') |
84 self.mh.cmd_add_attribute('Note', 'whatever') |
81 self.assertTrue('whatever' in self.schema) |
85 self.assertTrue('whatever' in self.schema) |
82 self.assertEqual(self.schema['whatever'].subjects(), ('Note',)) |
86 self.assertEqual(self.schema['whatever'].subjects(), ('Note',)) |
83 self.assertEqual(self.schema['whatever'].objects(), ('Int',)) |
87 self.assertEqual(self.schema['whatever'].objects(), ('Int',)) |
84 self.assertEqual(self.schema['Note'].default('whatever'), 2) |
88 self.assertEqual(self.schema['Note'].default('whatever'), 2) |
85 # test default value set on existing entities |
89 # test default value set on existing entities |
86 note = self.execute('Note X').get_entity(0, 0) |
90 note = self.session.execute('Note X').get_entity(0, 0) |
87 self.assertEqual(note.whatever, 2) |
91 self.assertEqual(note.whatever, 2) |
88 # test default value set for next entities |
92 # test default value set for next entities |
89 self.assertEqual(self.request().create_entity('Note').whatever, 2) |
93 self.assertEqual(self.session.create_entity('Note').whatever, 2) |
90 # test attribute order |
94 # test attribute order |
91 orderdict2 = dict(self.mh.rqlexec('Any RTN, O WHERE X name "Note", RDEF from_entity X, ' |
95 orderdict2 = dict(self.mh.rqlexec('Any RTN, O WHERE X name "Note", RDEF from_entity X, ' |
92 'RDEF relation_type RT, RDEF ordernum O, RT name RTN')) |
96 'RDEF relation_type RT, RDEF ordernum O, RT name RTN')) |
93 whateverorder = migrschema['whatever'].rdef('Note', 'Int').order |
97 whateverorder = migrschema['whatever'].rdef('Note', 'Int').order |
94 for k, v in orderdict.iteritems(): |
98 for k, v in orderdict.iteritems(): |
105 # two different entities) |
109 # two different entities) |
106 self.mh.rollback() |
110 self.mh.rollback() |
107 |
111 |
108 def test_add_attribute_varchar(self): |
112 def test_add_attribute_varchar(self): |
109 self.assertFalse('whatever' in self.schema) |
113 self.assertFalse('whatever' in self.schema) |
110 self.request().create_entity('Note') |
114 self.session.create_entity('Note') |
111 self.commit() |
115 self.session.commit(free_cnxset=False) |
112 self.assertFalse('shortpara' in self.schema) |
116 self.assertFalse('shortpara' in self.schema) |
113 self.mh.cmd_add_attribute('Note', 'shortpara') |
117 self.mh.cmd_add_attribute('Note', 'shortpara') |
114 self.assertTrue('shortpara' in self.schema) |
118 self.assertTrue('shortpara' in self.schema) |
115 self.assertEqual(self.schema['shortpara'].subjects(), ('Note', )) |
119 self.assertEqual(self.schema['shortpara'].subjects(), ('Note', )) |
116 self.assertEqual(self.schema['shortpara'].objects(), ('String', )) |
120 self.assertEqual(self.schema['shortpara'].objects(), ('String', )) |
117 # test created column is actually a varchar(64) |
121 # test created column is actually a varchar(64) |
118 notesql = self.mh.sqlexec("SELECT sql FROM sqlite_master WHERE type='table' and name='%sNote'" % SQL_PREFIX)[0][0] |
122 notesql = self.mh.sqlexec("SELECT sql FROM sqlite_master WHERE type='table' and name='%sNote'" % SQL_PREFIX)[0][0] |
119 fields = dict(x.strip().split()[:2] for x in notesql.split('(', 1)[1].rsplit(')', 1)[0].split(',')) |
123 fields = dict(x.strip().split()[:2] for x in notesql.split('(', 1)[1].rsplit(')', 1)[0].split(',')) |
120 self.assertEqual(fields['%sshortpara' % SQL_PREFIX], 'varchar(64)') |
124 self.assertEqual(fields['%sshortpara' % SQL_PREFIX], 'varchar(64)') |
121 req = self.request() |
|
122 # test default value set on existing entities |
125 # test default value set on existing entities |
123 self.assertEqual(req.execute('Note X').get_entity(0, 0).shortpara, 'hop') |
126 self.assertEqual(self.session.execute('Note X').get_entity(0, 0).shortpara, 'hop') |
124 # test default value set for next entities |
127 # test default value set for next entities |
125 self.assertEqual(req.create_entity('Note').shortpara, 'hop') |
128 self.assertEqual(self.session.create_entity('Note').shortpara, 'hop') |
126 self.mh.rollback() |
129 self.mh.rollback() |
127 |
130 |
128 def test_add_datetime_with_default_value_attribute(self): |
131 def test_add_datetime_with_default_value_attribute(self): |
129 self.assertFalse('mydate' in self.schema) |
132 self.assertFalse('mydate' in self.schema) |
130 self.assertFalse('shortpara' in self.schema) |
133 self.assertFalse('shortpara' in self.schema) |
193 self.assertFalse('Folder2' in self.schema) |
196 self.assertFalse('Folder2' in self.schema) |
194 self.assertFalse('filed_under2' in self.schema) |
197 self.assertFalse('filed_under2' in self.schema) |
195 self.mh.cmd_add_entity_type('Folder2') |
198 self.mh.cmd_add_entity_type('Folder2') |
196 self.assertTrue('Folder2' in self.schema) |
199 self.assertTrue('Folder2' in self.schema) |
197 self.assertTrue('Old' in self.schema) |
200 self.assertTrue('Old' in self.schema) |
198 self.assertTrue(self.execute('CWEType X WHERE X name "Folder2"')) |
201 self.assertTrue(self.session.execute('CWEType X WHERE X name "Folder2"')) |
199 self.assertTrue('filed_under2' in self.schema) |
202 self.assertTrue('filed_under2' in self.schema) |
200 self.assertTrue(self.execute('CWRType X WHERE X name "filed_under2"')) |
203 self.assertTrue(self.session.execute('CWRType X WHERE X name "filed_under2"')) |
201 self.schema.rebuild_infered_relations() |
204 self.schema.rebuild_infered_relations() |
202 self.assertEqual(sorted(str(rs) for rs in self.schema['Folder2'].subject_relations()), |
205 self.assertEqual(sorted(str(rs) for rs in self.schema['Folder2'].subject_relations()), |
203 ['created_by', 'creation_date', 'cw_source', 'cwuri', |
206 ['created_by', 'creation_date', 'cw_source', 'cwuri', |
204 'description', 'description_format', |
207 'description', 'description_format', |
205 'eid', |
208 'eid', |
223 ensure_workflowable=False) |
226 ensure_workflowable=False) |
224 todo = wf.add_state(u'todo', initial=True) |
227 todo = wf.add_state(u'todo', initial=True) |
225 done = wf.add_state(u'done') |
228 done = wf.add_state(u'done') |
226 wf.add_transition(u'redoit', done, todo) |
229 wf.add_transition(u'redoit', done, todo) |
227 wf.add_transition(u'markasdone', todo, done) |
230 wf.add_transition(u'markasdone', todo, done) |
228 self.commit() |
231 self.session.commit(free_cnxset=False) |
229 eschema = self.schema.eschema('Folder2') |
232 eschema = self.schema.eschema('Folder2') |
230 self.mh.cmd_drop_entity_type('Folder2') |
233 self.mh.cmd_drop_entity_type('Folder2') |
231 self.assertFalse('Folder2' in self.schema) |
234 self.assertFalse('Folder2' in self.schema) |
232 self.assertFalse(self.execute('CWEType X WHERE X name "Folder2"')) |
235 self.assertFalse(self.session.execute('CWEType X WHERE X name "Folder2"')) |
233 # test automatic workflow deletion |
236 # test automatic workflow deletion |
234 self.assertFalse(self.execute('Workflow X WHERE NOT X workflow_of ET')) |
237 self.assertFalse(self.session.execute('Workflow X WHERE NOT X workflow_of ET')) |
235 self.assertFalse(self.execute('State X WHERE NOT X state_of WF')) |
238 self.assertFalse(self.session.execute('State X WHERE NOT X state_of WF')) |
236 self.assertFalse(self.execute('Transition X WHERE NOT X transition_of WF')) |
239 self.assertFalse(self.session.execute('Transition X WHERE NOT X transition_of WF')) |
237 |
240 |
238 def test_rename_entity_type(self): |
241 def test_rename_entity_type(self): |
239 entity = self.mh.create_entity('Old', name=u'old') |
242 entity = self.mh.create_entity('Old', name=u'old') |
240 self.repo.type_and_source_from_eid(entity.eid) |
243 self.repo.type_and_source_from_eid(entity.eid) |
241 self.mh.cmd_rename_entity_type('Old', 'New') |
244 self.mh.cmd_rename_entity_type('Old', 'New') |
266 self.mh.cmd_add_relation_definition('Personne', 'concerne2', 'Note') |
269 self.mh.cmd_add_relation_definition('Personne', 'concerne2', 'Note') |
267 self.assertEqual(sorted(self.schema['concerne2'].objects()), ['Affaire', 'Note']) |
270 self.assertEqual(sorted(self.schema['concerne2'].objects()), ['Affaire', 'Note']) |
268 self.mh.create_entity('Personne', nom=u'tot') |
271 self.mh.create_entity('Personne', nom=u'tot') |
269 self.mh.create_entity('Affaire') |
272 self.mh.create_entity('Affaire') |
270 self.mh.rqlexec('SET X concerne2 Y WHERE X is Personne, Y is Affaire') |
273 self.mh.rqlexec('SET X concerne2 Y WHERE X is Personne, Y is Affaire') |
271 self.commit() |
274 self.session.commit(free_cnxset=False) |
272 self.mh.cmd_drop_relation_definition('Personne', 'concerne2', 'Affaire') |
275 self.mh.cmd_drop_relation_definition('Personne', 'concerne2', 'Affaire') |
273 self.assertTrue('concerne2' in self.schema) |
276 self.assertTrue('concerne2' in self.schema) |
274 self.mh.cmd_drop_relation_definition('Personne', 'concerne2', 'Note') |
277 self.mh.cmd_drop_relation_definition('Personne', 'concerne2', 'Note') |
275 self.assertFalse('concerne2' in self.schema) |
278 self.assertFalse('concerne2' in self.schema) |
276 |
279 |
288 self.assertEqual(sorted(str(e) for e in self.schema['concerne'].subjects()), |
291 self.assertEqual(sorted(str(e) for e in self.schema['concerne'].subjects()), |
289 ['Affaire', 'Personne']) |
292 ['Affaire', 'Personne']) |
290 self.assertEqual(sorted(str(e) for e in self.schema['concerne'].objects()), |
293 self.assertEqual(sorted(str(e) for e in self.schema['concerne'].objects()), |
291 ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision']) |
294 ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision']) |
292 # trick: overwrite self.maxeid to avoid deletion of just reintroduced types |
295 # trick: overwrite self.maxeid to avoid deletion of just reintroduced types |
293 self.maxeid = self.execute('Any MAX(X)')[0][0] |
296 self.maxeid = self.session.execute('Any MAX(X)')[0][0] |
294 |
297 |
295 def test_drop_relation_definition_with_specialization(self): |
298 def test_drop_relation_definition_with_specialization(self): |
296 self.assertEqual(sorted(str(e) for e in self.schema['concerne'].subjects()), |
299 self.assertEqual(sorted(str(e) for e in self.schema['concerne'].subjects()), |
297 ['Affaire', 'Personne']) |
300 ['Affaire', 'Personne']) |
298 self.assertEqual(sorted(str(e) for e in self.schema['concerne'].objects()), |
301 self.assertEqual(sorted(str(e) for e in self.schema['concerne'].objects()), |
312 ['Affaire', 'Note', 'Societe']) |
315 ['Affaire', 'Note', 'Societe']) |
313 self.schema.rebuild_infered_relations() # need to be explicitly called once everything is in place |
316 self.schema.rebuild_infered_relations() # need to be explicitly called once everything is in place |
314 self.assertEqual(sorted(str(e) for e in self.schema['concerne'].objects()), |
317 self.assertEqual(sorted(str(e) for e in self.schema['concerne'].objects()), |
315 ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision']) |
318 ['Affaire', 'Division', 'Note', 'Societe', 'SubDivision']) |
316 # trick: overwrite self.maxeid to avoid deletion of just reintroduced types |
319 # trick: overwrite self.maxeid to avoid deletion of just reintroduced types |
317 self.maxeid = self.execute('Any MAX(X)')[0][0] |
320 self.maxeid = self.session.execute('Any MAX(X)')[0][0] |
318 |
321 |
319 def test_rename_relation(self): |
322 def test_rename_relation(self): |
320 self.skipTest('implement me') |
323 self.skipTest('implement me') |
321 |
324 |
322 def test_change_relation_props_non_final(self): |
325 def test_change_relation_props_non_final(self): |
493 ('Bookmark', 'Note'), |
496 ('Bookmark', 'Note'), |
494 ('Note', 'Note'), |
497 ('Note', 'Note'), |
495 ('Note', 'Bookmark')])) |
498 ('Note', 'Bookmark')])) |
496 self.assertEqual(sorted(schema['see_also'].subjects()), ['Bookmark', 'Folder', 'Note']) |
499 self.assertEqual(sorted(schema['see_also'].subjects()), ['Bookmark', 'Folder', 'Note']) |
497 self.assertEqual(sorted(schema['see_also'].objects()), ['Bookmark', 'Folder', 'Note']) |
500 self.assertEqual(sorted(schema['see_also'].objects()), ['Bookmark', 'Folder', 'Note']) |
498 self.assertEqual(self.execute('Any X WHERE X pkey "system.version.email"').rowcount, 0) |
501 self.assertEqual(self.session.execute('Any X WHERE X pkey "system.version.email"').rowcount, 0) |
499 self.assertEqual(self.execute('Any X WHERE X pkey "system.version.file"').rowcount, 0) |
502 self.assertEqual(self.session.execute('Any X WHERE X pkey "system.version.file"').rowcount, 0) |
500 except : |
503 except : |
501 import traceback |
504 import traceback |
502 traceback.print_exc() |
505 traceback.print_exc() |
503 raise |
506 raise |
504 finally: |
507 finally: |
518 ('Note', 'Bookmark')])) |
521 ('Note', 'Bookmark')])) |
519 self.assertEqual(sorted(schema['see_also'].subjects()), ['Bookmark', 'EmailThread', 'Folder', 'Note']) |
522 self.assertEqual(sorted(schema['see_also'].subjects()), ['Bookmark', 'EmailThread', 'Folder', 'Note']) |
520 self.assertEqual(sorted(schema['see_also'].objects()), ['Bookmark', 'EmailThread', 'Folder', 'Note']) |
523 self.assertEqual(sorted(schema['see_also'].objects()), ['Bookmark', 'EmailThread', 'Folder', 'Note']) |
521 from cubes.email.__pkginfo__ import version as email_version |
524 from cubes.email.__pkginfo__ import version as email_version |
522 from cubes.file.__pkginfo__ import version as file_version |
525 from cubes.file.__pkginfo__ import version as file_version |
523 self.assertEqual(self.execute('Any V WHERE X value V, X pkey "system.version.email"')[0][0], |
526 self.assertEqual(self.session.execute('Any V WHERE X value V, X pkey "system.version.email"')[0][0], |
524 email_version) |
527 email_version) |
525 self.assertEqual(self.execute('Any V WHERE X value V, X pkey "system.version.file"')[0][0], |
528 self.assertEqual(self.session.execute('Any V WHERE X value V, X pkey "system.version.file"')[0][0], |
526 file_version) |
529 file_version) |
527 # trick: overwrite self.maxeid to avoid deletion of just reintroduced |
530 # trick: overwrite self.maxeid to avoid deletion of just reintroduced |
528 # types (and their associated tables!) |
531 # types (and their associated tables!) |
529 self.maxeid = self.execute('Any MAX(X)')[0][0] |
532 self.maxeid = self.session.execute('Any MAX(X)')[0][0] |
530 # why this commit is necessary is unclear to me (though without it |
533 # why this commit is necessary is unclear to me (though without it |
531 # next test may fail complaining of missing tables |
534 # next test may fail complaining of missing tables |
532 self.commit() |
535 self.session.commit(free_cnxset=False) |
533 |
536 |
534 |
537 |
535 @tag('longrun') |
538 @tag('longrun') |
536 def test_add_remove_cube_no_deps(self): |
539 def test_add_remove_cube_no_deps(self): |
537 cubes = set(self.config.cubes()) |
540 cubes = set(self.config.cubes()) |
552 finally: |
555 finally: |
553 self.mh.cmd_add_cube('email') |
556 self.mh.cmd_add_cube('email') |
554 self.assertTrue('email' in self.config.cubes()) |
557 self.assertTrue('email' in self.config.cubes()) |
555 # trick: overwrite self.maxeid to avoid deletion of just reintroduced |
558 # trick: overwrite self.maxeid to avoid deletion of just reintroduced |
556 # types (and their associated tables!) |
559 # types (and their associated tables!) |
557 self.maxeid = self.execute('Any MAX(X)')[0][0] |
560 self.maxeid = self.session.execute('Any MAX(X)')[0][0] |
558 # why this commit is necessary is unclear to me (though without it |
561 # why this commit is necessary is unclear to me (though without it |
559 # next test may fail complaining of missing tables |
562 # next test may fail complaining of missing tables |
560 self.commit() |
563 self.session.commit(free_cnxset=False) |
561 |
564 |
562 def test_remove_dep_cube(self): |
565 def test_remove_dep_cube(self): |
563 with self.assertRaises(ConfigurationError) as cm: |
566 with self.assertRaises(ConfigurationError) as cm: |
564 self.mh.cmd_remove_cube('file') |
567 self.mh.cmd_remove_cube('file') |
565 self.assertEqual(str(cm.exception), "can't remove cube file, used as a dependency") |
568 self.assertEqual(str(cm.exception), "can't remove cube file, used as a dependency") |
575 self.mh.repo.schema.rebuild_infered_relations() |
578 self.mh.repo.schema.rebuild_infered_relations() |
576 self.assertEqual(sorted(et.type for et in self.schema['Para'].specialized_by()), |
579 self.assertEqual(sorted(et.type for et in self.schema['Para'].specialized_by()), |
577 ['Note', 'Text']) |
580 ['Note', 'Text']) |
578 self.assertEqual(self.schema['Text'].specializes().type, 'Para') |
581 self.assertEqual(self.schema['Text'].specializes().type, 'Para') |
579 # test columns have been actually added |
582 # test columns have been actually added |
580 text = self.execute('INSERT Text X: X para "hip", X summary "hop", X newattr "momo"').get_entity(0, 0) |
583 text = self.session.execute('INSERT Text X: X para "hip", X summary "hop", X newattr "momo"').get_entity(0, 0) |
581 note = self.execute('INSERT Note X: X para "hip", X shortpara "hop", X newattr "momo", X unique_id "x"').get_entity(0, 0) |
584 note = self.session.execute('INSERT Note X: X para "hip", X shortpara "hop", X newattr "momo", X unique_id "x"').get_entity(0, 0) |
582 aff = self.execute('INSERT Affaire X').get_entity(0, 0) |
585 aff = self.session.execute('INSERT Affaire X').get_entity(0, 0) |
583 self.assertTrue(self.execute('SET X newnotinlined Y WHERE X eid %(x)s, Y eid %(y)s', |
586 self.assertTrue(self.session.execute('SET X newnotinlined Y WHERE X eid %(x)s, Y eid %(y)s', |
584 {'x': text.eid, 'y': aff.eid})) |
587 {'x': text.eid, 'y': aff.eid})) |
585 self.assertTrue(self.execute('SET X newnotinlined Y WHERE X eid %(x)s, Y eid %(y)s', |
588 self.assertTrue(self.session.execute('SET X newnotinlined Y WHERE X eid %(x)s, Y eid %(y)s', |
586 {'x': note.eid, 'y': aff.eid})) |
589 {'x': note.eid, 'y': aff.eid})) |
587 self.assertTrue(self.execute('SET X newinlined Y WHERE X eid %(x)s, Y eid %(y)s', |
590 self.assertTrue(self.session.execute('SET X newinlined Y WHERE X eid %(x)s, Y eid %(y)s', |
588 {'x': text.eid, 'y': aff.eid})) |
591 {'x': text.eid, 'y': aff.eid})) |
589 self.assertTrue(self.execute('SET X newinlined Y WHERE X eid %(x)s, Y eid %(y)s', |
592 self.assertTrue(self.session.execute('SET X newinlined Y WHERE X eid %(x)s, Y eid %(y)s', |
590 {'x': note.eid, 'y': aff.eid})) |
593 {'x': note.eid, 'y': aff.eid})) |
591 # XXX remove specializes by ourselves, else tearDown fails when removing |
594 # XXX remove specializes by ourselves, else tearDown fails when removing |
592 # Para because of Note inheritance. This could be fixed by putting the |
595 # Para because of Note inheritance. This could be fixed by putting the |
593 # MemSchemaCWETypeDel(session, name) operation in the |
596 # MemSchemaCWETypeDel(session, name) operation in the |
594 # after_delete_entity(CWEType) hook, since in that case the MemSchemaSpecializesDel |
597 # after_delete_entity(CWEType) hook, since in that case the MemSchemaSpecializesDel |
596 # |
599 # |
597 # also we need more tests about introducing/removing base classes or |
600 # also we need more tests about introducing/removing base classes or |
598 # specialization relationship... |
601 # specialization relationship... |
599 self.session.data['rebuild-infered'] = True |
602 self.session.data['rebuild-infered'] = True |
600 try: |
603 try: |
601 self.execute('DELETE X specializes Y WHERE Y name "Para"') |
604 self.session.execute('DELETE X specializes Y WHERE Y name "Para"') |
602 self.commit() |
605 self.session.commit(free_cnxset=False) |
603 finally: |
606 finally: |
604 self.session.data['rebuild-infered'] = False |
607 self.session.data['rebuild-infered'] = False |
605 self.assertEqual(sorted(et.type for et in self.schema['Para'].specialized_by()), |
608 self.assertEqual(sorted(et.type for et in self.schema['Para'].specialized_by()), |
606 []) |
609 []) |
607 self.assertEqual(self.schema['Note'].specializes(), None) |
610 self.assertEqual(self.schema['Note'].specializes(), None) |