Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
aws
GitHub Repository: aws/aws-cli
Path: blob/develop/tests/functional/docs/test_help_output.py
1567 views
1
#!/usr/bin/env python
2
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
#
4
# Licensed under the Apache License, Version 2.0 (the "License"). You
5
# may not use this file except in compliance with the License. A copy of
6
# the License is located at
7
#
8
# http://aws.amazon.com/apache2.0/
9
#
10
# or in the "license" file accompanying this file. This file is
11
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
12
# ANY KIND, either express or implied. See the License for the specific
13
# language governing permissions and limitations under the License.
14
"""Test help output for the AWS CLI.
15
16
The purpose of these docs is to test that the generated output looks how
17
we expect.
18
19
It's intended to be as end to end as possible, but instead of looking
20
at the man output, we look one step before at the generated rst output
21
(it's easier to verify).
22
23
"""
24
from awscli.testutils import BaseAWSHelpOutputTest
25
from awscli.testutils import FileCreator
26
from awscli.testutils import mock
27
from awscli.testutils import aws
28
from awscli.compat import StringIO
29
30
from awscli.alias import AliasLoader
31
32
33
class TestHelpOutput(BaseAWSHelpOutputTest):
34
def test_output(self):
35
self.driver.main(['help'])
36
# Check for the reference label.
37
self.assert_contains('.. _cli:aws:')
38
self.assert_contains('***\naws\n***')
39
self.assert_contains(
40
'The AWS Command Line Interface is a unified tool '
41
'to manage your AWS services.')
42
self.assert_contains('Use *aws help topics* to view')
43
# Verify we see the docs for top level params, so pick
44
# a few representative types of params.
45
self.assert_contains('``--endpoint-url``')
46
# Boolean type
47
self.assert_contains('``--no-paginate``')
48
# Arg with choices
49
self.assert_contains('``--color``')
50
self.assert_contains('* on')
51
self.assert_contains('* off')
52
self.assert_contains('* auto')
53
# Then we should see the services.
54
self.assert_contains('* ec2')
55
self.assert_contains('* s3api')
56
self.assert_contains('* sts')
57
# Make sure it its a related item
58
self.assert_contains('========\nSee Also\n========')
59
self.assert_contains('aws help topics')
60
61
def test_service_help_output(self):
62
self.driver.main(['ec2', 'help'])
63
# Check for the reference label.
64
self.assert_contains('.. _cli:aws ec2:')
65
# We should see the section title for the service.
66
self.assert_contains('***\nec2\n***')
67
# With a description header.
68
self.assert_contains('===========\nDescription\n===========')
69
# And we should see the operations listed.
70
self.assert_contains('* monitor-instances')
71
self.assert_contains('* run-instances')
72
self.assert_contains('* describe-instances')
73
74
def test_operation_help_output(self):
75
self.driver.main(['ec2', 'run-instances', 'help'])
76
# Check for the reference label.
77
self.assert_contains('.. _cli:aws ec2 run-instances:')
78
# Should see the title with the operation name
79
self.assert_contains('*************\nrun-instances\n*************')
80
# Should contain part of the help text from the model.
81
self.assert_contains('Launches the specified number of instances')
82
self.assert_contains('``--count`` (string)')
83
84
def test_custom_service_help_output(self):
85
self.driver.main(['s3', 'help'])
86
self.assert_contains('.. _cli:aws s3:')
87
self.assert_contains('high-level S3 commands')
88
self.assert_contains('* cp')
89
90
def test_waiter_does_not_have_global_args(self):
91
self.driver.main(['ec2', 'wait', 'help'])
92
self.assert_not_contains('--debug')
93
self.assert_not_contains('Global Options')
94
95
def test_custom_operation_help_output(self):
96
self.driver.main(['s3', 'ls', 'help'])
97
self.assert_contains('.. _cli:aws s3 ls:')
98
self.assert_contains('List S3 objects')
99
self.assert_contains('--summarize')
100
self.assert_contains('--debug')
101
102
def test_topic_list_help_output(self):
103
self.driver.main(['help', 'topics'])
104
# Should contain the title
105
self.assert_contains(
106
'*******************\nAWS CLI Topic Guide\n*******************'
107
)
108
# Should contain the description
109
self.assert_contains('This is the AWS CLI Topic Guide.')
110
# Should contain the available topics section
111
self.assert_contains('Available Topics')
112
# Assert the general order of topic categories.
113
self.assert_text_order(
114
'-------\nGeneral\n-------',
115
'--\nS3\n--',
116
starting_from='Available Topics'
117
)
118
# Make sure that the topic elements elements show up as well.
119
self.assert_contains(
120
'* return-codes: Describes'
121
)
122
# Make sure the topic elements are underneath the categories as well
123
# and they get added to each category they fall beneath
124
self.assert_text_order(
125
'-------\nGeneral\n-------',
126
'* return-codes: Describes',
127
'--\nS3\n--',
128
starting_from='-------\nGeneral\n-------'
129
)
130
131
def test_topic_help_command(self):
132
self.driver.main(['help', 'return-codes'])
133
self.assert_contains(
134
'********************\nAWS CLI Return Codes\n********************'
135
)
136
self.assert_contains('These are the following return codes')
137
138
def test_arguments_with_example_json_syntax(self):
139
self.driver.main(['ec2', 'run-instances', 'help'])
140
self.assert_contains('``--iam-instance-profile``')
141
self.assert_contains('JSON Syntax')
142
self.assert_contains('"Arn": "string"')
143
self.assert_contains('"Name": "string"')
144
145
def test_arguments_with_example_shorthand_syntax(self):
146
self.driver.main(['ec2', 'run-instances', 'help'])
147
self.assert_contains('``--iam-instance-profile``')
148
self.assert_contains('Shorthand Syntax')
149
self.assert_contains('Arn=string,Name=string')
150
151
def test_required_args_come_before_optional_args(self):
152
self.driver.main(['ec2', 'run-instances', 'help'])
153
# We're asserting that the args in the synopsis section appear
154
# in this order. They don't have to be in this exact order, but
155
# each item in the list has to come before the previous arg.
156
self.assert_text_order(
157
'--image-id <value>',
158
'[--key-name <value>]',
159
'[--security-groups <value>]', starting_from='Synopsis')
160
161
def test_service_operation_order(self):
162
self.driver.main(['ec2', 'help'])
163
self.assert_text_order(
164
'activate-license',
165
'allocate-address',
166
'assign-private-ip-addresses', starting_from='Available Commands')
167
168
def test_top_level_args_order(self):
169
self.driver.main(['help'])
170
self.assert_text_order(
171
'autoscaling\n', 'cloudformation\n', 'elb\n', 'swf\n',
172
starting_from='Available Services')
173
174
def test_examples_in_operation_help(self):
175
self.driver.main(['ec2', 'run-instances', 'help'])
176
self.assert_contains('========\nExamples\n========')
177
178
def test_add_help_for_dryrun(self):
179
self.driver.main(['ec2', 'run-instances', 'help'])
180
self.assert_contains('DryRunOperation')
181
self.assert_contains('UnauthorizedOperation')
182
183
def test_elb_help_output(self):
184
self.driver.main(['elb', 'help'])
185
# We should *not* have any invalid links like
186
# .. _`:
187
self.assert_not_contains('.. _`:')
188
189
def test_shorthand_flattens_list_of_single_member_structures(self):
190
self.driver.main(['elb', 'remove-tags', 'help'])
191
self.assert_contains("--tags Key1 Key2 Key3")
192
193
def test_deprecated_operations_not_documented(self):
194
self.driver.main(['s3api', 'help'])
195
self.assert_not_contains('get-bucket-lifecycle\n')
196
self.assert_not_contains('put-bucket-lifecycle\n')
197
self.assert_not_contains('get-bucket-notification\n')
198
self.assert_not_contains('put-bucket-notification\n')
199
200
201
class TestRemoveDeprecatedCommands(BaseAWSHelpOutputTest):
202
def assert_command_does_not_exist(self, service, command):
203
# Basically try to get the help output for the removed
204
# command verify that we get a SystemExit exception
205
# and that we get something in stderr that says that
206
# we made an invalid choice (because the operation is removed).
207
stderr = StringIO()
208
with mock.patch('sys.stderr', stderr):
209
with self.assertRaises(SystemExit):
210
self.driver.main([service, command, 'help'])
211
# We should see an error message complaining about
212
# an invalid choice because the operation has been removed.
213
self.assertIn('argument operation: Invalid choice', stderr.getvalue())
214
215
def test_ses_deprecated_commands(self):
216
self.driver.main(['ses', 'help'])
217
self.assert_not_contains('list-verified-email-addresses')
218
self.assert_not_contains('delete-verified-email-address')
219
self.assert_not_contains('verify-email-address')
220
221
self.assert_command_does_not_exist(
222
'ses', 'list-verified-email-addresses')
223
self.assert_command_does_not_exist(
224
'ses', 'delete-verified-email-address')
225
self.assert_command_does_not_exist(
226
'ses', 'verify-email-address')
227
228
def test_ec2_import_export(self):
229
self.driver.main(['ec2', 'help'])
230
self.assert_not_contains('import-instance')
231
self.assert_not_contains('import-volume')
232
self.assert_command_does_not_exist(
233
'ec2', 'import-instance')
234
self.assert_command_does_not_exist(
235
'ec2', 'import-volume')
236
237
def test_boolean_param_documented(self):
238
self.driver.main(['autoscaling',
239
'terminate-instance-in-auto-scaling-group', 'help'])
240
self.assert_contains(
241
('``--should-decrement-desired-capacity`` | '
242
'``--no-should-decrement-desired-capacity`` (boolean)'))
243
244
def test_streaming_output_arg(self):
245
self.driver.main(['s3api', 'get-object', 'help'])
246
self.assert_not_contains('``--outfile``')
247
self.assert_contains('``outfile`` (string)')
248
249
def test_rds_add_arg_help_has_correct_command_name(self):
250
self.driver.main(['rds', 'add-option-to-option-group', 'help'])
251
self.assert_contains('add-option-to-option-group')
252
253
def test_rds_remove_arg_help_has_correct_command_name(self):
254
self.driver.main(['rds', 'remove-option-from-option-group', 'help'])
255
self.assert_contains('remove-option-from-option-group')
256
257
def test_modify_operation_not_in_help(self):
258
self.driver.main(['rds', 'help'])
259
# This was split into add/remove commands. The modify
260
# command should not be available.
261
self.assert_not_contains('modify-option-group')
262
263
264
class TestPagingParamDocs(BaseAWSHelpOutputTest):
265
def test_starting_token_injected(self):
266
self.driver.main(['s3api', 'list-objects', 'help'])
267
self.assert_contains('``--starting-token``')
268
269
def test_max_items_injected(self):
270
self.driver.main(['s3api', 'list-objects', 'help'])
271
self.assert_contains('``--max-items``')
272
273
def test_builtin_paging_params_removed(self):
274
self.driver.main(['s3api', 'list-objects', 'help'])
275
self.assert_not_contains('``--next-token``')
276
self.assert_not_contains('``--max-keys``')
277
278
def test_paging_documentation_added(self):
279
self.driver.main(['s3api', 'list-objects', 'help'])
280
self.assert_contains('``list-objects`` is a paginated operation')
281
self.assert_contains('When using ``--output text`` and the')
282
self.assert_contains('following query expressions: ')
283
284
285
class TestMergeBooleanGroupArgs(BaseAWSHelpOutputTest):
286
def test_merge_bool_args(self):
287
# Boolean args need to be group together so rather than
288
# --foo foo docs
289
# --no-foo foo docs again
290
#
291
# We instead have:
292
# --foo | --no-foo foo docs
293
self.driver.main(['ec2', 'run-instances', 'help'])
294
self.assert_contains('``--dry-run`` | ``--no-dry-run``')
295
296
def test_top_level_bools(self):
297
# structure(scalar) of a single value of Value whose value is
298
# a boolean is pulled into a top level arg.
299
self.driver.main(['ec2', 'modify-instance-attribute', 'help'])
300
self.assert_contains('``--ebs-optimized`` | ``--no-ebs-optimized``')
301
302
def test_top_level_bool_has_no_example(self):
303
# Normally a structure(bool) param would have an example
304
# of {"Value": true|false}", but when we pull the arg up into
305
# a top level bool, we should not generate an example.
306
self.driver.main(['ec2', 'modify-instance-attribute', 'help'])
307
self.assert_not_contains('"Value": true|false')
308
309
310
class TestStructureScalarHasNoExamples(BaseAWSHelpOutputTest):
311
def test_no_examples_for_structure_single_scalar(self):
312
self.driver.main(['ec2', 'modify-instance-attribute', 'help'])
313
self.assert_not_contains('"Value": "string"')
314
self.assert_not_contains('Value=string')
315
316
def test_example_for_single_structure_not_named_value(self):
317
# Verify that if a structure does match our special case
318
# (single element named "Value"), then we still document
319
# the example syntax.
320
self.driver.main(['s3api', 'create-bucket', 'help'])
321
self.assert_contains('LocationConstraint=string')
322
# Also should see the JSON syntax in the help output.
323
self.assert_contains('"LocationConstraint": ')
324
325
326
class TestJSONListScalarDocs(BaseAWSHelpOutputTest):
327
def test_space_separated_list_docs(self):
328
# A list of scalar type can be specified as JSON:
329
# JSON Syntax:
330
#
331
# ["string", ...]
332
# But at the same time you can always replace that with
333
# a space separated list. Therefore we want to document
334
# the space separated list version and not the JSON list
335
# version.
336
self.driver.main(['ec2', 'terminate-instances', 'help'])
337
self.assert_not_contains('["string", ...]')
338
self.assert_contains('"string" "string"')
339
340
341
class TestParamRename(BaseAWSHelpOutputTest):
342
def test_create_image_renames(self):
343
# We're just cherry picking this particular operation to verify
344
# that the rename arg customizations are working.
345
self.driver.main(['ec2', 'create-image', 'help'])
346
self.assert_not_contains('no-no-reboot')
347
self.assert_contains('--reboot')
348
349
class TestCustomCommandDocsFromFile(BaseAWSHelpOutputTest):
350
def test_description_from_rst_file(self):
351
# The description for the configure command
352
# is in _description.rst. We're verifying that we
353
# can read those contents properly.
354
self.driver.main(['configure', 'help'])
355
# These are a few options that are documented in the help output.
356
self.assert_contains('metadata_service_timeout')
357
self.assert_contains('metadata_service_num_attempts')
358
self.assert_contains('aws_access_key_id')
359
360
class TestEnumDocsArentDuplicated(BaseAWSHelpOutputTest):
361
def test_enum_docs_arent_duplicated(self):
362
# Test for: https://github.com/aws/aws-cli/issues/609
363
# What's happening is if you have a list param that has
364
# an enum, we document it as:
365
# a|b|c|d a|b|c|d
366
# Except we show all of the possible enum params twice.
367
# Each enum param should only occur once. The ideal documentation
368
# should be:
369
#
370
# string1 string2
371
#
372
# Where each value is one of:
373
# value1
374
# value2
375
self.driver.main(['cloudformation', 'list-stacks', 'help'])
376
# "CREATE_IN_PROGRESS" is a enum value, and should only
377
# appear once in the help output.
378
contents = self.renderer.rendered_contents
379
self.assertTrue(contents.count("CREATE_IN_PROGRESS") == 1,
380
("Enum param was only suppose to be appear once in "
381
"rendered doc output, appeared: %s" %
382
contents.count("CREATE_IN_PROGRESS")))
383
384
385
class TestParametersCanBeHidden(BaseAWSHelpOutputTest):
386
def mark_as_undocumented(self, argument_table, **kwargs):
387
argument_table['starting-sequence-number']._UNDOCUMENTED = True
388
389
def test_hidden_params_are_not_documented(self):
390
# We're going to demonstrate hiding a parameter.
391
# --device
392
self.driver.session.register('building-argument-table',
393
self.mark_as_undocumented)
394
self.driver.main(['kinesis', 'get-shard-iterator', 'help'])
395
self.assert_not_contains('--starting-sequence-number')
396
397
398
class TestCanDocumentAsRequired(BaseAWSHelpOutputTest):
399
def test_can_doc_as_required(self):
400
# This param is already marked as required, but to be
401
# explicit this is repeated here to make it more clear.
402
def doc_as_required(argument_table, **kwargs):
403
arg = argument_table['volume-arns']
404
self.driver.session.register('building-argument-table',
405
doc_as_required)
406
self.driver.main(['storagegateway', 'describe-cached-iscsi-volumes',
407
'help'])
408
self.assert_not_contains('[--volume-arns <value>]')
409
410
411
class TestEC2AuthorizeSecurityGroupNotRendered(BaseAWSHelpOutputTest):
412
def test_deprecated_args_not_documented(self):
413
self.driver.main(['ec2', 'authorize-security-group-ingress', 'help'])
414
self.assert_not_contains('--ip-protocol')
415
self.assert_not_contains('--from-port')
416
self.assert_not_contains('--to-port')
417
self.assert_not_contains('--source-security-group-name')
418
self.assert_not_contains('--source-security-group-owner-id')
419
420
421
class TestKMSCreateGrant(BaseAWSHelpOutputTest):
422
def test_proper_casing(self):
423
self.driver.main(['kms', 'create-grant', 'help'])
424
# Ensure that the proper casing is used for this command's docs.
425
self.assert_not_contains('generate-data-key')
426
self.assert_contains('GenerateDataKey')
427
428
429
class TestRoute53CreateHostedZone(BaseAWSHelpOutputTest):
430
def test_proper_casing(self):
431
self.driver.main(['route53', 'create-hosted-zone', 'help'])
432
# Ensure that the proper casing is used for this command's docs.
433
self.assert_contains(
434
'do **not** include ``PrivateZone`` in this input structure')
435
436
437
class TestIotData(BaseAWSHelpOutputTest):
438
def test_service_help_command_has_note(self):
439
self.driver.main(['iot-data', 'help'])
440
# Ensure the note is in help page.
441
self.assert_contains(
442
'The default endpoints (intended for testing purposes only) can be found at '
443
'https://docs.aws.amazon.com/general/latest/gr/iot-core.html#iot-core-data-plane-endpoints')
444
445
def test_operation_help_command_has_note(self):
446
self.driver.main(['iot-data', 'get-thing-shadow', 'help'])
447
# Ensure the note is in help page.
448
self.assert_contains(
449
'The default endpoints (intended for testing purposes only) can be found at '
450
'https://docs.aws.amazon.com/general/latest/gr/iot-core.html#iot-core-data-plane-endpoints')
451
452
453
class TestSMSVoice(BaseAWSHelpOutputTest):
454
def test_service_help_not_listed(self):
455
self.driver.main(['help'])
456
# Ensure the hidden service is not in the help listing.
457
self.assert_not_contains('* sms-voice')
458
459
460
class TestAliases(BaseAWSHelpOutputTest):
461
def setUp(self):
462
super(TestAliases, self).setUp()
463
self.files = FileCreator()
464
self.alias_file = self.files.create_file('alias', '[toplevel]\n')
465
self.driver.alias_loader = AliasLoader(self.alias_file)
466
467
def tearDown(self):
468
super(TestAliases, self).tearDown()
469
self.files.remove_all()
470
471
def add_alias(self, alias_name, alias_value):
472
with open(self.alias_file, 'a+') as f:
473
f.write('%s = %s\n' % (alias_name, alias_value))
474
475
def test_alias_not_in_main_help(self):
476
self.add_alias('my-alias', 'ec2 describe-regions')
477
self.driver.main(['help'])
478
self.assert_not_contains('my-alias')
479
480
481
class TestStreamingOutputHelp(BaseAWSHelpOutputTest):
482
def test_service_help_command_has_note(self):
483
self.driver.main(['s3api', 'get-object', 'help'])
484
self.assert_not_contains('outfile <value>')
485
self.assert_contains('<outfile>')
486
487
488
# Use this test class for "help" cases that require the default renderer
489
# (i.e. renderer from get_render()) instead of a mocked version.
490
class TestHelpOutputDefaultRenderer:
491
def test_line_lengths_do_not_break_create_launch_template_version_cmd(self):
492
result = aws('ec2 create-launch-template-version help')
493
assert 'exceeds the line-length-limit' not in result.stderr
494
495