1# -*- coding: utf-8 -*- 2# Copyright 2017 Google Inc. All Rights Reserved. 3# 4# Permission is hereby granted, free of charge, to any person obtaining a 5# copy of this software and associated documentation files (the 6# "Software"), to deal in the Software without restriction, including 7# without limitation the rights to use, copy, modify, merge, publish, dis- 8# tribute, sublicense, and/or sell copies of the Software, and to permit 9# persons to whom the Software is furnished to do so, subject to the fol- 10# lowing conditions: 11# 12# The above copyright notice and this permission notice shall be included 13# in all copies or substantial portions of the Software. 14# 15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 16# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL- 17# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 18# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 19# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 21# IN THE SOFTWARE. 22"""Tests for media helper functions and classes for GCS JSON API.""" 23 24from __future__ import absolute_import 25from __future__ import print_function 26from __future__ import division 27from __future__ import unicode_literals 28 29from gslib.gcs_json_media import BytesTransferredContainer 30from gslib.gcs_json_media import UploadCallbackConnectionClassFactory 31import gslib.tests.testcase as testcase 32import six 33 34from six import add_move, MovedModule 35add_move(MovedModule('mock', 'mock', 'unittest.mock')) 36from six.moves import mock 37 38# Assigning string representations of the appropriate package, used for 39# '@mock.patch` methods that take a string in the following format: 40# "package.module.ClassName" 41if six.PY2: 42 https_connection = 'httplib.HTTPSConnection' 43else: 44 https_connection = 'http.client.HTTPSConnection' 45 46 47class TestUploadCallbackConnection(testcase.GsUtilUnitTestCase): 48 """Tests for the upload callback connection.""" 49 50 def setUp(self): 51 super(TestUploadCallbackConnection, self).setUp() 52 self.bytes_container = BytesTransferredContainer() 53 self.class_factory = UploadCallbackConnectionClassFactory( 54 self.bytes_container, 55 buffer_size=50, 56 total_size=100, 57 progress_callback='Sample') 58 self.instance = self.class_factory.GetConnectionClass()('host') 59 60 @mock.patch(https_connection) 61 def testHeaderDefaultBehavior(self, mock_conn): 62 """Test the size modifier is correct under expected headers.""" 63 mock_conn.putheader.return_value = None 64 self.instance.putheader('content-encoding', 'gzip') 65 self.instance.putheader('content-length', '10') 66 self.instance.putheader('content-range', 'bytes 0-104/*') 67 # Ensure the modifier is as expected. 68 self.assertAlmostEqual(self.instance.size_modifier, 10.5) 69 70 @mock.patch(https_connection) 71 def testHeaderIgnoreWithoutGzip(self, mock_conn): 72 """Test that the gzip content-encoding is required to modify size.""" 73 mock_conn.putheader.return_value = None 74 self.instance.putheader('content-length', '10') 75 self.instance.putheader('content-range', 'bytes 0-99/*') 76 # Ensure the modifier is unchanged. 77 self.assertAlmostEqual(self.instance.size_modifier, 1.0) 78 79 @mock.patch(https_connection) 80 def testHeaderRangeFormatX_YSlashStar(self, mock_conn): 81 """Test content-range header format X-Y/* """ 82 self.instance.putheader('content-encoding', 'gzip') 83 self.instance.putheader('content-length', '10') 84 self.instance.putheader('content-range', 'bytes 0-99/*') 85 # Ensure the modifier is as expected. 86 self.assertAlmostEqual(self.instance.size_modifier, 10.0) 87 88 @mock.patch(https_connection) 89 def testHeaderRangeFormatStarSlash100(self, mock_conn): 90 """Test content-range header format */100 """ 91 self.instance.putheader('content-encoding', 'gzip') 92 self.instance.putheader('content-length', '10') 93 self.instance.putheader('content-range', 'bytes */100') 94 # Ensure the modifier is as expected. 95 self.assertAlmostEqual(self.instance.size_modifier, 1.0) 96 97 @mock.patch(https_connection) 98 def testHeaderRangeFormat0_99Slash100(self, mock_conn): 99 """Test content-range header format 0-99/100 """ 100 self.instance.putheader('content-encoding', 'gzip') 101 self.instance.putheader('content-length', '10') 102 self.instance.putheader('content-range', 'bytes 0-99/100') 103 # Ensure the modifier is as expected. 104 self.assertAlmostEqual(self.instance.size_modifier, 10.0) 105 106 @mock.patch(https_connection) 107 def testHeaderParseFailure(self, mock_conn): 108 """Test incorrect header values do not raise exceptions.""" 109 mock_conn.putheader.return_value = None 110 self.instance.putheader('content-encoding', 'gzip') 111 self.instance.putheader('content-length', 'bytes 10') 112 self.instance.putheader('content-range', 'not a number') 113 # Ensure the modifier is unchanged. 114 self.assertAlmostEqual(self.instance.size_modifier, 1.0) 115 116 @mock.patch('gslib.progress_callback.ProgressCallbackWithTimeout') 117 @mock.patch('httplib2.HTTPSConnectionWithTimeout') 118 def testSendDefaultBehavior(self, mock_conn, mock_callback): 119 mock_conn.send.return_value = None 120 self.instance.size_modifier = 2 121 self.instance.processed_initial_bytes = True 122 self.instance.callback_processor = mock_callback 123 # Send 10 bytes of data. 124 sample_data = b'0123456789' 125 self.instance.send(sample_data) 126 # Ensure the data is fully sent since the buffer size is 50 bytes. 127 self.assertTrue(mock_conn.send.called) 128 (_, sent_data), _ = mock_conn.send.call_args_list[0] 129 self.assertEqual(sent_data, sample_data) 130 # Ensure the progress callback is correctly scaled. 131 self.assertTrue(mock_callback.Progress.called) 132 [sent_bytes], _ = mock_callback.Progress.call_args_list[0] 133 self.assertEqual(sent_bytes, 20) 134