|
18 | 18 |
|
19 | 19 | import argparse |
20 | 20 | import logging |
| 21 | +import os.path |
21 | 22 | import shlex |
22 | 23 | import typing |
23 | 24 | import unittest |
| 25 | +import zipfile |
24 | 26 | from os import linesep |
25 | | -from os import path |
26 | 27 | from os.path import exists |
27 | 28 | from shutil import rmtree |
28 | 29 | from tempfile import mkdtemp |
| 30 | +from unittest import mock |
29 | 31 |
|
30 | 32 | import pytest |
| 33 | +from parameterized import parameterized |
31 | 34 |
|
32 | 35 | import apache_beam as beam |
33 | 36 | from apache_beam.options.pipeline_options import DebugOptions |
34 | 37 | from apache_beam.options.pipeline_options import PortableOptions |
35 | 38 | from apache_beam.runners.portability import portable_runner_test |
| 39 | +from apache_beam.runners.portability import prism_runner |
36 | 40 | from apache_beam.testing.util import assert_that |
37 | 41 | from apache_beam.testing.util import equal_to |
38 | 42 |
|
@@ -119,10 +123,10 @@ def _create_conf_dir(cls): |
119 | 123 | cls.conf_dir = mkdtemp(prefix='prismtest-conf') |
120 | 124 |
|
121 | 125 | # path for a FileReporter to write metrics to |
122 | | - cls.test_metrics_path = path.join(cls.conf_dir, 'test-metrics.txt') |
| 126 | + cls.test_metrics_path = os.path.join(cls.conf_dir, 'test-metrics.txt') |
123 | 127 |
|
124 | 128 | # path to write Prism configuration to |
125 | | - conf_path = path.join(cls.conf_dir, 'prism-conf.yaml') |
| 129 | + conf_path = os.path.join(cls.conf_dir, 'prism-conf.yaml') |
126 | 130 | file_reporter = 'org.apache.beam.runners.prism.metrics.FileReporter' |
127 | 131 | with open(conf_path, 'w') as f: |
128 | 132 | f.write( |
@@ -220,8 +224,162 @@ def test_custom_window_type(self): |
220 | 224 | def test_metrics(self): |
221 | 225 | super().test_metrics(check_bounded_trie=False) |
222 | 226 |
|
| 227 | + # Inherits all other tests. |
| 228 | + |
| 229 | + |
| 230 | +class PrismJobServerTest(unittest.TestCase): |
| 231 | + def setUp(self) -> None: |
| 232 | + self.local_dir = mkdtemp() |
| 233 | + self.cache_dir = os.path.join(self.local_dir, "cache") |
| 234 | + os.mkdir(self.cache_dir) |
| 235 | + |
| 236 | + self.job_server = prism_runner.PrismJobServer(options=PortableOptions()) |
| 237 | + self.local_bin_path = os.path.join(self.local_dir, "my_prism_bin") |
| 238 | + self.local_zip_path = os.path.join(self.local_dir, "my_prism_bin.zip") |
| 239 | + self.cache_bin_path = os.path.join(self.cache_dir, "my_prism_bin") |
| 240 | + self.cache_zip_path = os.path.join(self.cache_dir, "my_prism_bin.zip") |
| 241 | + self.remote_zip_path = "https://github.com/apache/beam/releases/download/fake_ver/my_prism_bin.zip" # pylint: disable=line-too-long |
| 242 | + |
| 243 | + def tearDown(self) -> None: |
| 244 | + rmtree(self.local_dir) |
| 245 | + pass |
| 246 | + |
| 247 | + def _make_local_bin(self): |
| 248 | + with open(self.local_bin_path, 'wb'): |
| 249 | + pass |
| 250 | + |
| 251 | + def _make_local_zip(self): |
| 252 | + with zipfile.ZipFile(self.local_zip_path, 'w', zipfile.ZIP_DEFLATED): |
| 253 | + pass |
| 254 | + |
| 255 | + def _make_cache_bin(self): |
| 256 | + with open(self.cache_bin_path, 'wb'): |
| 257 | + pass |
| 258 | + |
| 259 | + def _make_cache_zip(self): |
| 260 | + with zipfile.ZipFile(self.cache_zip_path, 'w', zipfile.ZIP_DEFLATED): |
| 261 | + pass |
| 262 | + |
| 263 | + def _extract_side_effect(self, a, path=None): |
| 264 | + if path is None: |
| 265 | + return a |
| 266 | + |
| 267 | + if path.startswith(self.cache_dir): |
| 268 | + self._make_cache_bin() |
| 269 | + else: |
| 270 | + self._make_local_bin() |
| 271 | + |
| 272 | + return os.path.join(str(path), a) |
| 273 | + |
| 274 | + @parameterized.expand([[True, True], [True, False], [False, True], |
| 275 | + [False, False]]) |
| 276 | + def test_with_unknown_path(self, custom_bin_cache, ignore_cache): |
| 277 | + self.assertRaises( |
| 278 | + FileNotFoundError, |
| 279 | + lambda: self.job_server.local_bin( |
| 280 | + "/path/unknown", |
| 281 | + bin_cache=self.cache_dir if custom_bin_cache else '', |
| 282 | + ignore_cache=ignore_cache)) |
| 283 | + |
| 284 | + @parameterized.expand([ |
| 285 | + [True, True, True], |
| 286 | + [True, True, False], |
| 287 | + [True, False, True], |
| 288 | + [True, False, False], |
| 289 | + [False, True, True], |
| 290 | + [False, True, False], |
| 291 | + [False, False, True], |
| 292 | + [False, False, False], |
| 293 | + ]) |
| 294 | + def test_with_local_binary_and_zip( |
| 295 | + self, has_cache_bin, has_cache_zip, ignore_cache): |
| 296 | + self._make_local_bin() |
| 297 | + self._make_local_zip() |
| 298 | + if has_cache_bin: |
| 299 | + self._make_cache_bin() |
| 300 | + if has_cache_zip: |
| 301 | + self._make_cache_zip() |
| 302 | + |
| 303 | + with mock.patch('zipfile.is_zipfile') as mock_is_zipfile: |
| 304 | + with mock.patch('zipfile.ZipFile') as mock_zipfile_init: |
| 305 | + mock_zipfile = mock.MagicMock() |
| 306 | + mock_zipfile.extract = mock.Mock(side_effect=self._extract_side_effect) |
| 307 | + mock_zipfile_init.return_value = mock_zipfile |
| 308 | + |
| 309 | + # path is set to local binary |
| 310 | + # always use local binary even if we have a cached copy, no unzipping |
| 311 | + mock_is_zipfile.return_value = False |
| 312 | + self.assertEqual( |
| 313 | + self.job_server.local_bin( |
| 314 | + self.local_bin_path, self.cache_dir, ignore_cache), |
| 315 | + self.local_bin_path) |
| 316 | + |
| 317 | + mock_zipfile_init.assert_not_called() |
| 318 | + |
| 319 | + # path is set to local zip |
| 320 | + # use local zip and unzip only if cache binary not available or |
| 321 | + # ignore_cache is true |
| 322 | + mock_is_zipfile.return_value = True |
| 323 | + self.assertEqual( |
| 324 | + self.job_server.local_bin( |
| 325 | + self.local_zip_path, self.cache_dir, ignore_cache), |
| 326 | + self.cache_bin_path) |
| 327 | + |
| 328 | + if has_cache_bin and not ignore_cache: |
| 329 | + # if cache is enabled and binary is in cache, we wont't unzip |
| 330 | + mock_zipfile_init.assert_not_called() |
| 331 | + else: |
| 332 | + mock_zipfile_init.assert_called_once() |
| 333 | + mock_zipfile_init.reset_mock() |
| 334 | + |
| 335 | + @parameterized.expand([ |
| 336 | + [True, True, True], |
| 337 | + [True, True, False], |
| 338 | + [True, False, True], |
| 339 | + [True, False, False], |
| 340 | + [False, True, True], |
| 341 | + [False, True, False], |
| 342 | + [False, False, True], |
| 343 | + [False, False, False], |
| 344 | + ]) |
| 345 | + def test_with_remote_path(self, has_cache_bin, has_cache_zip, ignore_cache): |
| 346 | + if has_cache_bin: |
| 347 | + self._make_cache_bin() |
| 348 | + if has_cache_zip: |
| 349 | + self._make_cache_zip() |
| 350 | + |
| 351 | + with mock.patch( |
| 352 | + 'apache_beam.runners.portability.prism_runner.urlopen') as mock_urlopen: |
| 353 | + mock_response = mock.MagicMock() |
| 354 | + mock_response.read.return_value = b'' |
| 355 | + mock_urlopen.return_value = mock_response |
| 356 | + with mock.patch('zipfile.is_zipfile') as mock_is_zipfile: |
| 357 | + with mock.patch('zipfile.ZipFile') as mock_zipfile_init: |
| 358 | + mock_zipfile = mock.MagicMock() |
| 359 | + mock_zipfile.extract = mock.Mock( |
| 360 | + side_effect=self._extract_side_effect) |
| 361 | + mock_zipfile_init.return_value = mock_zipfile |
| 362 | + mock_is_zipfile.return_value = True |
| 363 | + self.assertEqual( |
| 364 | + self.job_server.local_bin( |
| 365 | + self.remote_zip_path, |
| 366 | + self.cache_dir, |
| 367 | + ignore_cache=ignore_cache), |
| 368 | + self.cache_bin_path) |
| 369 | + |
| 370 | + if has_cache_zip and not ignore_cache: |
| 371 | + # if cache is enabled and zip is in cache, we wont't download |
| 372 | + mock_urlopen.assert_not_called() |
| 373 | + else: |
| 374 | + mock_urlopen.assert_called_once() |
| 375 | + |
| 376 | + if has_cache_bin and has_cache_zip and not ignore_cache: |
| 377 | + # if cache is enabled and both binary and zip are in cache, we |
| 378 | + # wont't unzip |
| 379 | + mock_zipfile_init.assert_not_called() |
| 380 | + else: |
| 381 | + mock_zipfile_init.assert_called_once() |
223 | 382 |
|
224 | | -# Inherits all other tests. |
225 | 383 |
|
226 | 384 | if __name__ == '__main__': |
227 | 385 | # Run the tests. |
|
0 commit comments