1import os
2import threading
3
4import pytest
5
6from ddtrace.internal import periodic
7from ddtrace.internal import service
8
9
10if os.getenv("DD_PROFILE_TEST_GEVENT", False):
11    import gevent
12
13    class Event(object):
14        """
15        We can't use gevent Events here[0], nor can we use native threading
16        events (because gevent is not multi-threaded).
17
18        So for gevent, since it's not multi-threaded and will not run greenlets
19        in parallel (for our usage here, anyway) we can write a dummy Event
20        class which just does a simple busy wait on a shared variable.
21
22        [0] https://github.com/gevent/gevent/issues/891
23        """
24
25        state = False
26
27        def wait(self):
28            while not self.state:
29                gevent.sleep(0.001)
30
31        def set(self):
32            self.state = True
33
34
35else:
36    Event = threading.Event
37
38
39def test_periodic():
40    x = {"OK": False}
41
42    thread_started = Event()
43    thread_continue = Event()
44
45    def _run_periodic():
46        thread_started.set()
47        x["OK"] = True
48        thread_continue.wait()
49
50    def _on_shutdown():
51        x["DOWN"] = True
52
53    t = periodic.PeriodicRealThreadClass()(0.001, _run_periodic, on_shutdown=_on_shutdown)
54    t.start()
55    thread_started.wait()
56    thread_continue.set()
57    assert t.is_alive()
58    t.stop()
59    t.join()
60    assert not t.is_alive()
61    assert x["OK"]
62    assert x["DOWN"]
63    if hasattr(threading, "get_native_id"):
64        assert t.native_id is not None
65
66
67def test_periodic_double_start():
68    def _run_periodic():
69        pass
70
71    t = periodic.PeriodicRealThreadClass()(0.1, _run_periodic)
72    t.start()
73    with pytest.raises(RuntimeError):
74        t.start()
75
76
77def test_periodic_error():
78    x = {"OK": False}
79
80    thread_started = Event()
81    thread_continue = Event()
82
83    def _run_periodic():
84        thread_started.set()
85        thread_continue.wait()
86        raise ValueError
87
88    def _on_shutdown():
89        x["DOWN"] = True
90
91    t = periodic.PeriodicRealThreadClass()(0.001, _run_periodic, on_shutdown=_on_shutdown)
92    t.start()
93    thread_started.wait()
94    thread_continue.set()
95    t.stop()
96    t.join()
97    assert "DOWN" not in x
98
99
100def test_gevent_class():
101    if os.getenv("DD_PROFILE_TEST_GEVENT", False):
102        assert isinstance(periodic.PeriodicRealThreadClass()(1, sum), periodic._GeventPeriodicThread)
103    else:
104        assert isinstance(periodic.PeriodicRealThreadClass()(1, sum), periodic.PeriodicThread)
105
106
107def test_periodic_service_start_stop():
108    t = periodic.PeriodicService(1)
109    t.start()
110    with pytest.raises(service.ServiceStatusError):
111        t.start()
112    t.stop()
113    t.join()
114    with pytest.raises(service.ServiceStatusError):
115        t.stop()
116    with pytest.raises(service.ServiceStatusError):
117        t.stop()
118    t.join()
119    t.join()
120
121
122def test_periodic_join_stop_no_start():
123    t = periodic.PeriodicService(1)
124    t.join()
125    with pytest.raises(service.ServiceStatusError):
126        t.stop()
127    t.join()
128    t = periodic.PeriodicService(1)
129    with pytest.raises(service.ServiceStatusError):
130        t.stop()
131    t.join()
132    with pytest.raises(service.ServiceStatusError):
133        t.stop()
134
135
136def test_is_alive_before_start():
137    def x():
138        pass
139
140    t = periodic.PeriodicRealThreadClass()(1, x)
141    assert not t.is_alive()
142