D2721: util: observable proxy objects for sockets

2018-05-20 Thread pulkit (Pulkit Goyal)
pulkit added inline comments.

INLINE COMMENTS

> util.py:1014
> +
> +def setsockopt(self, level, optname, value):
> +if not self.states:

While debugging the test failure on Python 3.6, the caller at line 751 can pass 
5 positional arguments.

Here is the traceback after running test-wireproto-command-branchmap.t:

  +  ** Unknown exception encountered with possibly-broken third-party 
extension drawdag
  +  ** which supports versions unknown of Mercurial.
  +  ** Please disable drawdag and try your action again.
  +  ** If that fixes the bug please report it to the extension author.
  +  ** Python 3.6.5 (default, Mar 29 2018, 03:28:50) [GCC 5.4.0 20160609]
  +  ** Mercurial Distributed SCM (version 4.6+251-5a87bf0bd343)
  +  ** Extensions loaded: drawdag
  +  Traceback (most recent call last):
  +File "/tmp/hgtests.esettwc6/install/bin/hg", line 41, in 
  +  dispatch.run()
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/dispatch.py", 
line 90, in run
  +  status = dispatch(req)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/dispatch.py", 
line 213, in dispatch
  +  ret = _runcatch(req) or 0
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/dispatch.py", 
line 354, in _runcatch
  +  return _callcatch(ui, _runcatchfunc)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/dispatch.py", 
line 362, in _callcatch
  +  return scmutil.callcatch(ui, func)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/scmutil.py", 
line 161, in callcatch
  +  return func()
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/dispatch.py", 
line 344, in _runcatchfunc
  +  return _dispatch(req)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/dispatch.py", 
line 974, in _dispatch
  +  cmdpats, cmdoptions)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/dispatch.py", 
line 730, in runcommand
  +  ret = _runcommand(ui, options, cmd, d)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/dispatch.py", 
line 982, in _runcommand
  +  return cmdfunc()
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/dispatch.py", 
line 971, in 
  +  d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/util.py", line 
1550, in check
  +  return func(*args, **kwargs)
  +File 
"/tmp/hgtests.esettwc6/install/lib/python/mercurial/debugcommands.py", line 
2932, in debugwireproto
  +  peer = httppeer.makepeer(ui, path, opener=opener)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/httppeer.py", 
line 951, in makepeer
  +  respurl, info = performhandshake(ui, url, opener, requestbuilder)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/httppeer.py", 
line 871, in performhandshake
  +  resp = sendrequest(ui, opener, req)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/httppeer.py", 
line 311, in sendrequest
  +  res = opener.open(req)
  +File "/usr/lib/python3.6/urllib/request.py", line 526, in open
  +  response = self._open(req, data)
  +File "/usr/lib/python3.6/urllib/request.py", line 544, in _open
  +  '_open', req)
  +File "/usr/lib/python3.6/urllib/request.py", line 504, in _call_chain
  +  result = func(*args)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/url.py", line 
331, in http_open
  +  return self.do_open(self._makeconnection, req)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/keepalive.py", 
line 240, in do_open
  +  self._start_transaction(h, req)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/url.py", line 
301, in _start_transaction
  +  return keepalive.HTTPHandler._start_transaction(self, h, req)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/keepalive.py", 
line 343, in _start_transaction
  +  h.endheaders()
  +File "/usr/lib/python3.6/http/client.py", line 1234, in endheaders
  +  self._send_output(message_body, encode_chunked=encode_chunked)
  +File "/usr/lib/python3.6/http/client.py", line 1026, in _send_output
  +  self.send(msg)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/keepalive.py", 
line 563, in safesend
  +  self.connect()
  +File "/usr/lib/python3.6/http/client.py", line 937, in connect
  +  self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/util.py", line 
738, in setsockopt
  +  r'setsockopt', *args, **kwargs)
  +File "/tmp/hgtests.esettwc6/install/lib/python/mercurial/util.py", line 
679, in _observedcall
  +  fn(res, *args, **kwargs)
  +  TypeError: setsockopt() takes 4 positional arguments but 5 were given
  +  [1]

I am not sure whether this exact code path is tested on Python 2 or not. Can 
you have a look?

REPOSITORY
  rHG Mercurial


D2721: util: observable proxy objects for sockets

2018-05-20 Thread pulkit (Pulkit Goyal)
pulkit added inline comments.

INLINE COMMENTS

> util.py:1019
> +self.fh.write('%s> setsockopt(%r, %r, %r) -> %r\n' % (
> +self.name, level, optname, value))
> +

If I am understanding this correctly, this can lead to `TypeError: not enough 
arguments for format string`

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2721

To: indygreg, #hg-reviewers, durin42
Cc: pulkit, mharbison72, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2721: util: observable proxy objects for sockets

2018-03-19 Thread indygreg (Gregory Szorc)
This revision was automatically updated to reflect the committed changes.
Closed by commit rHG8453699a1f21: util: observable proxy objects for sockets 
(authored by indygreg, committed by ).

CHANGED PRIOR TO COMMIT
  https://phab.mercurial-scm.org/D2721?vs=7007=7156#toc

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2721?vs=7007=7156

REVISION DETAIL
  https://phab.mercurial-scm.org/D2721

AFFECTED FILES
  mercurial/util.py

CHANGE DETAILS

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -689,6 +689,125 @@
 
 return res
 
+PROXIED_SOCKET_METHODS = {
+r'makefile',
+r'recv',
+r'recvfrom',
+r'recvfrom_into',
+r'recv_into',
+r'send',
+r'sendall',
+r'sendto',
+r'setblocking',
+r'settimeout',
+r'gettimeout',
+r'setsockopt',
+}
+
+class socketproxy(object):
+"""A proxy around a socket that tells a watcher when events occur.
+
+This is like ``fileobjectproxy`` except for sockets.
+
+This type is intended to only be used for testing purposes. Think hard
+before using it in important code.
+"""
+__slots__ = (
+r'_orig',
+r'_observer',
+)
+
+def __init__(self, sock, observer):
+object.__setattr__(self, r'_orig', sock)
+object.__setattr__(self, r'_observer', observer)
+
+def __getattribute__(self, name):
+if name in PROXIED_SOCKET_METHODS:
+return object.__getattribute__(self, name)
+
+return getattr(object.__getattribute__(self, r'_orig'), name)
+
+def __delattr__(self, name):
+return delattr(object.__getattribute__(self, r'_orig'), name)
+
+def __setattr__(self, name, value):
+return setattr(object.__getattribute__(self, r'_orig'), name, value)
+
+def __nonzero__(self):
+return bool(object.__getattribute__(self, r'_orig'))
+
+__bool__ = __nonzero__
+
+def _observedcall(self, name, *args, **kwargs):
+# Call the original object.
+orig = object.__getattribute__(self, r'_orig')
+res = getattr(orig, name)(*args, **kwargs)
+
+# Call a method on the observer of the same name with arguments
+# so it can react, log, etc.
+observer = object.__getattribute__(self, r'_observer')
+fn = getattr(observer, name, None)
+if fn:
+fn(res, *args, **kwargs)
+
+return res
+
+def makefile(self, *args, **kwargs):
+res = object.__getattribute__(self, r'_observedcall')(
+r'makefile', *args, **kwargs)
+
+# The file object may be used for I/O. So we turn it into a
+# proxy using our observer.
+observer = object.__getattribute__(self, r'_observer')
+return makeloggingfileobject(observer.fh, res, observer.name,
+ reads=observer.reads,
+ writes=observer.writes,
+ logdata=observer.logdata)
+
+def recv(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recv', *args, **kwargs)
+
+def recvfrom(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recvfrom', *args, **kwargs)
+
+def recvfrom_into(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recvfrom_into', *args, **kwargs)
+
+def recv_into(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recv_info', *args, **kwargs)
+
+def send(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'send', *args, **kwargs)
+
+def sendall(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'sendall', *args, **kwargs)
+
+def sendto(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'sendto', *args, **kwargs)
+
+def setblocking(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'setblocking', *args, **kwargs)
+
+def settimeout(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'settimeout', *args, **kwargs)
+
+def gettimeout(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'gettimeout', *args, **kwargs)
+
+def setsockopt(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'setsockopt', *args, **kwargs)
+
 DATA_ESCAPE_MAP = {pycompat.bytechr(i): br'\x%02x' % i for i in range(256)}
 DATA_ESCAPE_MAP.update({
 b'\\': b'',
@@ -703,15 +822,7 @@
 
 return DATA_ESCAPE_RE.sub(lambda m: DATA_ESCAPE_MAP[m.group(0)], s)
 
-class fileobjectobserver(object):
-"""Logs file object 

D2721: util: observable proxy objects for sockets

2018-03-13 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 7007.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2721?vs=6972=7007

REVISION DETAIL
  https://phab.mercurial-scm.org/D2721

AFFECTED FILES
  mercurial/util.py

CHANGE DETAILS

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -687,6 +687,125 @@
 
 return res
 
+PROXIED_SOCKET_METHODS = {
+r'makefile',
+r'recv',
+r'recvfrom',
+r'recvfrom_into',
+r'recv_into',
+r'send',
+r'sendall',
+r'sendto',
+r'setblocking',
+r'settimeout',
+r'gettimeout',
+r'setsockopt',
+}
+
+class socketproxy(object):
+"""A proxy around a socket that tells a watcher when events occur.
+
+This is like ``fileobjectproxy`` except for sockets.
+
+This type is intended to only be used for testing purposes. Think hard
+before using it in important code.
+"""
+__slots__ = (
+r'_orig',
+r'_observer',
+)
+
+def __init__(self, sock, observer):
+object.__setattr__(self, r'_orig', sock)
+object.__setattr__(self, r'_observer', observer)
+
+def __getattribute__(self, name):
+if name in PROXIED_SOCKET_METHODS:
+return object.__getattribute__(self, name)
+
+return getattr(object.__getattribute__(self, r'_orig'), name)
+
+def __delattr__(self, name):
+return delattr(object.__getattribute__(self, r'_orig'), name)
+
+def __setattr__(self, name, value):
+return setattr(object.__getattribute__(self, r'_orig'), name, value)
+
+def __nonzero__(self):
+return bool(object.__getattribute__(self, r'_orig'))
+
+__bool__ = __nonzero__
+
+def _observedcall(self, name, *args, **kwargs):
+# Call the original object.
+orig = object.__getattribute__(self, r'_orig')
+res = getattr(orig, name)(*args, **kwargs)
+
+# Call a method on the observer of the same name with arguments
+# so it can react, log, etc.
+observer = object.__getattribute__(self, r'_observer')
+fn = getattr(observer, name, None)
+if fn:
+fn(res, *args, **kwargs)
+
+return res
+
+def makefile(self, *args, **kwargs):
+res = object.__getattribute__(self, r'_observedcall')(
+r'makefile', *args, **kwargs)
+
+# The file object may be used for I/O. So we turn it into a
+# proxy using our observer.
+observer = object.__getattribute__(self, r'_observer')
+return makeloggingfileobject(observer.fh, res, observer.name,
+ reads=observer.reads,
+ writes=observer.writes,
+ logdata=observer.logdata)
+
+def recv(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recv', *args, **kwargs)
+
+def recvfrom(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recvfrom', *args, **kwargs)
+
+def recvfrom_into(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recvfrom_into', *args, **kwargs)
+
+def recv_into(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recv_info', *args, **kwargs)
+
+def send(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'send', *args, **kwargs)
+
+def sendall(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'sendall', *args, **kwargs)
+
+def sendto(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'sendto', *args, **kwargs)
+
+def setblocking(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'setblocking', *args, **kwargs)
+
+def settimeout(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'settimeout', *args, **kwargs)
+
+def gettimeout(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'gettimeout', *args, **kwargs)
+
+def setsockopt(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'setsockopt', *args, **kwargs)
+
 DATA_ESCAPE_MAP = {pycompat.bytechr(i): br'\x%02x' % i for i in range(256)}
 DATA_ESCAPE_MAP.update({
 b'\\': b'',
@@ -701,15 +820,7 @@
 
 return DATA_ESCAPE_RE.sub(lambda m: DATA_ESCAPE_MAP[m.group(0)], s)
 
-class fileobjectobserver(object):
-"""Logs file object activity."""
-def __init__(self, fh, name, reads=True, writes=True, logdata=False):
-self.fh = fh
-self.name = name
-self.logdata = logdata
-self.reads = reads
-self.writes = writes
-

D2721: util: observable proxy objects for sockets

2018-03-13 Thread mharbison72 (Matt Harbison)
mharbison72 added a comment.


  fileobjectproxy needed `__nonzero__()`/`__bool__()`.  Does socketproxy need 
it for consistency?

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2721

To: indygreg, #hg-reviewers
Cc: mharbison72, mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2721: util: observable proxy objects for sockets

2018-03-13 Thread indygreg (Gregory Szorc)
indygreg requested review of this revision.
indygreg added a comment.


  This series should now be ready to review.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2721

To: indygreg, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2721: util: observable proxy objects for sockets

2018-03-13 Thread indygreg (Gregory Szorc)
indygreg planned changes to this revision.
indygreg added a comment.


  I have significant changes to this series in flight. It's worth holding off 
on review.

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2721

To: indygreg, #hg-reviewers
Cc: mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


D2721: util: observable proxy objects for sockets

2018-03-12 Thread indygreg (Gregory Szorc)
indygreg updated this revision to Diff 6972.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2721?vs=6716=6972

REVISION DETAIL
  https://phab.mercurial-scm.org/D2721

AFFECTED FILES
  mercurial/util.py

CHANGE DETAILS

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -687,6 +687,120 @@
 
 return res
 
+PROXIED_SOCKET_METHODS = {
+r'makefile',
+r'recv',
+r'recvfrom',
+r'recvfrom_into',
+r'recv_into',
+r'send',
+r'sendall',
+r'sendto',
+r'setblocking',
+r'settimeout',
+r'gettimeout',
+r'setsockopt',
+}
+
+class socketproxy(object):
+"""A proxy around a socket that tells a watcher when events occur.
+
+This is like ``fileobjectproxy`` except for sockets.
+
+This type is intended to only be used for testing purposes. Think hard
+before using it in important code.
+"""
+__slots__ = (
+r'_orig',
+r'_observer',
+)
+
+def __init__(self, sock, observer):
+object.__setattr__(self, r'_orig', sock)
+object.__setattr__(self, r'_observer', observer)
+
+def __getattribute__(self, name):
+if name in PROXIED_SOCKET_METHODS:
+return object.__getattribute__(self, name)
+
+return getattr(object.__getattribute__(self, r'_orig'), name)
+
+def __delattr__(self, name):
+return delattr(object.__getattribute__(self, r'_orig'), name)
+
+def __setattr__(self, name, value):
+return setattr(object.__getattribute__(self, r'_orig'), name, value)
+
+def _observedcall(self, name, *args, **kwargs):
+# Call the original object.
+orig = object.__getattribute__(self, r'_orig')
+res = getattr(orig, name)(*args, **kwargs)
+
+# Call a method on the observer of the same name with arguments
+# so it can react, log, etc.
+observer = object.__getattribute__(self, r'_observer')
+fn = getattr(observer, name, None)
+if fn:
+fn(res, *args, **kwargs)
+
+return res
+
+def makefile(self, *args, **kwargs):
+res = object.__getattribute__(self, r'_observedcall')(
+r'makefile', *args, **kwargs)
+
+# The file object may be used for I/O. So we turn it into a
+# proxy using our observer.
+observer = object.__getattribute__(self, r'_observer')
+return makeloggingfileobject(observer.fh, res, observer.name,
+ reads=observer.reads,
+ writes=observer.writes,
+ logdata=observer.logdata)
+
+def recv(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recv', *args, **kwargs)
+
+def recvfrom(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recvfrom', *args, **kwargs)
+
+def recvfrom_into(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recvfrom_into', *args, **kwargs)
+
+def recv_into(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recv_info', *args, **kwargs)
+
+def send(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'send', *args, **kwargs)
+
+def sendall(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'sendall', *args, **kwargs)
+
+def sendto(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'sendto', *args, **kwargs)
+
+def setblocking(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'setblocking', *args, **kwargs)
+
+def settimeout(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'settimeout', *args, **kwargs)
+
+def gettimeout(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'gettimeout', *args, **kwargs)
+
+def setsockopt(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'setsockopt', *args, **kwargs)
+
 DATA_ESCAPE_MAP = {pycompat.bytechr(i): br'\x%02x' % i for i in range(256)}
 DATA_ESCAPE_MAP.update({
 b'\\': b'',
@@ -701,15 +815,7 @@
 
 return DATA_ESCAPE_RE.sub(lambda m: DATA_ESCAPE_MAP[m.group(0)], s)
 
-class fileobjectobserver(object):
-"""Logs file object activity."""
-def __init__(self, fh, name, reads=True, writes=True, logdata=False):
-self.fh = fh
-self.name = name
-self.logdata = logdata
-self.reads = reads
-self.writes = writes
-
+class baseproxyobserver(object):
 def _writedata(self, data):
 if not self.logdata:
 

D2721: util: observable proxy objects for sockets

2018-03-07 Thread indygreg (Gregory Szorc)
indygreg created this revision.
Herald added a subscriber: mercurial-devel.
Herald added a reviewer: hg-reviewers.

REVISION SUMMARY
  We previously introduced proxy objects and observers for file objects
  to help implement low-level tests for the SSH wire protocol.
  
  In this commit, we do the same for sockets in order to help test the
  HTTP server.
  
  We only proxy/observe some socket methods. I didn't feel like
  implementing all the methods because there are so many of them and
  implementing them will provide no short term value. We can always
  implement them later.
  
  1. no-check-commit because we implement foo_bar methods on stdlib types

REPOSITORY
  rHG Mercurial

REVISION DETAIL
  https://phab.mercurial-scm.org/D2721

AFFECTED FILES
  mercurial/util.py

CHANGE DETAILS

diff --git a/mercurial/util.py b/mercurial/util.py
--- a/mercurial/util.py
+++ b/mercurial/util.py
@@ -695,6 +695,120 @@
 
 return res
 
+PROXIED_SOCKET_METHODS = {
+r'makefile',
+r'recv',
+r'recvfrom',
+r'recvfrom_into',
+r'recv_into',
+r'send',
+r'sendall',
+r'sendto',
+r'setblocking',
+r'settimeout',
+r'gettimeout',
+r'setsockopt',
+}
+
+class socketproxy(object):
+"""A proxy around a socket that tells a watcher when events occur.
+
+This is like ``fileobjectproxy`` except for sockets.
+
+This type is intended to only be used for testing purposes. Think hard
+before using it in important code.
+"""
+__slots__ = (
+r'_orig',
+r'_observer',
+)
+
+def __init__(self, sock, observer):
+object.__setattr__(self, r'_orig', sock)
+object.__setattr__(self, r'_observer', observer)
+
+def __getattribute__(self, name):
+if name in PROXIED_SOCKET_METHODS:
+return object.__getattribute__(self, name)
+
+return getattr(object.__getattribute__(self, r'_orig'), name)
+
+def __delattr__(self, name):
+return delattr(object.__getattribute__(self, r'_orig'), name)
+
+def __setattr__(self, name, value):
+return setattr(object.__getattribute__(self, r'_orig'), name, value)
+
+def _observedcall(self, name, *args, **kwargs):
+# Call the original object.
+orig = object.__getattribute__(self, r'_orig')
+res = getattr(orig, name)(*args, **kwargs)
+
+# Call a method on the observer of the same name with arguments
+# so it can react, log, etc.
+observer = object.__getattribute__(self, r'_observer')
+fn = getattr(observer, name, None)
+if fn:
+fn(res, *args, **kwargs)
+
+return res
+
+def makefile(self, *args, **kwargs):
+res = object.__getattribute__(self, r'_observedcall')(
+r'makefile', *args, **kwargs)
+
+# The file object may be used for I/O. So we turn it into a
+# proxy using our observer.
+observer = object.__getattribute__(self, r'_observer')
+return makeloggingfileobject(observer.fh, res, observer.name,
+ reads=observer.reads,
+ writes=observer.writes,
+ logdata=observer.logdata)
+
+def recv(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recv', *args, **kwargs)
+
+def recvfrom(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recvfrom', *args, **kwargs)
+
+def recvfrom_into(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recvfrom_into', *args, **kwargs)
+
+def recv_into(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'recv_info', *args, **kwargs)
+
+def send(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'send', *args, **kwargs)
+
+def sendall(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'sendall', *args, **kwargs)
+
+def sendto(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'sendto', *args, **kwargs)
+
+def setblocking(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'setblocking', *args, **kwargs)
+
+def settimeout(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'settimeout', *args, **kwargs)
+
+def gettimeout(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'gettimeout', *args, **kwargs)
+
+def setsockopt(self, *args, **kwargs):
+return object.__getattribute__(self, r'_observedcall')(
+r'setsockopt', *args, **kwargs)
+
 DATA_ESCAPE_MAP = {pycompat.bytechr(i): br'\x%02x' % i for i in range(256)}