11import signal
2+ import socket
23import subprocess
34import sys
45import textwrap
56import time
67
8+ import grpc
79import pytest
810
11+ from cloudquery .plugin_v3 import plugin_pb2 , plugin_pb2_grpc
12+
913SERVE_PLUGIN = """
1014from cloudquery.sdk import serve
1115from cloudquery.sdk.plugin import Plugin
1216
1317serve.PluginCommand(Plugin("test", "v0.0.1")).run(["serve", "--address", "127.0.0.1:0"])
1418"""
1519
20+ SERVE_PLUGIN_WITH_BLOCKED_INIT = """
21+ import sys, threading
22+ from cloudquery.sdk import serve
23+ from cloudquery.sdk.plugin import Plugin
24+
25+ class BlockedInitPlugin(Plugin):
26+ def init(self, spec, no_connection=False):
27+ threading.Event().wait()
28+
29+ serve.PluginCommand(BlockedInitPlugin("test", "v0.0.1")).run(["serve", "--address", sys.argv[1]])
30+ """
31+
1632SIGTERM_DURING_LOCK_RESTORE = """
1733import os, signal, sys, threading
1834from cloudquery.sdk import serve
@@ -38,11 +54,11 @@ def _acquire_restore(self, x):
3854"""
3955
4056
41- def start_plugin (tmp_path , source ):
57+ def start_plugin (tmp_path , source , * args ):
4258 script = tmp_path / "plugin_main.py"
4359 script .write_text (textwrap .dedent (source ))
4460 return subprocess .Popen (
45- [sys .executable , str (script )],
61+ [sys .executable , str (script ), * args ],
4662 stdout = subprocess .PIPE ,
4763 stderr = subprocess .STDOUT ,
4864 text = True ,
@@ -70,3 +86,47 @@ def test_signal_while_serving_exits_zero(tmp_path, signum):
7086 process .send_signal (signum )
7187 output , _ = process .communicate (timeout = 30 )
7288 assert process .returncode == 0 , output
89+
90+
91+ def free_address ():
92+ with socket .socket () as sock :
93+ sock .bind (("127.0.0.1" , 0 ))
94+ return "127.0.0.1:%d" % sock .getsockname ()[1 ]
95+
96+
97+ def start_plugin_with_blocked_init (tmp_path ):
98+ address = free_address ()
99+ process = start_plugin (tmp_path , SERVE_PLUGIN_WITH_BLOCKED_INIT , address )
100+ wait_until_serving (process )
101+ channel = grpc .insecure_channel (address )
102+ init_call = plugin_pb2_grpc .PluginStub (channel ).Init .future (
103+ plugin_pb2 .Init .Request (spec = b"" )
104+ )
105+ time .sleep (0.5 )
106+ return process , channel , init_call
107+
108+
109+ def test_sigterm_with_blocked_handler_exits_zero_within_bound (tmp_path ):
110+ process , channel , init_call = start_plugin_with_blocked_init (tmp_path )
111+ try :
112+ process .send_signal (signal .SIGTERM )
113+ output , _ = process .communicate (timeout = 20 )
114+ assert process .returncode == 0 , output
115+ finally :
116+ init_call .cancel ()
117+ channel .close ()
118+ process .kill ()
119+
120+
121+ def test_second_signal_with_blocked_handler_exits_zero_immediately (tmp_path ):
122+ process , channel , init_call = start_plugin_with_blocked_init (tmp_path )
123+ try :
124+ process .send_signal (signal .SIGTERM )
125+ time .sleep (0.5 )
126+ process .send_signal (signal .SIGINT )
127+ output , _ = process .communicate (timeout = 3 )
128+ assert process .returncode == 0 , output
129+ finally :
130+ init_call .cancel ()
131+ channel .close ()
132+ process .kill ()
0 commit comments