1 # -*- coding: utf-8 -*-
2 # Copyright 2013 Google Inc. All Rights Reserved.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 """Tests for compose command."""
17 from __future__
import absolute_import
19 from gslib
.commands
.compose
import MAX_COMPOSE_ARITY
20 from gslib
.cs_api_map
import ApiSelector
21 import gslib
.tests
.testcase
as testcase
22 from gslib
.tests
.testcase
.integration_testcase
import SkipForS3
23 from gslib
.tests
.util
import ObjectToURI
as suri
26 @SkipForS3('S3 does not support object composition.')
27 class TestCompose(testcase
.GsUtilIntegrationTestCase
):
28 """Integration tests for compose command."""
30 def check_n_ary_compose(self
, num_components
):
31 """Tests composing num_components object."""
32 bucket_uri
= self
.CreateBucket()
34 data_list
= ['data-%d,' % i
for i
in xrange(num_components
)]
35 components
= [self
.CreateObject(bucket_uri
=bucket_uri
, contents
=data
).uri
36 for data
in data_list
]
38 composite
= bucket_uri
.clone_replace_name(self
.MakeTempName('obj'))
40 self
.RunGsUtil(['compose'] + components
+ [composite
.uri
])
41 self
.assertEqual(composite
.get_contents_as_string(), ''.join(data_list
))
43 def test_compose_too_many_fails(self
):
44 components
= ['gs://b/component-obj'] * (MAX_COMPOSE_ARITY
+ 1)
45 stderr
= self
.RunGsUtil(['compose'] + components
+ ['gs://b/composite-obj'],
46 expected_status
=1, return_stderr
=True)
47 self
.assertIn('command accepts at most', stderr
)
49 def test_compose_too_few_fails(self
):
50 stderr
= self
.RunGsUtil(
51 ['compose', 'gs://b/component-obj', 'gs://b/composite-obj'],
52 expected_status
=1, return_stderr
=True)
54 'CommandException: "compose" requires at least 2 component objects.\n',
57 def test_compose_between_buckets_fails(self
):
58 target
= 'gs://b/composite-obj'
59 offending_obj
= 'gs://alt-b/obj2'
60 components
= ['gs://b/obj1', offending_obj
]
61 stderr
= self
.RunGsUtil(['compose'] + components
+ [target
],
62 expected_status
=1, return_stderr
=True)
64 'CommandException: GCS does '
65 'not support inter-bucket composing.\n')
66 self
.assertIn(expected_msg
, stderr
)
68 def test_versioned_target_disallowed(self
):
69 stderr
= self
.RunGsUtil(
70 ['compose', 'gs://b/o1', 'gs://b/o2', 'gs://b/o3#1234'],
71 expected_status
=1, return_stderr
=True)
72 expected_msg
= ('CommandException: A version-specific URL (%s) '
73 'cannot be the destination for gsutil compose - abort.'
75 self
.assertIn(expected_msg
, stderr
)
77 def test_simple_compose(self
):
78 self
.check_n_ary_compose(2)
80 def test_maximal_compose(self
):
81 self
.check_n_ary_compose(MAX_COMPOSE_ARITY
)
83 def test_compose_with_wildcard(self
):
84 """Tests composing objects with a wildcarded URI."""
85 bucket_uri
= self
.CreateBucket()
87 component1
= self
.CreateObject(
88 bucket_uri
=bucket_uri
, contents
='hello ', object_name
='component1')
89 component2
= self
.CreateObject(
90 bucket_uri
=bucket_uri
, contents
='world!', object_name
='component2')
92 composite
= bucket_uri
.clone_replace_name(self
.MakeTempName('obj'))
94 self
.RunGsUtil(['compose', component1
.uri
, component2
.uri
, composite
.uri
])
95 self
.assertEqual(composite
.get_contents_as_string(), 'hello world!')
97 def test_compose_with_precondition(self
):
98 """Tests composing objects with a destination precondition."""
99 # Tests that cp -v option handles the if-generation-match header correctly.
100 bucket_uri
= self
.CreateVersionedBucket()
101 k1_uri
= self
.CreateObject(bucket_uri
=bucket_uri
, contents
='data1')
102 k2_uri
= self
.CreateObject(bucket_uri
=bucket_uri
, contents
='data2')
103 g1
= k1_uri
.generation
105 gen_match_header
= 'x-goog-if-generation-match:%s' % g1
106 # Append object 1 and 2
107 self
.RunGsUtil(['-h', gen_match_header
, 'compose', suri(k1_uri
),
108 suri(k2_uri
), suri(k1_uri
)])
110 # Second compose should fail the precondition.
111 stderr
= self
.RunGsUtil(['-h', gen_match_header
, 'compose', suri(k1_uri
),
112 suri(k2_uri
), suri(k1_uri
)],
113 return_stderr
=True, expected_status
=1)
115 self
.assertIn('PreconditionException', stderr
)
117 def test_compose_missing_second_source_object(self
):
118 bucket_uri
= self
.CreateBucket()
119 object_uri
= self
.CreateObject(bucket_uri
=bucket_uri
, contents
='foo')
121 # Compose with missing source object
122 stderr
= self
.RunGsUtil(['compose', suri(object_uri
),
123 suri(bucket_uri
, 'nonexistent-obj'),
124 suri(bucket_uri
, 'valid-destination')],
125 expected_status
=1, return_stderr
=True)
126 self
.assertIn('NotFoundException', stderr
)
127 if self
.test_api
== ApiSelector
.JSON
:
128 self
.assertIn('One of the source objects does not exist', stderr
)
131 class TestCompatibleCompose(testcase
.GsUtilIntegrationTestCase
):
133 def test_compose_non_gcs_target(self
):
134 stderr
= self
.RunGsUtil(['compose', 'gs://b/o1', 'gs://b/o2', 's3://b/o3'],
135 expected_status
=1, return_stderr
=True)
136 expected_msg
= ('CommandException: "compose" called on URL with '
137 'unsupported provider (%s).\n' % 's3://b/o3')
138 self
.assertIn(expected_msg
, stderr
)
140 def test_compose_non_gcs_component(self
):
141 stderr
= self
.RunGsUtil(['compose', 'gs://b/o1', 's3://b/o2', 'gs://b/o3'],
142 expected_status
=1, return_stderr
=True)
143 expected_msg
= ('CommandException: "compose" called on URL with '
144 'unsupported provider (%s).\n' % 's3://b/o2')
145 self
.assertIn(expected_msg
, stderr
)