1 from __future__ import absolute_import, print_function, unicode_literals
4 from lxml.html import fromstring
9 from six.moves.urllib.parse import urljoin
11 from django.conf import settings
12 from django.core import mail
13 from django.core.exceptions import ObjectDoesNotExist
14 from django.core.urlresolvers import reverse
15 from django.test import TestCase
16 from django.utils.encoding import smart_text
18 from .models import SequencingRun, Sequencer, FlowCell, FileType
19 from samples.models import HTSUser
20 from .experiments import flowcell_information, lanes_for
21 from .experiments_factory import FlowCellFactory, LaneFactory
22 from samples.samples_factory import AffiliationFactory, HTSUserFactory, \
23 LibraryFactory, LibraryTypeFactory, MultiplexIndexFactory
24 from htsworkflow.auth import apidata
25 from htsworkflow.util.ethelp import validate_xhtml
27 from htsworkflow.pipelines.test.simulate_runfolder import TESTDATA_DIR
29 LANE_SET = range(1, 9)
31 NSMAP = {'libns': 'http://jumpgate.caltech.edu/wiki/LibraryOntology#'}
34 class ExperimentsTestCases(TestCase):
36 # Generate at least one fleshed out example flowcell
37 self.tempdir = tempfile.mkdtemp(prefix='htsw-test-experiments-')
38 settings.RESULT_HOME_DIR = self.tempdir
40 self.password = 'password'
41 self.user_odd = HTSUserFactory(username='user-odd')
42 self.user_odd.set_password(self.password)
43 self.affiliation_odd = AffiliationFactory(name='affiliation-odd', users=[self.user_odd])
44 self.user_even = HTSUserFactory(username='user-even')
45 self.user_even.set_password(self.password)
46 self.affiliation_even = AffiliationFactory(name='affiliation-even', users=[self.user_even])
47 self.admin = HTSUserFactory.create(username='admin', is_staff=True, is_superuser=True)
48 self.admin.set_password(self.password)
51 self.fc12150 = FlowCellFactory(flowcell_id='FC12150')
52 self.fc1_id = 'FC12150'
53 self.fc1_root = os.path.join(self.tempdir, self.fc1_id)
54 os.mkdir(self.fc1_root)
55 self.fc1_dir = os.path.join(self.fc1_root, 'C1-37')
56 os.mkdir(self.fc1_dir)
57 runxml = 'run_FC12150_2007-09-27.xml'
58 shutil.copy(os.path.join(TESTDATA_DIR, runxml),
59 os.path.join(self.fc1_dir, runxml))
61 affiliation = self.affiliation_odd if i % 2 == 1 else self.affiliation_even
62 library = LibraryFactory(id="1215" + str(i))
63 library.affiliations.add(affiliation)
64 lane = LaneFactory(flowcell=self.fc12150, lane_number=i, library=library)
66 os.path.join(TESTDATA_DIR,
67 'woldlab_070829_USI-EAS44_0017_FC11055_1.srf'),
68 os.path.join(self.fc1_dir,
69 'woldlab_070829_SERIAL_FC12150_%d.srf' %(i,))
73 self.fc42jtn = FlowCellFactory(flowcell_id='42JTNAAXX')
74 self.fc42jtn_lanes = []
76 affiliation = self.affiliation_odd if i % 2 == 1 else self.affiliation_even
77 library_type = LibraryTypeFactory(can_multiplex=True)
78 multiplex_index = MultiplexIndexFactory(adapter_type=library_type)
79 library = LibraryFactory(id="1300" + str(i),
80 library_type=library_type,
81 multiplex_id=multiplex_index.multiplex_id)
82 library.affiliations.add(affiliation)
83 lane = LaneFactory(flowcell=self.fc42jtn, lane_number=(i % 2) + 1, library=library)
84 self.fc42jtn_lanes.append(lane)
86 self.fc2_dir = os.path.join(self.tempdir, '42JTNAAXX')
87 os.mkdir(self.fc2_dir)
88 os.mkdir(os.path.join(self.fc2_dir, 'C1-25'))
89 os.mkdir(os.path.join(self.fc2_dir, 'C1-37'))
90 os.mkdir(os.path.join(self.fc2_dir, 'C1-37', 'Plots'))
93 shutil.rmtree(self.tempdir)
95 def test_flowcell_information(self):
97 Check the code that packs the django objects into simple types.
99 fc12150 = self.fc12150
100 fc42jtn = self.fc42jtn
101 fc42ju1 = FlowCellFactory(flowcell_id='42JU1AAXX')
103 for fc_id in ['FC12150', '42JTNAAXX', '42JU1AAXX']:
104 fc_dict = flowcell_information(fc_id)
105 fc_django = FlowCell.objects.get(flowcell_id=fc_id)
106 self.assertEqual(fc_dict['flowcell_id'], fc_id)
107 self.assertEqual(fc_django.flowcell_id, fc_id)
108 self.assertEqual(fc_dict['sequencer'], fc_django.sequencer.name)
109 self.assertEqual(fc_dict['read_length'], fc_django.read_length)
110 self.assertEqual(fc_dict['notes'], fc_django.notes)
111 self.assertEqual(fc_dict['cluster_station'], fc_django.cluster_station.name)
113 for lane in fc_django.lane_set.all():
114 lane_contents = fc_dict['lane_set'][lane.lane_number]
115 lane_dict = multi_lane_to_dict(lane_contents)[lane.library_id]
116 self.assertEqual(lane_dict['cluster_estimate'], lane.cluster_estimate)
117 self.assertEqual(lane_dict['comment'], lane.comment)
118 self.assertEqual(lane_dict['flowcell'], lane.flowcell.flowcell_id)
119 self.assertEqual(lane_dict['lane_number'], lane.lane_number)
120 self.assertEqual(lane_dict['library_name'], lane.library.library_name)
121 self.assertEqual(lane_dict['library_id'], lane.library.id)
122 self.assertAlmostEqual(float(lane_dict['pM']), float(lane.pM))
123 self.assertEqual(lane_dict['library_species'],
124 lane.library.library_species.scientific_name)
126 response = self.client.get('/experiments/config/%s/json' % (fc_id,), apidata)
127 # strptime isoformat string = '%Y-%m-%dT%H:%M:%S'
128 fc_json = json.loads(smart_text(response.content))['result']
129 self.assertEqual(fc_json['flowcell_id'], fc_id)
130 self.assertEqual(fc_json['sequencer'], fc_django.sequencer.name)
131 self.assertEqual(fc_json['read_length'], fc_django.read_length)
132 self.assertEqual(fc_json['notes'], fc_django.notes)
133 self.assertEqual(fc_json['cluster_station'], fc_django.cluster_station.name)
136 for lane in fc_django.lane_set.all():
137 lane_contents = fc_json['lane_set'][str(lane.lane_number)]
138 lane_dict = multi_lane_to_dict(lane_contents)[lane.library_id]
140 self.assertEqual(lane_dict['cluster_estimate'], lane.cluster_estimate)
141 self.assertEqual(lane_dict['comment'], lane.comment)
142 self.assertEqual(lane_dict['flowcell'], lane.flowcell.flowcell_id)
143 self.assertEqual(lane_dict['lane_number'], lane.lane_number)
144 self.assertEqual(lane_dict['library_name'], lane.library.library_name)
145 self.assertEqual(lane_dict['library_id'], lane.library.id)
146 self.assertAlmostEqual(float(lane_dict['pM']), float(lane.pM))
147 self.assertEqual(lane_dict['library_species'],
148 lane.library.library_species.scientific_name)
150 def test_invalid_flowcell(self):
152 Make sure we get a 404 if we request an invalid flowcell ID
154 response = self.client.get('/experiments/config/nottheone/json', apidata)
155 self.assertEqual(response.status_code, 404)
157 def test_no_key(self):
159 Require logging in to retrieve meta data
161 response = self.client.get('/experiments/config/FC12150/json')
162 self.assertEqual(response.status_code, 403)
164 def test_library_id(self):
165 """make sure we can retrive a non-numeric library ID
167 response = self.client.get('/experiments/config/FC12150/json', apidata)
168 self.assertEqual(response.status_code, 200)
169 flowcell = json.loads(smart_text(response.content))['result']
171 # library id is 12150 + lane number (1-8), so 12153
172 lane_contents = flowcell['lane_set']['3']
173 lane_library = lane_contents[0]
174 self.assertEqual(lane_library['library_id'], '12153')
176 response = self.client.get('/samples/library/12153/json', apidata)
177 self.assertEqual(response.status_code, 200)
178 library_12153 = json.loads(smart_text(response.content))['result']
180 self.assertEqual(library_12153['library_id'], '12153')
182 def test_raw_id_field(self):
185 Library's have IDs, libraries also have primary keys,
186 we eventually had enough libraries that the drop down combo
187 box was too hard to filter through, unfortnately we want a
188 field that uses our library id and not the internal
189 primary key, and raw_id_field uses primary keys.
191 This tests to make sure that the value entered in the raw
192 library id field matches the library id looked up.
195 expected_ids = [ '1215{}'.format(i) for i in range(1,9) ]
196 self.assertTrue(self.client.login(username=self.admin.username, password=self.password))
197 response = self.client.get('/admin/experiments/flowcell/{}/'.format(self.fc12150.id))
199 tree = fromstring(response.content)
201 xpath_expression = '//input[@id="id_lane_set-%d-library"]'
202 input_field = tree.xpath(xpath_expression % (i,))[0]
203 library_field = input_field.find('../strong')
204 library_id, library_name = library_field.text.split(':')
205 # strip leading '#' sign from name
206 library_id = library_id[1:]
207 self.assertEqual(library_id, expected_ids[i])
208 self.assertEqual(input_field.attrib['value'], library_id)
210 def test_library_to_flowcell_link(self):
212 Make sure the library page includes links to the flowcell pages.
213 That work with flowcell IDs that have parenthetical comments.
215 self.assertTrue(self.client.login(username=self.admin.username, password=self.password))
216 response = self.client.get('/library/12151/')
217 self.assertEqual(response.status_code, 200)
218 status = validate_xhtml(response.content)
219 if status is not None: self.assertTrue(status)
221 tree = fromstring(response.content)
222 flowcell_spans = tree.xpath('//span[@property="libns:flowcell_id"]',
224 self.assertEqual(flowcell_spans[1].text, 'FC12150')
225 failed_fc_span = flowcell_spans[1]
226 failed_fc_a = failed_fc_span.getparent()
227 # make sure some of our RDF made it.
228 self.assertEqual(failed_fc_a.get('typeof'), 'libns:IlluminaFlowcell')
229 self.assertEqual(failed_fc_a.get('href'), '/flowcell/FC12150/')
230 fc_response = self.client.get(failed_fc_a.get('href'))
231 self.assertEqual(fc_response.status_code, 200)
232 status = validate_xhtml(response.content)
233 if status is not None: self.assertTrue(status)
235 fc_lane_response = self.client.get('/flowcell/FC12150/8/')
236 self.assertEqual(fc_lane_response.status_code, 200)
237 status = validate_xhtml(response.content)
238 if status is not None: self.assertTrue(status)
240 def test_pooled_multiplex_id(self):
241 fc_dict = flowcell_information(self.fc42jtn.flowcell_id)
243 lane_contents = fc_dict['lane_set'][2]
244 self.assertEqual(len(lane_contents), len(self.fc42jtn_lanes) / 2)
245 lane_dict = multi_lane_to_dict(lane_contents)
247 self.assertTrue(self.fc42jtn_lanes[0].library.multiplex_id in \
248 lane_dict['13001']['index_sequence'])
249 self.assertTrue(self.fc42jtn_lanes[2].library.multiplex_id in \
250 lane_dict['13003']['index_sequence'])
253 def test_lanes_for_view_user_odd(self):
254 """Make sure lanes_for HTML UI works.
256 user = self.user_odd.username
257 lanes = lanes_for(user)
258 self.assertEqual(len(lanes), 8)
260 response = self.client.get(
261 reverse('experiments.views.lanes_for',
263 self.assertEqual(response.status_code, 200)
264 tree = fromstring(response.content)
265 lane_trs = tree.xpath('//div[@id="changelist"]/table/tbody/tr')
266 self.assertEqual(len(lane_trs), len(lanes))
267 # lanes is in db order
268 # lane_trs is in newest to oldest order
269 for lane_tr, lane_db in zip(lane_trs, reversed(lanes)):
270 library_id = lane_tr.xpath('td[6]/a')[0].text
271 self.assertEqual(library_id, lane_db['library'])
273 def test_lanes_for_view_invalid_user(self):
274 """Make sure we don't find anything with an invalid user
276 response = self.client.get(
277 reverse('experiments.views.lanes_for',
278 args=["doesntexist"]))
279 self.assertEqual(response.status_code, 404)
281 def test_lanes_for_json(self):
283 Check the code that packs the django objects into simple types.
285 user = self.user_odd.username
286 lanes = lanes_for(user)
287 self.assertEqual(len(lanes), 8)
289 response = self.client.get('/experiments/lanes_for/%s/json' % (user,), apidata)
290 lanes_json = json.loads(smart_text(response.content))['result']
291 self.assertEqual(len(lanes), len(lanes_json))
292 for i in range(len(lanes)):
293 self.assertEqual(lanes[i]['comment'], lanes_json[i]['comment'])
294 self.assertEqual(lanes[i]['lane_number'], lanes_json[i]['lane_number'])
295 self.assertEqual(lanes[i]['flowcell'], lanes_json[i]['flowcell'])
296 self.assertEqual(lanes[i]['run_date'], lanes_json[i]['run_date'])
298 def test_lanes_for_no_lanes(self):
300 Do we get something meaningful back when the user isn't attached to anything?
302 user = HTSUserFactory.create(username='supertest')
303 lanes = lanes_for(user.username)
304 self.assertEqual(len(lanes), 0)
306 response = self.client.get('/experiments/lanes_for/%s/json' % (user,), apidata)
307 self.assertEqual(response.status_code, 404)
309 def test_lanes_for_no_user(self):
311 Do we get something meaningful back when its the wrong user
313 user = 'not a real user'
314 self.assertRaises(ObjectDoesNotExist, lanes_for, user)
316 response = self.client.get('/experiments/lanes_for/%s/json' % (user,), apidata)
317 self.assertEqual(response.status_code, 404)
319 def test_raw_data_dir(self):
320 """Raw data path generator check"""
321 flowcell_id = self.fc1_id
322 raw_dir = os.path.join(settings.RESULT_HOME_DIR, flowcell_id)
324 fc = FlowCell.objects.get(flowcell_id=flowcell_id)
325 self.assertEqual(fc.get_raw_data_directory(), raw_dir)
327 fc.flowcell_id = flowcell_id + " (failed)"
328 self.assertEqual(fc.get_raw_data_directory(), raw_dir)
330 def test_sequencing_run_import(self):
331 srf_file_type = FileType.objects.get(name='SRF')
332 runxml_file_type = FileType.objects.get(name='run_xml')
333 flowcell_id = self.fc1_id
334 flowcell = FlowCell.objects.get(flowcell_id=flowcell_id)
335 flowcell.update_sequencing_runs()
336 self.assertEqual(len(flowcell.sequencingrun_set.all()), 1)
338 run = flowcell.sequencingrun_set.all()[0]
339 result_files = run.datafile_set.all()
340 result_dict = dict(((rf.relative_pathname, rf) for rf in result_files))
342 srf4 = result_dict['FC12150/C1-37/woldlab_070829_SERIAL_FC12150_4.srf']
343 self.assertEqual(srf4.file_type, srf_file_type)
344 self.assertEqual(srf4.library_id, '12154')
345 self.assertEqual(srf4.sequencing_run.flowcell.flowcell_id, 'FC12150')
347 srf4.sequencing_run.flowcell.lane_set.get(lane_number=4).library_id,
351 os.path.join(settings.RESULT_HOME_DIR, srf4.relative_pathname))
353 lane_files = run.lane_files()
354 self.assertEqual(lane_files[4]['srf'], srf4)
356 runxml= result_dict['FC12150/C1-37/run_FC12150_2007-09-27.xml']
357 self.assertEqual(runxml.file_type, runxml_file_type)
358 self.assertEqual(runxml.library_id, None)
360 import1 = len(SequencingRun.objects.filter(result_dir='FC12150/C1-37'))
361 # what happens if we import twice?
362 flowcell.import_sequencing_run('FC12150/C1-37',
363 'run_FC12150_2007-09-27.xml')
365 len(SequencingRun.objects.filter(result_dir='FC12150/C1-37')),
368 def test_read_result_file(self):
369 """make sure we can return a result file
371 flowcell_id = self.fc1_id
372 flowcell = FlowCell.objects.get(flowcell_id=flowcell_id)
373 flowcell.update_sequencing_runs()
375 #self.client.login(username='supertest', password='BJOKL5kAj6aFZ6A5')
377 result_files = flowcell.sequencingrun_set.all()[0].datafile_set.all()
378 for f in result_files:
379 url = '/experiments/file/%s' % ( f.random_key,)
380 response = self.client.get(url)
381 self.assertEqual(response.status_code, 200)
382 mimetype = f.file_type.mimetype
384 mimetype = 'application/octet-stream'
386 self.assertEqual(mimetype, response['content-type'])
388 def test_flowcell_rdf(self):
390 from htsworkflow.util.rdfhelp import get_model, \
392 load_string_into_model, \
399 expected = {'1': ['12151'],
407 url = '/flowcell/{}/'.format(self.fc12150.flowcell_id)
408 response = self.client.get(url)
409 self.assertEqual(response.status_code, 200)
410 status = validate_xhtml(response.content)
411 if status is not None: self.assertTrue(status)
413 ns = urljoin('http://localhost', url)
414 load_string_into_model(model, 'rdfa', smart_text(response.content), ns=ns)
415 body = """prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
416 prefix libns: <http://jumpgate.caltech.edu/wiki/LibraryOntology#>
418 select ?flowcell ?flowcell_id ?lane_id ?library_id
420 ?flowcell a libns:IlluminaFlowcell ;
421 libns:flowcell_id ?flowcell_id ;
422 libns:has_lane ?lane .
423 ?lane libns:lane_number ?lane_id ;
424 libns:library ?library .
425 ?library libns:library_id ?library_id .
427 query = RDF.SPARQLQuery(body)
429 for r in query.execute(model):
431 self.assertEqual(fromTypedNode(r['flowcell_id']), 'FC12150')
432 lane_id = fromTypedNode(r['lane_id'])
433 library_id = fromTypedNode(r['library_id'])
434 self.assertTrue(library_id in expected[lane_id])
435 self.assertEqual(count, 8)
437 class TestEmailNotify(TestCase):
439 self.password = 'foo27'
440 self.user = HTSUserFactory.create(username='test')
441 self.user.set_password(self.password)
443 self.admin = HTSUserFactory.create(username='admintest', is_staff=True)
444 self.admin.set_password(self.password)
446 self.super = HTSUserFactory.create(username='supertest', is_staff=True, is_superuser=True)
447 self.super.set_password(self.password)
450 self.library = LibraryFactory.create()
451 self.affiliation = AffiliationFactory()
452 self.affiliation.users.add(self.user)
453 self.library.affiliations.add(self.affiliation)
454 self.fc = FlowCellFactory.create()
455 self.lane = LaneFactory(flowcell=self.fc, lane_number=1, library=self.library)
457 self.url = '/experiments/started/{}/'.format(self.fc.id)
459 def test_started_email_not_logged_in(self):
460 response = self.client.get(self.url)
461 self.assertEqual(response.status_code, 302)
463 def test_started_email_logged_in_user(self):
464 self.assertTrue(self.client.login(username=self.user.username, password=self.password))
465 response = self.client.get(self.url)
466 self.assertEqual(response.status_code, 302)
468 def test_started_email_logged_in_staff(self):
469 self.assertTrue(self.admin.is_staff)
470 admin = HTSUser.objects.get(username=self.admin.username)
471 self.assertTrue(admin.is_staff)
472 self.assertTrue(admin.check_password(self.password))
473 self.assertTrue(self.client.login(username=self.admin.username, password=self.password))
474 response = self.client.get(self.url)
475 self.assertEqual(response.status_code, 200)
477 def test_started_email_send(self):
478 self.assertTrue(self.client.login(username=self.admin.username, password=self.password))
479 response = self.client.get(self.url)
480 self.assertEqual(response.status_code, 200)
482 self.assertTrue(self.affiliation.email in smart_text(response.content))
483 self.assertTrue(self.library.library_name in smart_text(response.content))
485 response = self.client.get(self.url, {'send':'1','bcc':'on'})
486 self.assertEqual(response.status_code, 200)
487 self.assertEqual(len(mail.outbox), 2)
488 bcc = set(settings.NOTIFICATION_BCC).copy()
489 bcc.update(set(settings.MANAGERS))
490 for m in mail.outbox:
491 self.assertTrue(len(m.body) > 0)
492 self.assertEqual(set(m.bcc), bcc)
494 def test_email_navigation(self):
496 Can we navigate between the flowcell and email forms properly?
498 admin_url = '/admin/experiments/flowcell/{}/'.format(self.fc.id)
499 self.client.login(username=self.admin.username, password=self.password)
500 response = self.client.get(self.url)
501 self.assertEqual(response.status_code, 200)
502 #print("email navigation content:", response.content)
503 self.assertTrue(re.search(self.fc.flowcell_id, smart_text(response.content)))
504 # require that navigation back to the admin page exists
505 self.assertTrue(re.search('<a href="{}">[^<]+</a>'.format(admin_url),
506 smart_text(response.content)))
508 def multi_lane_to_dict(lane):
509 """Convert a list of lane entries into a dictionary indexed by library ID
511 return dict( ((x['library_id'],x) for x in lane) )
513 class TestSequencer(TestCase):
515 self.fc12150 = FlowCellFactory(flowcell_id='FC12150')
516 self.library = LibraryFactory(id="12150")
517 self.lane = LaneFactory(flowcell=self.fc12150, lane_number=1, library=self.library)
519 def test_name_generation(self):
522 seq.instrument_name = "HWI-SEQ1"
523 seq.model = "Imaginary 5000"
525 self.assertEqual(str(seq), "Seq1 (HWI-SEQ1)")
527 def test_lookup(self):
529 self.assertEqual(fc.sequencer.model, 'HiSeq 1')
530 self.assertTrue(fc.sequencer.instrument_name.startswith('instrument name')),
531 # well actually we let the browser tack on the host name
532 url = fc.get_absolute_url()
533 self.assertEqual(url, '/flowcell/FC12150/')
536 response = self.client.get('/flowcell/FC12150/', apidata)
537 tree = fromstring(response.content)
538 seq_by = tree.xpath('//div[@rel="libns:sequenced_by"]',
540 self.assertEqual(len(seq_by), 1)
541 self.assertEqual(seq_by[0].attrib['rel'], 'libns:sequenced_by')
542 seq = seq_by[0].getchildren()
543 self.assertEqual(len(seq), 1)
544 sequencer = '/sequencer/' + str(self.fc12150.sequencer.id)
545 self.assertEqual(seq[0].attrib['about'], sequencer)
546 self.assertEqual(seq[0].attrib['typeof'], 'libns:Sequencer')
548 name = seq[0].xpath('./span[@property="libns:sequencer_name"]')
549 self.assertEqual(len(name), 1)
550 self.assertTrue(name[0].text.startswith('sequencer '))
551 instrument = seq[0].xpath(
552 './span[@property="libns:sequencer_instrument"]')
553 self.assertEqual(len(instrument), 1)
554 self.assertTrue(instrument[0].text.startswith('instrument name'))
555 model = seq[0].xpath(
556 './span[@property="libns:sequencer_model"]')
557 self.assertEqual(len(model), 1)
558 self.assertEqual(model[0].text, 'HiSeq 1')
560 def test_flowcell_with_rdf_validation(self):
561 from htsworkflow.util.rdfhelp import add_default_schemas, \
564 load_string_into_model
565 from htsworkflow.util.rdfinfer import Infer
568 add_default_schemas(model)
569 inference = Infer(model)
571 url ='/flowcell/FC12150/'
572 response = self.client.get(url)
573 self.assertEqual(response.status_code, 200)
574 status = validate_xhtml(response.content)
575 if status is not None: self.assertTrue(status)
577 load_string_into_model(model, 'rdfa', smart_text(response.content))
579 errmsgs = list(inference.run_validation())
580 self.assertEqual(len(errmsgs), 0)
582 def test_lane_with_rdf_validation(self):
583 from htsworkflow.util.rdfhelp import add_default_schemas, \
586 load_string_into_model
587 from htsworkflow.util.rdfinfer import Infer
590 add_default_schemas(model)
591 inference = Infer(model)
593 url = '/lane/{}'.format(self.lane.id)
594 response = self.client.get(url)
595 rdfbody = smart_text(response.content)
596 self.assertEqual(response.status_code, 200)
597 status = validate_xhtml(rdfbody)
598 if status is not None: self.assertTrue(status)
600 load_string_into_model(model, 'rdfa', rdfbody)
602 errmsgs = list(inference.run_validation())
603 self.assertEqual(len(errmsgs), 0)
606 from unittest import TestSuite, defaultTestLoader
608 for testcase in [ExerimentsTestCases,
611 suite.addTests(defaultTestLoader.loadTestsFromTestCase(testcase))
614 if __name__ == "__main__":
615 from unittest import main
616 main(defaultTest="suite")