|
5 | 5 | import subprocess |
6 | 6 | import tempfile |
7 | 7 | import time |
8 | | -from typing import Optional, Sequence, Type |
| 8 | +import threading |
| 9 | +from typing import Any, Optional, Sequence, Type |
9 | 10 |
|
10 | 11 | from irctest.basecontrollers import ( |
11 | 12 | BaseServerController, |
@@ -372,6 +373,12 @@ class SableController(BaseServerController, DirectoryBasedController): |
372 | 373 | """Sable processes commands very quickly, but responses for commands changing the |
373 | 374 | state may be sent after later commands for messages which don't.""" |
374 | 375 |
|
| 376 | + history_controller: Optional[BaseServicesController] = None |
| 377 | + |
| 378 | + def __init__(self, *args: Any, **kwargs: Any): |
| 379 | + super().__init__(*args, **kwargs) |
| 380 | + self.history_controller = None |
| 381 | + |
375 | 382 | def run( |
376 | 383 | self, |
377 | 384 | hostname: str, |
@@ -548,6 +555,19 @@ def registerUser( |
548 | 555 | case.sendLine(client, "QUIT") |
549 | 556 | case.assertDisconnected(client) |
550 | 557 |
|
| 558 | + def wait_for_services(self) -> None: |
| 559 | + # FIXME: this isn't called when sable_history is enabled but sable_services |
| 560 | + # isn't. This doesn't happen with the existing tests so this isn't an issue yet |
| 561 | + if self.services_controller is not None: |
| 562 | + t1 = threading.Thread(target=self.services_controller.wait_for_services) |
| 563 | + t1.start() |
| 564 | + if self.history_controller is not None: |
| 565 | + t2 = threading.Thread(target=self.history_controller.wait_for_services) |
| 566 | + t2.start() |
| 567 | + t2.join() |
| 568 | + if self.services_controller is not None: |
| 569 | + t1.join() |
| 570 | + |
551 | 571 |
|
552 | 572 | class SableServicesController(BaseServicesController): |
553 | 573 | server_controller: SableController |
@@ -660,6 +680,48 @@ def run(self, protocol: str, server_hostname: str, server_port: int) -> None: |
660 | 680 | ) |
661 | 681 | self.pgroup_id = os.getpgid(self.proc.pid) |
662 | 682 |
|
| 683 | + def wait_for_services(self) -> None: |
| 684 | + """Overrides the default implementation, as it relies on |
| 685 | + ``PRIVMSG NickServ: HELP``, which always succeeds on Sable. |
| 686 | +
|
| 687 | + Instead, this relies on SASL PLAIN availability.""" |
| 688 | + if self.services_up: |
| 689 | + # Don't check again if they are already available |
| 690 | + return |
| 691 | + self.server_controller.wait_for_port() |
| 692 | + |
| 693 | + c = ClientMock(name="chkHist", show_io=True) |
| 694 | + c.connect(self.server_controller.hostname, self.server_controller.port) |
| 695 | + c.sendLine("NICK chkHist") |
| 696 | + c.sendLine("USER chk chk chk chk") |
| 697 | + time.sleep(self.server_controller.sync_sleep_time) |
| 698 | + got_end_of_motd = False |
| 699 | + while not got_end_of_motd: |
| 700 | + for msg in c.getMessages(synchronize=False): |
| 701 | + if msg.command == "PING": |
| 702 | + c.sendLine("PONG :" + msg.params[0]) |
| 703 | + if msg.command in ("376", "422"): # RPL_ENDOFMOTD / ERR_NOMOTD |
| 704 | + got_end_of_motd = True |
| 705 | + |
| 706 | + def wait(): |
| 707 | + timeout = time.time() + 10 |
| 708 | + while time.time() < timeout: |
| 709 | + c.sendLine("LINKS") |
| 710 | + time.sleep(self.server_controller.sync_sleep_time) |
| 711 | + for msg in c.getMessages(synchronize=False): |
| 712 | + if msg.command == "364": # RPL_LINKS |
| 713 | + if msg.params[2] == "My.Little.History": |
| 714 | + return |
| 715 | + |
| 716 | + raise Exception("History server is not available") |
| 717 | + |
| 718 | + wait() |
| 719 | + |
| 720 | + c.sendLine("QUIT") |
| 721 | + c.getMessages() |
| 722 | + c.disconnect() |
| 723 | + self.services_up = True |
| 724 | + |
663 | 725 | def kill_proc(self) -> None: |
664 | 726 | os.killpg(self.pgroup_id, signal.SIGKILL) |
665 | 727 | super().kill_proc() |
|
0 commit comments