1717from contextlib import redirect_stdout
1818from unittest import mock
1919
20+ import pyarrow as pa
21+ import pyspark .sql .connect .proto as pb2
2022from google .cloud .managed_spark_connect import ManagedSparkSession
2123from google .cloud .managed_spark_magics import ManagedSparkMagics
2224from IPython .core .interactiveshell import InteractiveShell
@@ -31,6 +33,14 @@ def setUp(self):
3133 self .shell .config = Config ()
3234 self .magics = ManagedSparkMagics (shell = self .shell )
3335
36+ def _create_mock_arrow_binary (self , lines : list [str ]) -> bytes :
37+ schema = pa .schema ([pa .field ("output" , pa .string ())])
38+ table = pa .Table .from_arrays ([lines ], schema = schema )
39+ sink = pa .BufferOutputStream ()
40+ with pa .ipc .RecordBatchStreamWriter (sink , table .schema ) as writer :
41+ writer .write_table (table )
42+ return sink .getvalue ()
43+
3444 def test_dpip_with_flags (self ):
3545 with self .assertRaisesRegex (
3646 RuntimeError , "Error: Flags are not currently supported."
@@ -74,29 +84,89 @@ def test_dpip_no_packages_specified(self):
7484
7585 def test_dpip_install_packages_success (self ):
7686 mock_session = mock .Mock (spec = ManagedSparkSession )
87+ mock_session .client = mock .Mock ()
88+
89+ # Create a mock for the properties object
90+ properties = mock .Mock ()
91+
92+ # Create a pyarrow table and serialize it
93+ binary_data = self ._create_mock_arrow_binary (
94+ ["Collecting pandas" , "Successfully installed pandas" ]
95+ )
96+
97+ # Set up the mock response structure
98+ properties .sql_command_result .local_relation .data = binary_data
99+ mock_session .client .execute_command .return_value = (
100+ None ,
101+ {"sql_command_result" : properties .sql_command_result },
102+ None ,
103+ )
104+
77105 self .shell .user_ns ["spark" ] = mock_session
78106
79107 f = io .StringIO ()
80108 with redirect_stdout (f ):
81109 self .magics .dpip ("install pandas numpy" )
82110
83- mock_session .addArtifacts .assert_called_once_with (
84- "pandas" , "numpy" , pypi = True
111+ # Check that execute_command was called
112+ mock_session .client .execute_command .assert_called_once ()
113+ call_args = mock_session .client .execute_command .call_args [0 ][0 ]
114+ self .assertIsInstance (call_args , pb2 .Command )
115+ self .assertEqual (
116+ call_args .execute_external_command .command ,
117+ ManagedSparkMagics .PIP_INSTALL_COMMAND ,
118+ )
119+ self .assertEqual (
120+ call_args .execute_external_command .options ["0" ], "pandas"
121+ )
122+ self .assertEqual (
123+ call_args .execute_external_command .options ["1" ], "numpy"
85124 )
86- self .assertEqual (mock_session .addArtifacts .call_count , 1 )
87- self .assertIn ("Finished installing packages." , f .getvalue ())
88125
89- def test_dpip_add_artifacts_fails (self ):
126+ output = f .getvalue ()
127+ self .assertIn ("Installing packages: ['pandas', 'numpy']" , output )
128+ self .assertIn ("Collecting pandas" , output )
129+ self .assertIn ("Successfully installed pandas" , output )
130+ self .assertIn ("Finished installing packages." , output )
131+
132+ def test_dpip_install_failure (self ):
90133 mock_session = mock .Mock (spec = ManagedSparkSession )
91- mock_session .addArtifacts .side_effect = Exception ("Failed" )
134+ mock_session .client = mock .Mock ()
135+
136+ # Create a mock for the properties object with failure message
137+ properties = mock .Mock ()
138+ binary_data = self ._create_mock_arrow_binary (
139+ [
140+ ManagedSparkMagics .PIP_INSTALL_FAILURE_MSG ,
141+ "ERROR: some pip error" ,
142+ ]
143+ )
144+
145+ properties .sql_command_result .local_relation .data = binary_data
146+ mock_session .client .execute_command .return_value = (
147+ None ,
148+ {"sql_command_result" : properties .sql_command_result },
149+ None ,
150+ )
151+
92152 self .shell .user_ns ["spark" ] = mock_session
93153
94154 with self .assertRaisesRegex (
95- RuntimeError , "Failed to install packages: Failed "
155+ RuntimeError , "Failed to install packages: Pip install failed "
96156 ):
97- self .magics .dpip ("install pandas" )
157+ self .magics .dpip ("install non-existent-package" )
158+
159+ def test_dpip_unexpected_response (self ):
160+ mock_session = mock .Mock (spec = ManagedSparkSession )
161+ mock_session .client = mock .Mock ()
162+ # Return response without 'sql_command_result'
163+ mock_session .client .execute_command .return_value = (None , {}, None )
164+ self .shell .user_ns ["spark" ] = mock_session
98165
99- mock_session .addArtifacts .assert_called_once_with ("pandas" , pypi = True )
166+ with self .assertRaisesRegex (
167+ RuntimeError , "Unexpected response structure: missing binary data"
168+ ):
169+ self .magics .dpip ("install pandas" )
100170
101171
102172if __name__ == "__main__" :
0 commit comments