|
26 | 26 |
|
27 | 27 | ;;; Code: |
28 | 28 |
|
29 | | -(require 'zmq) |
30 | | -(require 'jupyter-zmq-channel-comm) |
31 | 29 | (require 'jupyter-env) |
32 | 30 | (require 'jupyter-client) |
33 | 31 | (require 'jupyter-repl) |
|
493 | 491 | msg) |
494 | 492 | (cons "idle" "foo"))))) |
495 | 493 |
|
496 | | -;;; Channels |
497 | | - |
498 | | -(ert-deftest jupyter-zmq-channel () |
499 | | - :tags '(channels zmq) |
500 | | - (let* ((port (car (jupyter-available-local-ports 1))) |
501 | | - (channel (jupyter-zmq-channel |
502 | | - :type :shell |
503 | | - :endpoint (format "tcp://127.0.0.1:%s" port)))) |
504 | | - (ert-info ("Starting the channel") |
505 | | - (should-not (jupyter-channel-alive-p channel)) |
506 | | - (jupyter-start-channel channel :identity "foo") |
507 | | - (should (jupyter-channel-alive-p channel)) |
508 | | - (should (equal (zmq-socket-get (oref channel socket) |
509 | | - zmq-ROUTING-ID) |
510 | | - "foo"))) |
511 | | - (ert-info ("Stopping the channel") |
512 | | - (let ((sock (oref channel socket))) |
513 | | - (jupyter-stop-channel channel) |
514 | | - (should-not (jupyter-channel-alive-p channel)) |
515 | | - ;; Ensure the socket was disconnected |
516 | | - (should-error (zmq-send sock "foo" zmq-NOBLOCK) :type 'zmq-EAGAIN))))) |
517 | | - |
518 | | -(ert-deftest jupyter-hb-channel () |
519 | | - :tags '(channels) |
520 | | - (should (eq (oref (jupyter-hb-channel) type) :hb)) |
521 | | - (let* ((port (car (jupyter-available-local-ports 1))) |
522 | | - (channel (jupyter-hb-channel |
523 | | - :endpoint (format "tcp://127.0.0.1:%s" port) |
524 | | - :session (jupyter-session))) |
525 | | - (died-cb-called nil) |
526 | | - (jupyter-hb-max-failures 1)) |
527 | | - (oset channel time-to-dead 0.1) |
528 | | - (should-not (jupyter-channel-alive-p channel)) |
529 | | - (should-not (jupyter-hb-beating-p channel)) |
530 | | - (should (oref channel paused)) |
531 | | - (oset channel beating t) |
532 | | - (jupyter-start-channel channel) |
533 | | - (jupyter-hb-on-kernel-dead channel (lambda () (setq died-cb-called t))) |
534 | | - (should (jupyter-channel-alive-p channel)) |
535 | | - ;; `jupyter-hb-unpause' needs to explicitly called |
536 | | - (should (oref channel paused)) |
537 | | - (jupyter-hb-unpause channel) |
538 | | - (sleep-for 0.2) |
539 | | - ;; It seems the timers are run after returning from the first `sleep-for' |
540 | | - ;; call. |
541 | | - (sleep-for 0.1) |
542 | | - (should (oref channel paused)) |
543 | | - (should-not (oref channel beating)) |
544 | | - (should died-cb-called) |
545 | | - (should (jupyter-channel-alive-p channel)) |
546 | | - (should-not (jupyter-hb-beating-p channel)))) |
547 | | - |
548 | 494 | ;;; GC |
549 | 495 |
|
550 | 496 | (ert-deftest jupyter-weak-ref () |
|
609 | 555 | (lambda (_) nil))) |
610 | 556 | (should-error (jupyter-locate-python)))) |
611 | 557 |
|
612 | | -(ert-deftest jupyter-kernel-lifetime () |
613 | | - :tags '(kernel) |
614 | | - (let* ((conn-info (jupyter-local-tcp-conn-info)) |
615 | | - (kernel (jupyter-spec-kernel |
616 | | - :spec (jupyter-guess-kernelspec "python") |
617 | | - :session (jupyter-session |
618 | | - :key (plist-get conn-info :key) |
619 | | - :conn-info conn-info)))) |
620 | | - (should-not (jupyter-kernel-alive-p kernel)) |
621 | | - (jupyter-start-kernel kernel) |
622 | | - (should (jupyter-kernel-alive-p kernel)) |
623 | | - (jupyter-kill-kernel kernel) |
624 | | - (should-not (jupyter-kernel-alive-p kernel)) |
625 | | - (setq conn-info (jupyter-local-tcp-conn-info)) |
626 | | - (ert-info ("`jupyter-kernel-manager'") |
627 | | - ;; TODO: Should the manager create a session if one isn't present? |
628 | | - (oset kernel session (jupyter-session |
629 | | - :key (plist-get conn-info :key) |
630 | | - :conn-info conn-info)) |
631 | | - (let* ((manager (jupyter-kernel-process-manager :kernel kernel)) |
632 | | - (control-channel (oref manager control-channel)) |
633 | | - process) |
634 | | - (should-not (jupyter-kernel-alive-p manager)) |
635 | | - (should-not control-channel) |
636 | | - (jupyter-start-kernel manager) |
637 | | - (setq process (oref kernel process)) |
638 | | - (setq control-channel (oref manager control-channel)) |
639 | | - (should (jupyter-zmq-channel-p control-channel)) |
640 | | - (should (jupyter-kernel-alive-p manager)) |
641 | | - (should (jupyter-kernel-alive-p kernel)) |
642 | | - (jupyter-shutdown-kernel manager) |
643 | | - (ert-info ("Kernel shutdown is clean") |
644 | | - (should-not (process-live-p process)) |
645 | | - (should (zerop (process-exit-status process))) |
646 | | - (should-not (jupyter-kernel-alive-p manager)) |
647 | | - (should-not (jupyter-kernel-alive-p kernel))) |
648 | | - (setq control-channel (oref manager control-channel)) |
649 | | - (should-not (jupyter-zmq-channel-p control-channel)))))) |
650 | | - |
651 | 558 | (ert-deftest jupyter-command-kernel () |
652 | 559 | :tags '(kernel) |
653 | 560 | (let ((kernel (jupyter-command-kernel |
|
703 | 610 |
|
704 | 611 | ;;; Client |
705 | 612 |
|
706 | | -;; TODO: Different values of the session argument |
707 | | -;; TODO: Update for new `jupyter-channel-ioloop-comm' |
708 | | -(ert-deftest jupyter-comm-initialize () |
709 | | - :tags '(client init) |
710 | | - (skip-unless nil) |
711 | | - ;; The default comm is a jupyter-channel-ioloop-comm |
712 | | - (let ((conn-info (jupyter-test-conn-info-plist)) |
713 | | - (client (jupyter-kernel-client))) |
714 | | - (oset client kcomm (jupyter-zmq-channel-comm)) |
715 | | - (jupyter-comm-initialize client conn-info) |
716 | | - ;; kcomm by default is a `jupyter-channel-ioloop-comm' |
717 | | - (with-slots (session kcomm) client |
718 | | - (ert-info ("Client session") |
719 | | - (should (string= (jupyter-session-key session) |
720 | | - (plist-get conn-info :key))) |
721 | | - (should (equal (jupyter-session-conn-info session) |
722 | | - conn-info))) |
723 | | - (ert-info ("Heartbeat channel initialized") |
724 | | - (should (eq session (oref (oref kcomm hb) session))) |
725 | | - (should (string= (oref (oref kcomm hb) endpoint) |
726 | | - (format "tcp://127.0.0.1:%d" |
727 | | - (plist-get conn-info :hb_port))))) |
728 | | - (ert-info ("Shell, iopub, stdin initialized") |
729 | | - (cl-loop |
730 | | - for channel in '(:shell :iopub :stdin) |
731 | | - for port_sym = (intern (concat (symbol-name channel) "_port")) |
732 | | - do |
733 | | - (should (plist-member (plist-get channels channel) :alive-p)) |
734 | | - (should (plist-member (plist-get channels channel) :endpoint)) |
735 | | - (should |
736 | | - (string= (plist-get (plist-get channels channel) :endpoint) |
737 | | - (format "tcp://127.0.0.1:%d" |
738 | | - (plist-get conn-info port_sym)))))) |
739 | | - (ert-info ("Initialization stops any running channels") |
740 | | - (should-not (jupyter-channels-running-p client)) |
741 | | - (jupyter-start-channels client) |
742 | | - (should (jupyter-channels-running-p client)) |
743 | | - (jupyter-comm-initialize client conn-info) |
744 | | - (should-not (jupyter-channels-running-p client))) |
745 | | - (ert-info ("Invalid signature scheme") |
746 | | - (plist-put conn-info :signature_scheme "hmac-foo") |
747 | | - (should-error (jupyter-comm-initialize client conn-info)))))) |
748 | | - |
749 | 613 | (ert-deftest jupyter-write-connection-file () |
750 | 614 | :tags '(client) |
751 | 615 | (skip-unless (not (memq system-type '(ms-dos windows-nt cygwin)))) |
|
771 | 635 | (delete-file file))) |
772 | 636 | (should-not (memq fun kill-emacs-hook)))) |
773 | 637 |
|
774 | | -(ert-deftest jupyter-client-channels () |
775 | | - :tags '(client channels) |
776 | | - (ert-info ("Starting/stopping channels") |
777 | | - (let ((conn-info (jupyter-test-conn-info-plist)) |
778 | | - (client (jupyter-kernel-client))) |
779 | | - (oset client kcomm (jupyter-zmq-channel-comm)) |
780 | | - (jupyter-comm-initialize client conn-info) |
781 | | - (cl-loop |
782 | | - for channel in '(:hb :shell :iopub :stdin) |
783 | | - for alive-p = (jupyter-channel-alive-p client channel) |
784 | | - do (should-not alive-p)) |
785 | | - (jupyter-start-channels client) |
786 | | - (cl-loop |
787 | | - for channel in '(:hb :shell :iopub :stdin) |
788 | | - for alive-p = (jupyter-channel-alive-p client channel) |
789 | | - do (should alive-p)) |
790 | | - (jupyter-stop-channels client) |
791 | | - (cl-loop |
792 | | - for channel in '(:hb :shell :iopub :stdin) |
793 | | - for alive-p = (jupyter-channel-alive-p client channel) |
794 | | - do (should-not alive-p))))) |
795 | | - |
796 | 638 | (ert-deftest jupyter-inhibited-handlers () |
797 | 639 | :tags '(client handlers) |
798 | 640 | (jupyter-test-with-python-client client |
|
908 | 750 | (should (memq r1 mapped)) |
909 | 751 | (should-not (memq r2 mapped))))) |
910 | 752 |
|
911 | | -;;; IOloop |
912 | | - |
913 | | -(ert-deftest jupyter-ioloop-lifetime () |
914 | | - :tags '(ioloop) |
915 | | - (let ((ioloop (jupyter-ioloop)) |
916 | | - (jupyter-default-timeout 2)) |
917 | | - (should-not (process-live-p (oref ioloop process))) |
918 | | - (jupyter-ioloop-start ioloop :tag1) |
919 | | - (should (equal (jupyter-ioloop-last-event ioloop) '(start))) |
920 | | - (with-slots (process) ioloop |
921 | | - (should (process-live-p process)) |
922 | | - (jupyter-ioloop-stop ioloop) |
923 | | - (should (equal (jupyter-ioloop-last-event ioloop) '(quit))) |
924 | | - (sleep-for 0.1) |
925 | | - (should-not (process-live-p process))))) |
926 | | - |
927 | | -(defvar jupyter-ioloop-test-handler-called nil |
928 | | - "Flag variable used for testing the `juyter-ioloop'.") |
929 | | - |
930 | | -(cl-defmethod jupyter-ioloop-handler ((_ioloop jupyter-ioloop) |
931 | | - (_tag (eql :test)) |
932 | | - (event (head test))) |
933 | | - (should (equal (cadr event) "message")) |
934 | | - (setq jupyter-ioloop-test-handler-called t)) |
935 | | - |
936 | | -(ert-deftest jupyter-ioloop-wait-until () |
937 | | - :tags '(ioloop) |
938 | | - (let ((ioloop (jupyter-ioloop))) |
939 | | - (should-not (jupyter-ioloop-last-event ioloop)) |
940 | | - (jupyter-ioloop-start ioloop :test) |
941 | | - (should (equal (jupyter-ioloop-last-event ioloop) '(start))) |
942 | | - (jupyter-ioloop-stop ioloop))) |
943 | | - |
944 | | -(ert-deftest jupyter-ioloop-callbacks () |
945 | | - :tags '(ioloop) |
946 | | - (ert-info ("Callback added before starting the ioloop") |
947 | | - (let ((ioloop (jupyter-ioloop))) |
948 | | - (setq jupyter-ioloop-test-handler-called nil) |
949 | | - (jupyter-ioloop-add-callback ioloop |
950 | | - `(lambda () (zmq-prin1 (list 'test "message")))) |
951 | | - (jupyter-ioloop-start ioloop :test) |
952 | | - (jupyter-ioloop-stop ioloop) |
953 | | - (should jupyter-ioloop-test-handler-called))) |
954 | | - (ert-info ("Callback added after starting the ioloop") |
955 | | - (let ((ioloop (jupyter-ioloop))) |
956 | | - (setq jupyter-ioloop-test-handler-called nil) |
957 | | - (jupyter-ioloop-start ioloop :test) |
958 | | - (should (process-live-p (oref ioloop process))) |
959 | | - (jupyter-ioloop-add-callback ioloop |
960 | | - `(lambda () (zmq-prin1 (list 'test "message")))) |
961 | | - (jupyter-ioloop-wait-until ioloop 'test #'identity) |
962 | | - (jupyter-ioloop-stop ioloop) |
963 | | - (should jupyter-ioloop-test-handler-called)))) |
964 | | - |
965 | | -(ert-deftest jupyter-ioloop-setup () |
966 | | - :tags '(ioloop) |
967 | | - (let ((ioloop (jupyter-ioloop))) |
968 | | - (setq jupyter-ioloop-test-handler-called nil) |
969 | | - (jupyter-ioloop-add-setup ioloop |
970 | | - (zmq-prin1 (list 'test "message"))) |
971 | | - (jupyter-ioloop-start ioloop :test) |
972 | | - (jupyter-ioloop-stop ioloop) |
973 | | - (should jupyter-ioloop-test-handler-called))) |
974 | | - |
975 | | -(ert-deftest jupyter-ioloop-teardown () |
976 | | - :tags '(ioloop) |
977 | | - (let ((ioloop (jupyter-ioloop))) |
978 | | - (setq jupyter-ioloop-test-handler-called nil) |
979 | | - (jupyter-ioloop-add-teardown ioloop |
980 | | - (zmq-prin1 (list 'test "message"))) |
981 | | - (jupyter-ioloop-start ioloop :test) |
982 | | - (jupyter-ioloop-stop ioloop) |
983 | | - (should jupyter-ioloop-test-handler-called))) |
984 | | - |
985 | | -(ert-deftest jupyter-ioloop-add-event () |
986 | | - :tags '(ioloop) |
987 | | - (let ((ioloop (jupyter-ioloop))) |
988 | | - (setq jupyter-ioloop-test-handler-called nil) |
989 | | - (jupyter-ioloop-add-event ioloop test (data) |
990 | | - "Echo DATA back to the parent process." |
991 | | - (list 'test data)) |
992 | | - (jupyter-ioloop-start ioloop :test) |
993 | | - (jupyter-send ioloop 'test "message") |
994 | | - (jupyter-ioloop-stop ioloop) |
995 | | - (should jupyter-ioloop-test-handler-called))) |
996 | | - |
997 | | -(ert-deftest jupyter-channel-ioloop-send-event () |
998 | | - :tags '(ioloop) |
999 | | - (jupyter-test-channel-ioloop |
1000 | | - (ioloop (jupyter-zmq-channel-ioloop)) |
1001 | | - (cl-letf (((symbol-function #'jupyter-send) |
1002 | | - (lambda (_channel _msg-type _msg msg-id) msg-id))) |
1003 | | - (setq jupyter-channel-ioloop-session (jupyter-session :key "foo")) |
1004 | | - (push (jupyter-zmq-channel :type :shell) jupyter-channel-ioloop-channels) |
1005 | | - (let* ((msg-id (jupyter-new-uuid)) |
1006 | | - (event `(list 'send :shell :execute-request '(msg) ,msg-id))) |
1007 | | - (jupyter-test-ioloop-eval-event ioloop event) |
1008 | | - (ert-info ("Return value to parent process") |
1009 | | - (let ((result (read (buffer-string)))) |
1010 | | - (should (equal result `(sent :shell ,msg-id))))))))) |
1011 | | - |
1012 | | -(ert-deftest jupyter-channel-ioloop-start-channel-event () |
1013 | | - :tags '(ioloop) |
1014 | | - (jupyter-test-channel-ioloop |
1015 | | - (ioloop (jupyter-zmq-channel-ioloop)) |
1016 | | - (setq jupyter-channel-ioloop-session (jupyter-session :key "foo")) |
1017 | | - (let ((channel-endpoint "tcp://127.0.0.1:5555")) |
1018 | | - (ert-info ("start-channel event creates channel") |
1019 | | - (should (null jupyter-channel-ioloop-channels)) |
1020 | | - (let ((event `(list 'start-channel :shell ,channel-endpoint))) |
1021 | | - (jupyter-test-ioloop-eval-event ioloop event)) |
1022 | | - (should-not (null jupyter-channel-ioloop-channels)) |
1023 | | - (let ((channel (object-assoc :shell :type jupyter-channel-ioloop-channels))) |
1024 | | - (should (jupyter-zmq-channel-p channel)))) |
1025 | | - (let ((channel (object-assoc :shell :type jupyter-channel-ioloop-channels))) |
1026 | | - (with-slots (type socket endpoint) channel |
1027 | | - (ert-info ("Verify the requested channel was started") |
1028 | | - (should (eq type :shell)) |
1029 | | - (should (zmq-socket-p socket)) |
1030 | | - (should (equal endpoint channel-endpoint)) |
1031 | | - (should (equal (zmq-socket-get socket zmq-LAST-ENDPOINT) channel-endpoint)) |
1032 | | - (ert-info ("Identity of socket matches session") |
1033 | | - (should (equal (zmq-socket-get socket zmq-IDENTITY) |
1034 | | - (jupyter-session-id jupyter-channel-ioloop-session))))) |
1035 | | - (ert-info ("Ensure the channel was added to the poller") |
1036 | | - ;; FIXME: Does it make sense to have this side effect as part of starting |
1037 | | - ;; a channel? It makes it so that we don't reference any `zmq' functions |
1038 | | - ;; in `jupyter-channel-ioloop'. |
1039 | | - (should-error |
1040 | | - (zmq-poller-add jupyter-ioloop-poller socket (list zmq-POLLIN)) |
1041 | | - :type 'zmq-EINVAL))) |
1042 | | - (ert-info ("Return value to parent process") |
1043 | | - (let ((result (read (buffer-string)))) |
1044 | | - (should (equal result `(start-channel :shell))))))))) |
1045 | | - |
1046 | | -(ert-deftest jupyter-channel-ioloop-stop-channel-event () |
1047 | | - :tags '(ioloop) |
1048 | | - (jupyter-test-channel-ioloop |
1049 | | - (ioloop (jupyter-zmq-channel-ioloop)) |
1050 | | - (setq jupyter-channel-ioloop-session (jupyter-session :key "foo")) |
1051 | | - (let ((event `(list 'start-channel :shell "tcp://127.0.0.1:5556"))) |
1052 | | - (jupyter-test-ioloop-eval-event ioloop event) |
1053 | | - (erase-buffer)) |
1054 | | - (let* ((channel (object-assoc :shell :type jupyter-channel-ioloop-channels)) |
1055 | | - (socket (oref channel socket))) |
1056 | | - (ert-info ("Verify the requested channel stops") |
1057 | | - (should (jupyter-channel-alive-p channel)) |
1058 | | - (should (progn (zmq-poller-modify |
1059 | | - jupyter-ioloop-poller |
1060 | | - (oref channel socket) (list zmq-POLLIN zmq-POLLOUT)) |
1061 | | - t)) |
1062 | | - (jupyter-test-ioloop-eval-event ioloop `(list 'stop-channel :shell)) |
1063 | | - (should-not (jupyter-channel-alive-p channel))) |
1064 | | - (ert-info ("Ensure the channel was removed from the poller") |
1065 | | - (should-error |
1066 | | - (zmq-poller-modify jupyter-ioloop-poller socket (list zmq-POLLIN)) |
1067 | | - :type 'zmq-EINVAL)) |
1068 | | - (ert-info ("Return value to parent process") |
1069 | | - (let ((result (read (buffer-string)))) |
1070 | | - (should (equal result `(stop-channel :shell)))))))) |
1071 | | - |
1072 | | -(ert-deftest jupyter-zmq-channel-ioloop-send-fast () |
1073 | | - :tags '(ioloop queue) |
1074 | | - ;; :expected-result :failed |
1075 | | - (jupyter-test-with-python-client client |
1076 | | - (let ((jupyter-current-client client)) |
1077 | | - (jupyter-send-execute-request client :code "1 + 1") |
1078 | | - (jupyter-send-execute-request client :code "1 + 1") |
1079 | | - (jupyter-send-execute-request client :code "1 + 1") |
1080 | | - (let ((req (jupyter-send-execute-request client :code "1 + 1"))) |
1081 | | - (should |
1082 | | - (equal |
1083 | | - (jupyter-message-data |
1084 | | - (jupyter-wait-until-received :execute-result req jupyter-long-timeout) |
1085 | | - :text/plain) |
1086 | | - "2")))))) |
1087 | | - |
1088 | 753 | ;;; Completion |
1089 | 754 |
|
1090 | 755 | (ert-deftest jupyter-completion-number-p () |
|
0 commit comments