-
Notifications
You must be signed in to change notification settings - Fork 144
Expand file tree
/
Copy pathtest_telemetry.py
More file actions
239 lines (212 loc) · 7.64 KB
/
test_telemetry.py
File metadata and controls
239 lines (212 loc) · 7.64 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
#!/usr/bin/env python3
#
# Copyright (c) 2012-2024 Snowflake Computing Inc. All rights reserved.
#
import os
import tempfile
from typing import Callable, Optional
from unittest.mock import ANY, MagicMock, patch
import pytest
from pandas.errors import IndexingError, SpecificationError
from ci.check_standalone_function_snowpark_pandas_telemetry_decorator import (
DecoratorError,
check_standalone_function_snowpark_pandas_telemetry_decorator,
)
from snowflake.connector.errors import DatabaseError
from snowflake.snowpark.exceptions import (
SnowparkDataframeException,
SnowparkSessionException,
)
from snowflake.snowpark.modin.plugin._internal.telemetry import (
error_to_telemetry_type,
snowpark_pandas_telemetry_method_decorator,
)
@patch(
"snowflake.snowpark.modin.plugin._internal.telemetry._send_snowpark_pandas_telemetry_helper"
)
def snowpark_pandas_error_test_helper(
send_telemetry_helper_mock,
func: Callable,
error: Exception,
telemetry_type: str,
loc_pref: Optional[str] = "SnowflakeQueryCompiler",
mock_arg: Optional[MagicMock] = None,
error_msg: Optional[str] = None,
):
decorated_func = MagicMock(side_effect=error)
decorated_func.__qualname__ = "magic_mock"
with pytest.raises(type(error)):
wrap_func = func(decorated_func)
wrap_func(mock_arg)
send_telemetry_helper_mock.assert_called_with(
session=ANY,
func_name=f"{loc_pref}.magic_mock",
api_calls=[
{
"name": f"{loc_pref}.magic_mock",
}
],
query_history=ANY,
telemetry_type=telemetry_type,
error_msg=error_msg,
method_call_count=ANY,
)
@patch(
"snowflake.snowpark.modin.plugin._internal.telemetry._send_snowpark_pandas_telemetry_helper"
)
@patch("snowflake.snowpark.session._get_active_session")
def test_snowpark_pandas_telemetry_method_decorator(
_get_active_session_mock, send_telemetry_mock
):
# SnowparkSessionException: test SnowparkSessionException is suppressed
def raise_session_error():
raise SnowparkSessionException("Mock Session Error")
def raise_real_type_error(_):
raise TypeError("Mock Real Error")
_get_active_session_mock.side_effect = raise_session_error
decorated_func1 = MagicMock()
decorated_func1.__qualname__ = "magic_mock"
decorated_func1.return_value = 10 # eager API is supposed to be sent
mock_arg1 = MagicMock(spec=type) # mock a class instance
mock_arg1.__name__ = "MockClass"
mock_arg1._query_compiler = MagicMock()
mock_arg1._query_compiler.snowpark_pandas_api_calls = []
wrap_func1 = snowpark_pandas_telemetry_method_decorator(decorated_func1)
wrap_func1(mock_arg1)
# Test that the SnowparkSessionException raising _get_active_session is called once.
assert _get_active_session_mock.call_count == 1
send_telemetry_mock.assert_not_called()
assert len(mock_arg1._query_compiler.snowpark_pandas_api_calls) == 0
# Test user errors + SnowparkSessionException
decorated_func1.side_effect = raise_real_type_error
wrap_func2 = snowpark_pandas_telemetry_method_decorator(decorated_func1)
with pytest.raises(TypeError) as exc_info:
wrap_func2(mock_arg1)
exception = exc_info.value
# Test "Mock Session Error" is suppressed from real error msg
assert str(exception) == "Mock Real Error"
assert _get_active_session_mock.call_count == 2
send_telemetry_mock.assert_not_called()
assert len(mock_arg1._query_compiler.snowpark_pandas_api_calls) == 0
# Test user errors
mock_arg2 = MagicMock()
mock_arg2._query_compiler.snowpark_pandas_api_calls = []
mock_arg2.__class__.__name__ = "mock_class"
with pytest.raises(TypeError):
wrap_func2(mock_arg2)
send_telemetry_mock.assert_called_with(
session=ANY,
func_name="mock_class.magic_mock",
api_calls=[
{
"name": "mock_class.magic_mock",
}
],
query_history=ANY,
telemetry_type="snowpark_pandas_type_error",
error_msg=None,
method_call_count=ANY,
)
assert len(mock_arg2._query_compiler.snowpark_pandas_api_calls) == 0
# Test instance method TypeError, IndexError, AttributeError
# from `api_calls = copy.deepcopy(args[0]._query_compiler.snowpark_pandas_api_calls)`
decorated_func1.side_effect = None
wrap_func2()
send_telemetry_mock.assert_called_with(
session=ANY,
func_name="mock_class.magic_mock",
api_calls=[
{
"name": "mock_class.magic_mock",
}
],
query_history=ANY,
telemetry_type="snowpark_pandas_type_error",
error_msg=None,
method_call_count=ANY,
)
@pytest.mark.parametrize(
"error",
[
NotImplementedError("test"),
TypeError("test"),
ValueError("test"),
KeyError("test"),
AttributeError("test"),
ZeroDivisionError("test"),
IndexError("test"),
AssertionError("test"),
IndexingError("test"),
SpecificationError("test"),
DatabaseError("test"),
],
)
def test_snowpark_pandas_telemetry_method_error(error):
mock_arg = MagicMock()
mock_arg._query_compiler.snowpark_pandas_api_calls = []
mock_arg.__class__.__name__ = "mock_class"
snowpark_pandas_error_test_helper(
func=snowpark_pandas_telemetry_method_decorator,
error=error,
telemetry_type=error_to_telemetry_type(error),
loc_pref="mock_class",
mock_arg=mock_arg,
error_msg="test"
if isinstance(error, (AssertionError, NotImplementedError))
else None,
)
@pytest.mark.parametrize(
"error, telemetry_type",
[
(NotImplementedError, "snowpark_pandas_not_implemented_error"),
(AssertionError, "snowpark_pandas_assertion_error"),
(TypeError, "snowpark_pandas_type_error"),
(SpecificationError, "snowpark_pandas_specification_error"),
(DatabaseError, "snowpark_pandas_database_error"),
(SnowparkDataframeException, "snowpark_pandas_snowpark_dataframe_exception"),
],
)
def test_error_to_telemetry_type(error, telemetry_type):
assert error_to_telemetry_type(error("error_msg")) == telemetry_type
def test_check_standalone_function_snowpark_pandas_telemetry_decorator():
# Create a temporary file with sample code
code = """
import modin.pandas as pd
from modin.pandas.dataframe import DataFrame
from modin.pandas.series import Series
import test_decorator
def func1() -> DataFrame:
def sub_func() -> DataFrame: #sub function should be excluded
return pd.DataFrame()
return pd.DataFrame()
def func2() -> Series:
return pd.Series()
@test_decorator
def _private_func() -> DataFrame:
return pd.DataFrame()
def func3() -> int:
return 0
@test_decorator
def func4() -> DataFrame:
return pd.DataFrame()
# Test class methods/instance methods will not be decorated
class TestClass:
def test_instance_method(self) -> DataFrame:
return pd.DataFrame()
def test_class_method(cls) -> DataFrame:
return pd.DataFrame()
"""
with tempfile.NamedTemporaryFile(mode="w", delete=False) as tmp_file:
tmp_file.write(code)
tmp_file.flush()
with pytest.raises(DecoratorError) as exc_info:
check_standalone_function_snowpark_pandas_telemetry_decorator(
target_file=tmp_file.name,
telemetry_decorator_name="test_decorator",
)
assert (
str(exc_info.value)
== "functions ['func1', 'func2', 'func3'] should be decorated with test_decorator"
)
# Clean up the temporary file
os.remove(tmp_file.name)