def test_send(self) -> None: # pylint: disable=too-many-locals with temppathlib.TemporaryDirectory() as tmp_dir: env = persipubsub.environment.Environment(path=tmp_dir.path) _ = setup(env=env, sub_set={'sub'}) pub = env.new_publisher() msg = "Hello world!".encode(tests.ENCODING) pub.send(msg=msg) subscriber = "sub".encode(tests.ENCODING) with env._env.begin(write=False) as txn: self.assertIsNotNone(txn.get(key=subscriber)) sub_db = env._env.open_db(key=subscriber, txn=txn, create=False) cursor = txn.cursor(db=sub_db) self.assertTrue(cursor.first()) key = cursor.key() data_db = env._env.open_db(key=tests.DATA_DB, txn=txn, create=False) item = txn.get(key=key, db=data_db) self.assertIsNotNone(item) self.assertEqual(msg, item)
def test_local_dependency_without_init(self): # pylint: disable=invalid-name with temppathlib.TemporaryDirectory() as tmp: script_pth = tmp.path / "some_script.py" script_pth.write_text( "#!/usr/bin/env python\nimport some_module.something\n") module_pth = tmp.path / "some_module/something.py" module_pth.parent.mkdir() (tmp.path / "some_module/__init__.py").write_text("'''hello'''\n") module_pth.write_text("#!/usr/bin/env python\n'''hello'''\n") pkg = packagery.collect_dependency_graph( root_dir=tmp.path, rel_paths=[pathlib.Path("some_script.py")], requirements={}, module_to_requirement={}) self.assertListEqual([], pkg.unresolved_modules) self.assertSetEqual( { pathlib.Path("some_script.py"), pathlib.Path("some_module/something.py"), pathlib.Path("some_module/__init__.py") }, pkg.rel_paths)
def test_builtin_local_pip_dependencies(self): # pylint: disable=invalid-name with temppathlib.TemporaryDirectory() as tmp: script_pth = tmp.path / "some_script.py" script_pth.write_text("#!/usr/bin/env python\n\n" "import sys\n\n" "import PIL.Image\n\n" "import something_local\n") module_pth = tmp.path / "something_local.py" module_pth.write_text("#!/usr/bin/env python\n") requirements = packagery.parse_requirements(text="pillow==5.2.0\n") module_to_requirement = packagery.parse_module_to_requirement( text="PIL.Image\tpillow") pkg = packagery.collect_dependency_graph( root_dir=tmp.path, rel_paths=[pathlib.Path("some_script.py")], requirements=requirements, module_to_requirement=module_to_requirement) self.assertListEqual([], pkg.unresolved_modules) self.assertListEqual(["pillow"], list(pkg.requirements.keys())) self.assertSetEqual( { pathlib.Path("some_script.py"), pathlib.Path("something_local.py") }, pkg.rel_paths)
def test_overflow_limit_size(self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: env = persipubsub.environment.Environment(path=tmp_dir.path) subscriber = 'sub' _ = setup(env=env, sub_set={subscriber}) queue = env.new_publisher().queue assert queue is not None assert queue.hwm is not None queue.hwm.hwm_lmdb_size = tests.TEST_HWM_LMDB_SIZE msg = ("a" * (int(tests.LMDB_PAGE_SIZE / 4))).encode( tests.ENCODING) while queue.check_current_lmdb_size() <= tests.TEST_HWM_LMDB_SIZE: queue.put(msg=msg) self.assertTrue( queue.check_current_lmdb_size() > tests.TEST_HWM_LMDB_SIZE) queue.put(msg=msg) self.assertTrue( queue.check_current_lmdb_size() <= tests.TEST_HWM_LMDB_SIZE)
def test_put_to_single_subscriber(self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: msg = "I'm a message.".encode(tests.ENCODING) env = persipubsub.environment.Environment(path=tmp_dir.path) subscriber = 'sub' _ = setup(env=env, sub_set={subscriber}) queue = env.new_publisher().queue assert queue is not None queue.put(msg=msg) assert queue.env is not None with queue.env.begin() as txn: self.assertIsNotNone( txn.get(key=subscriber.encode(tests.ENCODING))) sub_db = queue.env.open_db(key=subscriber.encode( tests.ENCODING), txn=txn, create=False) cursor = txn.cursor(db=sub_db) self.assertTrue(cursor.first()) key = cursor.key() data_db = queue.env.open_db(key=tests.DATA_DB, txn=txn, create=False) value = txn.get(key=key, db=data_db) self.assertIsNotNone(value) self.assertEqual(msg, value)
def test_get_data(self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: env = lmdb.open(path=tmp_dir.path.as_posix(), max_dbs=2) with env.begin(write=True) as txn: queue_db = env.open_db(key=QUEUE_DB, txn=txn) txn.put(key=persipubsub.database.MESSAGE_TIMEOUT_KEY, value=persipubsub.database.int_to_bytes(500), db=queue_db) txn.put(key=persipubsub.database.MAX_MESSAGES_KEY, value=persipubsub.database.int_to_bytes(1000), db=queue_db) txn.put(key=persipubsub.database.HWM_DB_SIZE_KEY, value=persipubsub.database.int_to_bytes(1024**3), db=queue_db) txn.put(key=persipubsub.database.STRATEGY_KEY, value=persipubsub.database.str_to_bytes("prune_last"), db=queue_db) subscriber_db = env.open_db(key=SUBSCRIBER_DB, txn=txn) txn.put(key=persipubsub.database.str_to_bytes(""), db=subscriber_db) queue_data = persipubsub.database.retrieve_queue_data(env=env) env.close() self.assertEqual(500, queue_data.message_timeout) self.assertEqual(1000, queue_data.max_messages) self.assertEqual(1024**3, queue_data.hwm_db_size) self.assertEqual("prune_last", queue_data.strategy) self.assertEqual([], queue_data.subscriber_ids)
def test_queue_initialisation(self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: env = persipubsub.environment.Environment(path=tmp_dir.path) subscriber = 'sub' control = setup(env=env, sub_set={subscriber}) hwm = persipubsub.queue.HighWaterMark( message_timeout=tests.TEST_MSG_TIMEOUT, max_messages=tests.TEST_HWM_MSG_NUM, hwm_db_size=tests.TEST_HWM_LMDB_SIZE) control.set_hwm(hwm=hwm) control.set_strategy( strategy=persipubsub.queue.Strategy.PRUNE_FIRST) queue = env.new_control().queue assert queue is not None self.assertIsNotNone(queue.env) assert queue.env is not None self.assertEqual(tmp_dir.path.as_posix(), queue.env.path()) assert queue.hwm is not None self.assertEqual(tests.TEST_HWM_LMDB_SIZE, queue.hwm.hwm_lmdb_size) self.assertEqual(tests.TEST_HWM_MSG_NUM, queue.hwm.max_messages) self.assertEqual(tests.TEST_MSG_TIMEOUT, queue.hwm.message_timeout) assert queue.strategy is not None self.assertEqual(persipubsub.queue.Strategy.PRUNE_FIRST.name, queue.strategy.name) self.assertEqual({'sub'}, queue.subscriber_ids)
def test_new_subscriber(self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: env = persipubsub.environment.initialize(path=tmp_dir.path) ctl = env.new_control() ctl.init() sub = env.new_subscriber(identifier="sub") self.assertIsInstance(sub, persipubsub.subscriber.Subscriber)
def get(self, remote_path: Union[str, pathlib.Path], local_path: Union[str, pathlib.Path], create_directories: bool = True, consistent: bool = True) -> None: """ Get a file from the remote host. :param remote_path: to the file :param local_path: to the file :param create_directories: if set, creates the parent directories of the local path with permission mode 0o777 :param consistent: if set, copies to a temporary local file first, and then renames it. :return: """ rmt_pth_str = remote_path if isinstance(remote_path, str) else remote_path.as_posix() loc_pth = local_path if isinstance(local_path, pathlib.Path) else pathlib.Path(local_path) if create_directories: loc_pth.parent.mkdir(mode=0o777, exist_ok=True, parents=True) if consistent: with temppathlib.TemporaryDirectory() as local_tmpdir: tmp_pth = local_tmpdir.path / str(uuid.uuid4()) self._sftp.get(remotepath=rmt_pth_str, localpath=tmp_pth.as_posix()) shutil.move(src=tmp_pth.as_posix(), dst=loc_pth.as_posix()) else: self._sftp.get(remotepath=rmt_pth_str, localpath=loc_pth.as_posix())
def test_that_it_works(self): with temppathlib.TemporaryDirectory() as tmp_dir: pth = tmp_dir.path / 'file.txt' posixfs.atomic_write_text(path=pth, text='hello', durable=True) text = pth.read_text() self.assertEqual(text, 'hello')
def test_new_publisher(self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: env = persipubsub.environment.initialize(path=tmp_dir.path) ctl = env.new_control() ctl.init() pub = env.new_publisher() self.assertIsInstance(pub, persipubsub.publisher.Publisher)
def test_that_it_works(self): with temppathlib.TemporaryDirectory() as tmp_dir: pth = tmp_dir.path / 'file.txt' posixfs.atomic_write_bytes(path=pth, data=b'hello', durable=True) data = pth.read_bytes() self.assertEqual(data, b'hello')
def test_prune_all_messages_for_subscriber(self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: control = setup(path=tmp_dir.path, sub_set={"sub"}) msg = persipubsub.database.str_to_bytes("hello world!") assert control.queue is not None assert control.queue.env is not None control.queue.put(msg=msg) control.queue.put(msg=msg) with control.queue.env.begin(write=False) as txn: sub_db = control.queue.env.open_db( key=persipubsub.database.str_to_bytes('sub'), txn=txn, create=False) sub_stat = txn.stat(db=sub_db) self.assertEqual(2, sub_stat['entries']) control._prune_all_messages_for(sub_id="sub") with control.queue.env.begin(write=False) as txn: sub_db = control.queue.env.open_db( key=persipubsub.database.str_to_bytes('sub'), txn=txn, create=False) sub_stat = txn.stat(db=sub_db) self.assertEqual(0, sub_stat['entries'])
def test_put(self) -> None: with spurplus.TemporaryDirectory(shell=self.shell) as remote_tmpdir, \ temppathlib.TemporaryDirectory() as local_tmpdir: local_pth = local_tmpdir.path / 'file.txt' local_pth.write_text("hello") remote_pth = remote_tmpdir.path / 'file.txt' # Diff dir_diff = self.shell.directory_diff( local_path=local_tmpdir.path, remote_path=remote_tmpdir.path) self.assertListEqual([pathlib.Path('file.txt')], dir_diff.local_only_files) self.reconnecting_sftp._sftp.sock.close() # pylint: disable=protected-access self.shell.put(local_path=local_pth, remote_path=remote_pth) # Diff self.reconnecting_sftp._sftp.sock.close() # pylint: disable=protected-access dir_diff = self.shell.directory_diff( local_path=local_tmpdir.path, remote_path=remote_tmpdir.path) self.assertListEqual([pathlib.Path('file.txt')], dir_diff.identical_files)
def test_2_subscriber_non_blocking(self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: env = persipubsub.environment.initialize(path=tmp_dir.path) _ = env.new_control(subscriber_ids={'sub1', 'sub2'}) pub = env.new_publisher() sub1 = env.new_subscriber(identifier='sub1') sub2 = env.new_subscriber(identifier='sub2') pub.send(msg='msg for two subscriber'.encode('utf-8')) sub1_thread = threading.Thread(target=subscriber_receive_first, kwargs={ 'sub': sub1, }) sub2_thread = threading.Thread(target=subscriber_receive_second, kwargs={ 'sub': sub2, }) sub1_thread.start() sub2_thread.start() for thread in [sub1_thread, sub2_thread]: thread.join() assert sub2.queue is not None assert sub2.queue.env is not None assert sub2.queue.env.path() is not None assert sub2.identifier is not None result = pathlib.Path(sub2.queue.env.path()) / sub2.identifier self.assertEqual('pass', result.read_text())
def test_put_no_permission(self): with spurplus.TemporaryDirectory(shell=self.shell) as remote_tmpdir: with temppathlib.TemporaryDirectory() as local_tmpdir: local_pth = local_tmpdir.path / 'file.txt' local_pth.write_text("hello") remote_pth = remote_tmpdir.path / 'file.txt' self.shell.put(local_path=local_pth, remote_path=remote_pth) self.shell.chmod(remote_path=remote_pth, mode=0o444) # consistent put succeeds even though the remote path has read-only permissions. self.shell.put(local_path=local_pth, remote_path=remote_pth, consistent=True) a_stat = self.shell.stat(remote_path=remote_pth.as_posix()) self.assertEqual(0o100444, a_stat.st_mode) # direct put fails since we can not write to the file. with self.assertRaises(PermissionError): self.shell.put(local_path=local_pth, remote_path=remote_pth, consistent=False) # consistent put fails if we don't have write permissions to the directory try: self.shell.chmod(remote_path=remote_tmpdir.path, mode=0o444) with self.assertRaises(PermissionError): self.shell.put(local_path=local_pth, remote_path=remote_pth, consistent=True) finally: self.shell.chmod(remote_path=remote_tmpdir.path, mode=0o777)
def test_multithreaded_communication_one_publisher_one_subscriber( self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: env = persipubsub.environment.initialize(path=tmp_dir.path) _ = env.new_control({'sub'}) result = tmp_dir.path / "sub" result.touch() pub = env.new_publisher() sub = env.new_subscriber(identifier='sub') num_msg = 1000 pub_thread = threading.Thread(target=send, kwargs={ 'pub': pub, 'num_msg': num_msg }) sub_thread = threading.Thread(target=receive, kwargs={ 'sub': sub, 'num_msg': num_msg }) pub_thread.start() sub_thread.start() for thread in [pub_thread, sub_thread]: thread.join() self.assertEqual('pass', result.read_text())
def test_file_instead_of_dir(self): with spurplus.TemporaryDirectory(shell=self.shell) as remote_tmpdir, \ temppathlib.TemporaryDirectory() as local_tmpdir: local_file = local_tmpdir.path / "local-file" local_file.write_text("hello") remote_file = remote_tmpdir.path / "remote-file" self.shell.write_text(remote_path=remote_file, text="hello") local_direrr = None # type: Optional[NotADirectoryError] remote_direrr = None # type: Optional[NotADirectoryError] try: self.shell.directory_diff(local_path=local_file, remote_path=remote_tmpdir.path) except NotADirectoryError as err: local_direrr = err self.assertEqual("Local path is not a directory: {}".format(local_file), str(local_direrr)) try: self.shell.directory_diff(local_path=local_tmpdir.path, remote_path=remote_file) except NotADirectoryError as err: remote_direrr = err a_stat = self.shell.stat(remote_path=remote_file) self.assertEqual("Remote path is not a directory: {} (mode: {})".format(remote_file, a_stat.st_mode), str(remote_direrr))
def test_json(self): with temppathlib.TemporaryDirectory() as tmp: pth = tmp.path / "some_module.py" pth.write_text(TestMain.TEXT) with sys_path_with(tmp.path): buf = io.StringIO() stream = cast(TextIO, buf) args = icontract_lint.main.parse_args(sys_argv=[ "some-executable.py", pth.as_posix(), "--format", "json" ]) retcode = icontract_lint.main._main(args=args, stream=stream) self.assertEqual(1, retcode) self.assertEqual( textwrap.dedent("""\ [ {{ "identifier": "pre-invalid-arg", "description": "Precondition argument(s) are missing in the function signature: x", "filename": "{pth}", "lineno": 3 }} ]""".format(pth=pth.as_posix())), buf.getvalue())
def test_upload_two_files(self) -> None: with temppathlib.TemporaryDirectory() as local_tmpdir: tmp_folder = local_tmpdir.path / 'some-folder' tmp_folder.mkdir() tmp_path = tmp_folder / str(uuid.uuid4()) tmp_path.write_text('hello') other_folder = local_tmpdir.path / 'another-folder' other_folder.mkdir() other_file = other_folder / str(uuid.uuid4()) other_file.write_text('hello') self.client.cp( src=local_tmpdir.path.as_posix(), dst='gs://{}/{}/'.format(tests.common.TEST_GCS_BUCKET, self.bucket_prefix), recursive=True) content = self.client._bucket.get_blob(blob_name="{}/{}".format( self.bucket_prefix, tmp_path.relative_to(local_tmpdir.path.parent))) content_other_file = self.client._bucket.get_blob( blob_name="{}/{}".format( self.bucket_prefix, other_file.relative_to(local_tmpdir.path.parent))) text = content.download_as_string() text_other_file = content_other_file.download_as_string() self.assertEqual(b'hello', text) self.assertEqual(b'hello', text_other_file) content.delete() content_other_file.delete()
def test_overflow_msgs_limit(self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: env = persipubsub.environment.Environment(path=tmp_dir.path) subscriber = 'sub' _ = setup(env=env, sub_set={subscriber}) queue = env.new_publisher().queue assert queue is not None assert queue.hwm is not None queue.hwm.max_messages = tests.TEST_HWM_MSG_NUM msg = "hello world".encode(tests.ENCODING) self.assertEqual(0, queue.count_msgs()) for _ in range(tests.TEST_HWM_MSG_NUM): queue.put(msg=msg) self.assertEqual(tests.TEST_HWM_MSG_NUM, queue.count_msgs()) queue.put(msg=msg) self.assertEqual( int(tests.TEST_HWM_MSG_NUM - int(tests.TEST_HWM_MSG_NUM / 2)), queue.count_msgs())
def test_multithreaded_component_publisher_component_subscriber( self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: env = persipubsub.environment.initialize(path=tmp_dir.path) _ = env.new_control({'sub'}) result = tmp_dir.path / "sub" result.touch() num_msg = 1000 pub_thread = threading.Thread( target=tests.component_publisher.send_thread, kwargs={ 'env': env, 'num_msg': num_msg }) sub_thread = threading.Thread( target=tests.component_subscriber.receive_thread, kwargs={ 'path': tmp_dir.path, 'env': env, 'identifier': 'sub', 'num_msg': num_msg }) pub_thread.start() sub_thread.start() for thread in [pub_thread, sub_thread]: thread.join() self.assertEqual('pass', result.read_text())
def test_strategy_prune_last(self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: env = persipubsub.environment.Environment(path=tmp_dir.path) subscriber = 'sub' _ = setup(env=env, sub_set={subscriber}) queue = env.new_publisher().queue assert queue is not None queue.strategy = persipubsub.queue.Strategy.PRUNE_LAST assert queue.hwm is not None queue.hwm.max_messages = tests.TEST_HWM_MSG_NUM for index in range(tests.TEST_HWM_MSG_NUM): msg = "secret message {}".format(index).encode(tests.ENCODING) queue.put(msg=msg) _, received_msg = queue.front(sub_id='sub') self.assertEqual("secret message 0".encode(tests.ENCODING), received_msg) msg = "secret message {}".format(tests.TEST_HWM_MSG_NUM).encode( tests.ENCODING) queue.put(msg=msg) _, received_msg = queue.front(sub_id='sub') self.assertEqual("secret message 0".encode(tests.ENCODING), received_msg)
def test_multiprocess_component_publisher_component_subscriber( self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: env = persipubsub.environment.initialize(path=tmp_dir.path) _ = env.new_control({'sub'}) env._env.close() # pylint: disable=protected-access result = tmp_dir.path / "sub" result.touch() num_msg = 1000 pub_process = multiprocessing.Process( target=tests.component_publisher.send_process, kwargs={ 'path': tmp_dir.path, 'num_msg': num_msg }) sub_process = multiprocessing.Process( target=tests.component_subscriber.receive_process, kwargs={ 'path': tmp_dir.path, 'identifier': 'sub', 'num_msg': num_msg }) pub_process.start() sub_process.start() for process in [pub_process, sub_process]: process.join() self.assertEqual('pass', result.read_text())
def test_single_file(self): with temppathlib.TemporaryDirectory() as tmp: pth = tmp.path / "some_file.py" pth.write_text("hello") self.assertListEqual( [pth], packagery.resolve_initial_paths(initial_paths=[pth]))
def test_multithreaded_race_condition_of_the_component_publisher( self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: env = persipubsub.environment.initialize(path=tmp_dir.path) control = env.new_control({'sub'}) num_msg = 50 num_threads = 50 threads = [] for _ in range(num_threads): pub_thread = threading.Thread( target=tests.component_publisher.send_thread, kwargs={ 'env': env, 'num_msg': num_msg }) pub_thread.start() threads.append(pub_thread) for thread in threads: thread.join() assert control.queue is not None # pylint: disable=protected-access assert control.queue.env is not None with control.queue.env.begin(write=False) as txn: sub_db = control.queue.env.open_db(key='sub'.encode('utf-8'), txn=txn) self.assertEqual(num_msg * num_threads, txn.stat(db=sub_db)['entries'])
def test_local_dependency_with_init(self): with temppathlib.TemporaryDirectory(dont_delete=True) as tmp: script_pth = tmp.path / "some_script.py" script_pth.write_text( "#!/usr/bin/env python\nimport mapried.config\n") module_pth = tmp.path / "mapried/config/__init__.py" module_pth.parent.mkdir(parents=True) module_pth.write_text("#!/usr/bin/env python\n") (tmp.path / "mapried/__init__.py").write_text("#!/usr/bin/env python\n") pkg = packagery.collect_dependency_graph( root_dir=tmp.path, rel_paths=[pathlib.Path("some_script.py")], requirements={}, module_to_requirement={}) self.assertListEqual([], pkg.unresolved_modules) self.assertSetEqual( { pathlib.Path("some_script.py"), pathlib.Path("mapried/__init__.py"), pathlib.Path("mapried/config/__init__.py") }, pkg.rel_paths)
def test_multiprocess_race_condition_of_the_component_publisher( self) -> None: with temppathlib.TemporaryDirectory() as tmp_dir: env = persipubsub.environment.initialize(path=tmp_dir.path) control = env.new_control({'sub'}) num_msg = 50 num_processes = 50 processes = [] for _ in range(num_processes): pub_process = multiprocessing.Process( target=tests.component_publisher.send_process, kwargs={ 'path': tmp_dir.path, 'num_msg': num_msg }) pub_process.start() processes.append(pub_process) for process in processes: process.join() assert control.queue is not None # pylint: disable=protected-access assert control.queue.env is not None with control.queue.env.begin(write=False) as txn: sub_db = control.queue.env.open_db(key='sub'.encode('utf-8'), txn=txn) self.assertEqual(num_processes * num_msg, txn.stat(db=sub_db)['entries'])
def test_transitivity(self): with temppathlib.TemporaryDirectory() as tmp: script_pth = tmp.path / "some_script.py" script_pth.write_text("#!/usr/bin/env python\n\n" "import something_local\n") module_pth = tmp.path / "something_local.py" module_pth.write_text("#!/usr/bin/env python\n" "import unittest\n" "import PIL.Image\n" "import something_else_local") another_module_pth = tmp.path / "something_else_local.py" another_module_pth.write_text("#!/usr/bin/env python\n") requirements = packagery.parse_requirements(text="pillow==5.2.0\n") module_to_requirement = packagery.parse_module_to_requirement( text="PIL.Image\tpillow") pkg = packagery.collect_dependency_graph( root_dir=tmp.path, rel_paths=[pathlib.Path("some_script.py")], requirements=requirements, module_to_requirement=module_to_requirement) self.assertListEqual([], pkg.unresolved_modules) self.assertListEqual(["pillow"], list(pkg.requirements.keys())) self.assertSetEqual( { pathlib.Path("some_script.py"), pathlib.Path("something_local.py"), pathlib.Path("something_else_local.py") }, pkg.rel_paths)
def test_directory(self): with temppathlib.TemporaryDirectory() as tmp: text = textwrap.dedent("""\ from icontract import require @require(lambda x:int: x > 3) def some_func(x: int) -> int: return x """) pth = tmp.path / "some_module.py" pth.write_text(text) with sys_path_with(tmp.path): errors = icontract_lint.check_paths(paths=[tmp.path]) self.assertEqual(1, len(errors)) err = errors[0] self.assertDictEqual({ 'identifier': 'invalid-syntax', 'description': 'invalid syntax', 'filename': pth.as_posix(), 'lineno': 3 }, err.as_mapping())