-
Notifications
You must be signed in to change notification settings - Fork 50
Expand file tree
/
Copy pathmocket.py
More file actions
129 lines (103 loc) · 3.91 KB
/
mocket.py
File metadata and controls
129 lines (103 loc) · 3.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
from __future__ import annotations
import collections
import itertools
import os
from pathlib import Path
from typing import TYPE_CHECKING, Any, ClassVar
import mocket.inject
from mocket.recording import MocketRecordStorage
# NOTE this is here for backwards-compat to keep old import-paths working
# from mocket.socket import MocketSocket as MocketSocket
if TYPE_CHECKING:
from mocket.entry import MocketEntry
from mocket.types import Address
class Mocket:
_socket_pairs: ClassVar[dict[Address, tuple[int, int]]] = {}
_address: ClassVar[Address | tuple[None, None]] = (None, None)
_entries: ClassVar[dict[Address, list[MocketEntry]]] = collections.defaultdict(list)
_requests: ClassVar[list] = []
_record_storage: ClassVar[MocketRecordStorage | None] = None
@classmethod
def enable(
cls,
namespace: str | None = None,
truesocket_recording_dir: str | None = None,
) -> None:
if namespace is None:
namespace = str(id(cls._entries))
if truesocket_recording_dir is not None:
recording_dir = Path(truesocket_recording_dir)
assert recording_dir.is_dir(), f"Not a directory: {recording_dir}"
cls._record_storage = MocketRecordStorage(
directory=recording_dir,
namespace=namespace,
)
mocket.inject.enable()
@classmethod
def disable(cls) -> None:
cls.reset()
mocket.inject.disable()
@classmethod
def get_pair(cls, address: Address) -> tuple[int, int] | tuple[None, None]:
"""
Given the id() of the caller, return a pair of file descriptors
as a tuple of two integers: (<read_fd>, <write_fd>)
"""
return cls._socket_pairs.get(address, (None, None))
@classmethod
def set_pair(cls, address: Address, pair: tuple[int, int]) -> None:
"""
Store a pair of file descriptors under the key `id_`
as a tuple of two integers: (<read_fd>, <write_fd>)
"""
cls._socket_pairs[address] = pair
@classmethod
def register(cls, *entries: MocketEntry) -> None:
for entry in entries:
cls._entries[entry.location].append(entry)
@classmethod
def get_entry(cls, host: str, port: int, data) -> MocketEntry | None:
host = host or cls._address[0]
port = port or cls._address[1]
entries = cls._entries.get((host, port), [])
for entry in entries:
if entry.can_handle(data):
return entry
return None
@classmethod
def collect(cls, data) -> None:
cls._requests.append(data)
@classmethod
def reset(cls) -> None:
for r_fd, w_fd in cls._socket_pairs.values():
os.close(r_fd)
os.close(w_fd)
cls._socket_pairs = {}
cls._entries = collections.defaultdict(list)
cls._requests = []
cls._record_storage = None
@classmethod
def last_request(cls) -> Any:
if cls.has_requests():
return cls._requests[-1]
@classmethod
def request_list(cls) -> list[Any]:
return cls._requests
@classmethod
def remove_last_request(cls) -> None:
if cls.has_requests():
del cls._requests[-1]
@classmethod
def has_requests(cls) -> bool:
return bool(cls.request_list())
@classmethod
def get_namespace(cls) -> str | None:
return cls._record_storage.namespace if cls._record_storage else None
@classmethod
def get_truesocket_recording_dir(cls) -> str | None:
return str(cls._record_storage.directory) if cls._record_storage else None
@classmethod
def assert_fail_if_entries_not_served(cls) -> None:
"""Mocket checks that all entries have been served at least once."""
if not all(entry._served for entry in itertools.chain(*cls._entries.values())):
raise AssertionError("Some Mocket entries have not been served")