# Path: txtorcon/attacher.py
# class PriorityAttacher(object):
#     """
#     This can fill the role of an IStreamAttacher to which you can add
#     and remove "sub" attachers. These are consulted in order and the
#     first one to return something besides None wins. We use a "heapq"
#     priority queue, with 0 being the "most important" and higher
#     numbers indicating less important.
# 
#     For example::
# 
#         tor = yield txtorcon.connect(..)
#         attachers = txtorcon.attacher.PriorityAttacher()
# 
#         @implementer(IStreamAttacher)
#         class MyAttacher(object):
#             def __init__(self, interesting_host, circuit):
#                 self._host = interesting_host
#                 self._circuit = circuit
# 
#             def attach_stream(self, stream, circuits):
#                 if stream.target_host == self._host:
#                     return self._circuit
#                 return None
# 
#         attachers.add_attacher(MyAttacher('torproject.org', circ1))
#         attachers.add_attacher(MyAttacher('meejah.ca', circ2))
#     """
# 
#     def __init__(self):
#         # use only heapq.* to modify this; 0th item is "smallest"
#         # item. contains 3-tuples of (priority, number, attacher)
#         self._attacher_heap = []
#         # need to keep a map so we can delete from the priority-queue :(
#         self._attacher_to_entry = dict()
#         # need to keep a counter so the sorting has a tie-breaker
#         self._counter = itertools.count(0, 1)
# 
#     def add_attacher(self, attacher, priority=0):
#         """
#         Add a new IStreamAttacher at a certain priortiy; lower priority
#         values mean more important (that is, 0 is the most important).
#         """
#         item = [priority, next(self._counter), IStreamAttacher(attacher)]
#         self._attacher_to_entry[attacher] = item
#         heapq.heappush(self._attacher_heap, item)
# 
#     def remove_attacher(self, attacher):
#         try:
#             item = self._attacher_to_entry.pop(attacher)
#         except KeyError:
#             raise ValueError(
#                 "attacher {} not found".format(attacher)
#             )
#         item[-1] = None  # we can't actually remove it from the heap ...
# 
#     def attach_stream_failure(self, stream, fail):
#         pass
#     # hmm, should we try to remember which attacher answered
#     # 'something' for this stream, and then report the failure via
#     # it...? or just log all failures here?
# 
#     def attach_stream(self, stream, circuits):
#         for _, _, attacher in self._attacher_heap:
#             if attacher is not None:
#                 answer = attacher.attach_stream(stream, circuits)
#                 if answer is not None:
#                     return answer
#         return None
# 
# Path: txtorcon/interface.py
# class IStreamAttacher(Interface):
#     """
#     Used by :class:`txtorcon.TorState` to map streams to circuits (see
#     :meth:`txtorcon.TorState.set_attacher`).
# 
#     Each time a new :class:`txtorcon.Stream` is created, this
#     interface will be queried to find out which
#     :class:`txtorcon.Circuit` it should be attached to.
# 
#     Only advanced use-cases should need to use this directly; for most
#     users, using the :func:`txtorcon.Circuit.stream_via` interface
#     should be preferred.
#     """
# 
#     def attach_stream_failure(stream, fail):
#         """
#         :param stream:
#             The stream we were trying to attach.
# 
#         :param fail:
#             A Failure instance.
# 
#         A failure has occurred while trying to attach the stream.
#         """
# 
#     def attach_stream(stream, circuits):
#         """
#         :param stream:
#             The stream to attach, which will be in NEW or NEWRESOLVE
#             state.
# 
#         :param circuits:
#             all currently available :class:`txtorcon.Circuit` objects
#             in the :class:`txtorcon.TorState` in a dict indexed by id.
#             Note they are *not* limited to BUILT circuits.
# 
#         You should return a :class:`txtorcon.Circuit` instance which
#         should be at state BUILT in the currently running Tor. You may
#         also return a Deferred which will callback with the desired
#         circuit. In this case, you will probably need to be aware that
#         the callback from :meth:`txtorcon.TorState.build_circuit` does
#         not wait for the circuit to be in BUILT state.
# 
#         Alternatively, you may return None in which case the Tor
#         controller will be told to choose a circuit itself.
# 
#         Note that Tor will refuse to attach to any circuit not in
#         BUILT state; see ATTACHSTREAM in control-spec.txt
# 
#         Note also that although you get a request to attach a stream
#         that ends in .onion Tor doesn't currently let you specify how
#         to attach .onion addresses and will always give a 551 error.
#         """


from mock import Mock
from zope.interface import directlyProvides
from twisted.trial import unittest
from txtorcon.attacher import PriorityAttacher
from txtorcon.interface import IStreamAttacher




class PriorityAttacherTest(unittest.TestCase):

    def test_add_remove(self):
        a = PriorityAttacher()
        boom = Mock()
        directlyProvides(boom, IStreamAttacher)

        a.add_attacher(boom)
        a.remove_attacher(boom)
        with self.assertRaises(ValueError) as ctx:
            a.remove_attacher(boom)
        self.assertTrue('not found' in str(ctx.exception))

    def test_stream_failure(self):
        a = PriorityAttacher()
        boom = Mock()
        directlyProvides(boom, IStreamAttacher)

        a.add_attacher(boom)
        a.attach_stream_failure(Mock(), Mock())

    def test_attach_stream(self):

# Path: txtorcon/attacher.py
# class PriorityAttacher(object):
#     """
#     This can fill the role of an IStreamAttacher to which you can add
#     and remove "sub" attachers. These are consulted in order and the
#     first one to return something besides None wins. We use a "heapq"
#     priority queue, with 0 being the "most important" and higher
#     numbers indicating less important.
# 
#     For example::
# 
#         tor = yield txtorcon.connect(..)
#         attachers = txtorcon.attacher.PriorityAttacher()
# 
#         @implementer(IStreamAttacher)
#         class MyAttacher(object):
#             def __init__(self, interesting_host, circuit):
#                 self._host = interesting_host
#                 self._circuit = circuit
# 
#             def attach_stream(self, stream, circuits):
#                 if stream.target_host == self._host:
#                     return self._circuit
#                 return None
# 
#         attachers.add_attacher(MyAttacher('torproject.org', circ1))
#         attachers.add_attacher(MyAttacher('meejah.ca', circ2))
#     """
# 
#     def __init__(self):
#         # use only heapq.* to modify this; 0th item is "smallest"
#         # item. contains 3-tuples of (priority, number, attacher)
#         self._attacher_heap = []
#         # need to keep a map so we can delete from the priority-queue :(
#         self._attacher_to_entry = dict()
#         # need to keep a counter so the sorting has a tie-breaker
#         self._counter = itertools.count(0, 1)
# 
#     def add_attacher(self, attacher, priority=0):
#         """
#         Add a new IStreamAttacher at a certain priortiy; lower priority
#         values mean more important (that is, 0 is the most important).
#         """
#         item = [priority, next(self._counter), IStreamAttacher(attacher)]
#         self._attacher_to_entry[attacher] = item
#         heapq.heappush(self._attacher_heap, item)
# 
#     def remove_attacher(self, attacher):
#         try:
#             item = self._attacher_to_entry.pop(attacher)
#         except KeyError:
#             raise ValueError(
#                 "attacher {} not found".format(attacher)
#             )
#         item[-1] = None  # we can't actually remove it from the heap ...
# 
#     def attach_stream_failure(self, stream, fail):
#         pass
#     # hmm, should we try to remember which attacher answered
#     # 'something' for this stream, and then report the failure via
#     # it...? or just log all failures here?
# 
#     def attach_stream(self, stream, circuits):
#         for _, _, attacher in self._attacher_heap:
#             if attacher is not None:
#                 answer = attacher.attach_stream(stream, circuits)
#                 if answer is not None:
#                     return answer
#         return None
# 
# Path: txtorcon/interface.py
# class IStreamAttacher(Interface):
#     """
#     Used by :class:`txtorcon.TorState` to map streams to circuits (see
#     :meth:`txtorcon.TorState.set_attacher`).
# 
#     Each time a new :class:`txtorcon.Stream` is created, this
#     interface will be queried to find out which
#     :class:`txtorcon.Circuit` it should be attached to.
# 
#     Only advanced use-cases should need to use this directly; for most
#     users, using the :func:`txtorcon.Circuit.stream_via` interface
#     should be preferred.
#     """
# 
#     def attach_stream_failure(stream, fail):
#         """
#         :param stream:
#             The stream we were trying to attach.
# 
#         :param fail:
#             A Failure instance.
# 
#         A failure has occurred while trying to attach the stream.
#         """
# 
#     def attach_stream(stream, circuits):
#         """
#         :param stream:
#             The stream to attach, which will be in NEW or NEWRESOLVE
#             state.
# 
#         :param circuits:
#             all currently available :class:`txtorcon.Circuit` objects
#             in the :class:`txtorcon.TorState` in a dict indexed by id.
#             Note they are *not* limited to BUILT circuits.
# 
#         You should return a :class:`txtorcon.Circuit` instance which
#         should be at state BUILT in the currently running Tor. You may
#         also return a Deferred which will callback with the desired
#         circuit. In this case, you will probably need to be aware that
#         the callback from :meth:`txtorcon.TorState.build_circuit` does
#         not wait for the circuit to be in BUILT state.
# 
#         Alternatively, you may return None in which case the Tor
#         controller will be told to choose a circuit itself.
# 
#         Note that Tor will refuse to attach to any circuit not in
#         BUILT state; see ATTACHSTREAM in control-spec.txt
# 
#         Note also that although you get a request to attach a stream
#         that ends in .onion Tor doesn't currently let you specify how
#         to attach .onion addresses and will always give a 551 error.
#         """


# Path: test/test_attacher.py
from mock import Mock
from zope.interface import directlyProvides
from twisted.trial import unittest
from txtorcon.attacher import PriorityAttacher
from txtorcon.interface import IStreamAttacher





class PriorityAttacherTest(unittest.TestCase):

    def test_add_remove(self):
        a = PriorityAttacher()
        boom = Mock()
        directlyProvides(boom, IStreamAttacher)

        a.add_attacher(boom)
        a.remove_attacher(boom)
        with self.assertRaises(ValueError) as ctx:
            a.remove_attacher(boom)
        self.assertTrue('not found' in str(ctx.exception))

    def test_stream_failure(self):
        a = PriorityAttacher()
        boom = Mock()
        directlyProvides(boom, IStreamAttacher)

        a.add_attacher(boom)
        a.attach_stream_failure(Mock(), Mock())

    def test_attach_stream(self):

        a = PriorityAttacher()