tests package¶
Subpackages¶
tests.mkdocspackage- Submodules
tests.mkdocs.conftestmoduletests.mkdocs.test_mkdocsmoduletest_mkdocs_entry_point()test_on_config_patches_formatters()test_on_config_idempotent()test_patched_formatter_preserves_pymdownx_mro()test_patched_formatter_renders_ansi()test_ansi_stylesheet_only_contains_ansi_rules()test_on_config_registers_stylesheet()test_on_post_build_writes_stylesheet()test_patch_mkdocs_click_usage()test_patch_mkdocs_click_plain_options()test_patch_mkdocs_click_idempotent()test_patch_mkdocs_click_forces_color_during_capture()test_on_config_patches_mkdocs_click()test_full_build_renders_ansi_colors()
tests.sphinxpackage- Submodules
tests.sphinx.conftestmoduletests.sphinx.test_sphinxmoduletests.sphinx.test_sphinx_announcementmoduletests.sphinx.test_sphinx_changelogmoduletests.sphinx.test_sphinx_clickmoduletest_directive_functionality()test_directive_option_format()test_directive_option_language_override()test_sphinx_directive_state_persistence()test_directive_variable_conflict()GENERATED_BLOCK_ANCHORtest_generated_block_is_attributed_to_the_directive_body()test_exit_exception_percolate()test_clickrunner_forces_color()test_clickrunner_sets_the_handlers_of_the_build_aside()test_clickrunner_sets_the_level_of_the_build_aside()test_clickrunner_capture_mode_controls_fileno()test_clickrunner_captures_prep_path_stdout()test_program_from_command_line()test_clickrunner_keeps_a_multi_word_prog_name()test_clickrunner_prompt_inlines_environment_assignments()test_clickrunner_prompt_quotes_a_spaced_argument()test_clickrunner_hide_prompt_drops_the_invocation()test_clickrunner_prompt_past_the_input_aborts()test_click_run_prompt_options()test_click_run_output_holding_a_fence_stays_in_its_block()test_code_fence_outgrows_the_fences_it_wraps()test_click_run_hide_prompt_reaches_the_screenshot()test_click_run_screenshot_writes_the_asset()ANIMATED_SOURCEtest_click_run_screenshot_animate_stacks_a_spinner()test_click_run_screenshot_animate_defines_every_class_it_uses()test_click_run_screenshot_animate_accepts_bare_frames()test_click_run_screenshot_line_numbers_give_every_frame_one_gutter()test_click_run_screenshot_animate_bare_frames_need_an_interval()test_click_run_screenshot_animate_rejects_a_foreign_subject()test_click_run_screenshot_animate_is_deterministic()test_click_run_screenshot_animate_rewrites_a_declared_asset()test_click_run_screenshot_animate_carries_a_presentation_change()test_click_run_screenshot_leaves_an_unchanged_asset_alone()RECORDED_SOURCEtest_click_run_screenshot_record_writes_once()test_click_run_screenshot_record_holds_its_last_frame()test_click_run_screenshot_hold_overrides_the_pause()test_click_run_screenshot_blank_closes_the_cycle()test_click_run_screenshot_speed_scales_the_recorded_frames()test_click_run_screenshot_emphasize_lines_bands_a_still()test_click_run_screenshot_emphasize_lines_bands_an_animation()test_click_run_screenshot_emphasize_lines_rejects_a_line_that_is_not_there()test_click_run_screenshot_background()test_click_run_screenshot_columns_auto()test_click_run_screenshot_columns_rejects_an_unusable_width()test_click_run_screenshot_frame_options()test_click_run_screenshot_preset_swaps_the_prompt()test_click_run_screenshot_preset_defaults_to_the_project_wide_one()test_click_run_screenshot_carries_no_watermark_by_default()test_click_run_screenshot_watermark()test_click_run_screenshot_opacity()test_click_run_screenshot_opacity_rejects_what_is_not_one()test_click_run_screenshot_timing_options_take_a_positive_number()test_click_run_screenshot_background_rejects_an_unknown_chrome()test_click_run_mirror_region_round_trips()test_click_run_mirror_needs_both_options()test_click_run_mirror_skips_a_nested_example()test_update_screenshot_blocks_writes_and_checks()test_click_source_screenshot_pictures_its_own_code()test_click_source_screenshot_takes_a_syntax_style()test_click_source_screenshot_bands_its_emphasized_lines()test_click_source_screenshot_is_deterministic()test_click_source_mirror_shows_the_snippet()test_python_render_mirror_is_left_to_its_own_refresher()
tests.sphinx.test_sphinx_click_configmoduleBasketConfigMarketConfigMARKET_SEEDPastryConfigPASTRY_CONFIGtest_click_config_default_and_toml_literal()test_click_config_renders_table_and_sections()test_click_config_links_every_option_to_its_section()test_click_config_keeps_a_piped_default_in_its_cell()test_click_config_toml_examples()test_click_config_section_override_and_suppress()test_click_config_bare_schema_argument()test_click_config_no_table_and_no_examples()test_click_config_exec_defined_schema_lacks_docstrings()test_click_config_errors_without_schema()test_click_config_errors_on_non_schema()test_click_config_errors_on_unknown_name()test_click_config_errors_in_rst()test_click_config_heading_offset_adapts_to_surrounding_section()OrchardConfigtest_click_config_warns_on_docstring_heading()
tests.sphinx.test_sphinx_click_treemoduleKITCHEN_CLItest_click_tree_renders_summary_table_and_help_blocks()test_click_tree_inline_import_in_body()test_click_tree_no_table_and_no_root()test_click_tree_max_depth_truncates_walk()test_click_tree_label_and_anchor_prefix_override()test_click_tree_summary_keeps_a_piped_description_in_its_cell()test_click_tree_summary_shows_the_whole_description()test_click_tree_links_every_command_to_its_own_section()test_click_tree_walks_subcommands_like_the_tree_option()test_click_tree_errors_on_non_command()test_click_tree_errors_on_unknown_name()test_click_tree_errors_in_rst()test_click_tree_heading_offset_defaults_to_top_level()test_click_tree_heading_offset_adapts_to_surrounding_section()test_click_tree_heading_offset_explicit_override()
tests.sphinx.test_sphinx_crosshairmoduletests.sphinx.test_sphinx_doctreesmoduletests.sphinx.test_sphinx_fail_on_warningsmoduletests.sphinx.test_sphinx_linkcheckmoduletests.sphinx.test_sphinx_manpagesmoduletest_manpages_hook_writes_tree_into_outdir()test_manpages_hook_honors_prog_name_and_output_dir()test_manpages_hook_skips_non_html_builder()test_manpages_hook_empty_config_is_noop()test_manpages_hook_skips_entry_without_script()test_manpages_module_help_documents_config_shape()test_manpages_hook_emits_html_siblings()test_manpages_hook_respects_render_html_opt_out()test_manpages_directive_renders_one_link_per_command()test_manpages_directive_links_are_ignored_by_linkcheck()test_manpages_directive_links_follow_the_builder_layout()test_manpages_directive_links_survive_a_shared_doctree_cache()test_manpages_directive_is_noop_when_config_empty()test_manpages_directive_renders_inline_literals_as_code()
tests.sphinx.test_sphinx_matrixmodulepytestmarkMATRIX_MODULELABEL_DATE_REWIDGET_CORNERoffline_pypi()unreadable_pypi()git_repo()dated_tag()declare_python()declare_widget()named_repo()table_cells()table_header()tagged_table_rows()test_parse_python_spec()test_python_versions_released_by_default_table()test_python_versions_released_by_sorted_ascending()test_python_versions_released_by_custom_table()test_python_release_dates_shape()synthetic_repo()test_python_matrix_groups_synthetic()test_python_matrix_groups_returns_named_tuple()test_python_matrix_groups_reads_a_tag_that_is_no_utf8()test_python_matrix_groups_no_tags()test_python_matrix_groups_version_floor()test_python_matrix_groups_ceiling_honored()test_python_matrix_groups_keeps_declared_spec()test_python_matrix_groups_respelled_floor_continues_range()test_range_labels()test_python_cell()test_python_cell_spec_shapes()test_python_matrix_table_three_states()test_python_matrix_table_synthetic()test_python_matrix_table_show_date()test_python_matrix_table_column_order()test_python_matrix_table_row_order()test_python_matrix_table_invalid_order()test_python_matrix_table_python_floor()test_python_matrix_table_empty()split_floor_repo()test_python_matrix_table_envelope()test_python_matrix_table_full_history()test_matrix_directive_full_history_option()test_update_matrix_blocks_full_history_marker()test_matrix_python_directive_renders_table()test_matrix_python_directive_respects_floors()test_matrix_python_directive_order_options()test_matrix_python_directive_no_tags_renders_nothing()test_matrix_python_directive_renders_embedded_table()test_update_matrix_blocks_populates_empty_block()test_update_matrix_blocks_idempotent()test_update_matrix_blocks_check_mode()test_update_matrix_blocks_leaves_bad_path_untouched()test_update_matrix_blocks_keeps_other_line_separators()test_update_matrix_blocks_leaves_an_unreadable_pypi_untouched()test_update_matrix_blocks_skips_examples_nested_in_code_block()test_refresh_directives_cli()test_refresh_directives_cli_without_sphinx()test_to_specifier_set()test_to_specifier_set_unparsable()test_to_specifier_set_empty_matches_everything()test_spec_identity()test_spec_floor()test_extract_requirement()test_extract_requirement_canonicalizes_name()test_extract_requirement_from_setup_py()test_dependency_cell()test_dependency_cell_caps_later_majors()test_newest_major()test_dependency_columns()test_envelope()test_column_candidates()test_latest_locked_version()test_latest_locked_version_without_lockfile()stub_releases()synthetic_dep_repo()test_dependency_matrix_groups()test_dependency_matrix_table_columns_and_cells()exotic_spec_repo()test_dependency_matrix_table_exotic_specs()test_dependency_matrix_table_merges_equivalent_specs()narrowing_floor_repo()test_dependency_matrix_table_merge_keeps_distinct_specs()test_dependency_matrix_table_bins_split_ranges()pinned_widget_repo()test_dependency_matrix_table_pinned_spec()test_dependency_matrix_table_lone_ceiling()test_dependency_matrix_table_suffixed_poetry_floor()test_dependency_matrix_table_shows_a_release_ahead_of_the_lock()WIDGET_DATESdated_dep_repo()test_dependency_matrix_table_undeclared_after_superseded()test_dependency_matrix_table_unmet_major_after_last_release()test_dependency_matrix_table_envelope()ENVELOPE_WIDGET_DATESenvelope_repo()test_envelope_list_across_axes()test_envelope_list_without_cut()test_update_matrix_blocks_envelope_region()test_update_matrix_blocks_leaves_an_envelope_region_untouched()test_dependency_matrix_groups_respelled_spec_continues_range()test_dependency_matrix_table_headers_read_in_column_order()pypi()test_pypi_releases()test_pypi_releases_dates()test_pypi_releases_discards()test_pypi_releases_unreadable()test_to_specifier_set_poetry_conformance()test_to_specifier_set_poetry_bare_wildcard()test_dependency_matrix_table_show_date()test_dependency_matrix_table_column_order()test_dependency_matrix_table_row_order()test_dependency_matrix_table_empty()test_matrix_dependency_directive_renders()test_update_matrix_blocks_marker_form()test_walk_tags_dates_an_annotated_tag()test_update_matrix_blocks_reads_git_twice_per_refresh()test_update_matrix_blocks_marker_order_options()test_update_matrix_blocks_marker_show_date()test_matrix_directive_show_date_option()test_marker_form_renders_natively_in_sphinx()test_resolve_root_relative_base_dir()test_render_block_default_package_label()test_update_matrix_blocks_label_ignores_checkout_name()test_render_block_raises_without_git()test_update_matrix_blocks_preserves_table_without_git()test_matrix_directive_renders_embedded_without_git()
tests.sphinx.test_sphinx_mermaidmoduletests.sphinx.test_sphinx_myst_docstringsmoduletest_xref_conversion()test_colon_fence_conversion()test_colon_fence_preserves_blank_lines()test_colon_fence_blank_line_before_body()test_backtick_fence_conversion()test_mixed_colon_outer_backtick_inner()test_plain_code_fence_conversion()test_plain_code_fence_blank_line_before_body()test_plain_code_fence_not_directive()test_plain_code_fence_idempotent()test_directive_options_follow_the_header()test_role_opening_a_body_is_not_an_option()test_code_block_content_is_verbatim()test_closing_fence_does_not_open_a_code_block()test_footnote_reference_conversion()test_footnote_definition_conversion()test_footnote_ref_and_def_combined()test_footnote_def_not_matched_as_ref()test_link_conversion()test_link_not_converted()test_multiple_links_on_same_line()test_link_strips_backticks_from_label()test_inline_code_conversion()test_inline_code_not_converted()test_double_backtick_span_keeps_its_backticks()test_inline_code_no_cross_contamination_with_xref()test_leading_underscore_span_not_a_hyperlink_ref()test_rst_literals_are_opaque()test_nested_protected_spans_are_restored()test_role_shaped_code_span_not_protected_as_role()test_idempotent_rst_passthrough()test_idempotent_already_converted()test_idempotent_full_rst_docstring()test_mixed_myst_attribute_then_rst_metadata()test_real_detection_function()test_real_group_with_wikipedia_link()test_all_conversions_combined()test_real_caution_with_code_block()test_myst_inside_field_lists()test_full_docstring_with_myst_field_lists()test_unsupported_constructs_pass_through()test_curly_braces_in_inline_code()test_plain_code_fence_empty_body()test_longer_fence_holds_a_shorter_one()test_blank_line_opening_a_body_goes_with_the_header()test_nested_same_type_fences_not_supported()test_directive_inside_field_list_not_supported()test_link_with_parentheses_in_url()test_consecutive_field_list_entries_all_converted()test_empty_field_list_content()test_xref_adjacent_to_field_list_colon()test_multiple_xrefs_in_single_field_entry()test_link_with_backtick_label_in_field_list()test_setup_rejects_wrong_extension_order()test_setup_accepts_correct_order()
tests.sphinx.test_sphinx_pythonmoduletest_python_run_renders_stdout()test_python_source_seeds_namespace_for_python_run()test_python_run_language_override()test_python_run_screenshot_writes_the_asset()test_python_run_emphasize_lines_split()test_python_render_passes_block_level_html()test_python_render_host_myst_injects_table()test_python_render_host_myst_injects_heading()test_python_render_host_rst_injects_admonition()test_python_render_myst_in_rst_host()test_python_render_rst_in_myst_host()test_python_render_myst_in_myst_host_still_works()test_python_render_rst_in_rst_host_still_works()test_exec_directives_disabled_by_default()test_exec_directives_enabled_with_opt_in()test_python_runner_isolated_from_click_runner()test_mirror_src_rewrite_inserts_region()test_mirror_src_rewrite_is_idempotent()test_mirror_src_first_sight_stops_at_the_next_generator()test_mirror_src_via_update_mirror_blocks()test_mirror_src_rewrite_skips_example_nested_in_code_block()test_rewrite_mirrors_refreshes_every_mirror_src_spelling()test_mirror_rewrite_inserts_region()test_mirror_rewrite_is_idempotent()test_mirror_rewrite_replaces_stale_region()test_mirror_rewrite_skips_example_nested_in_code_block()test_python_render_mirror_renders_fresh_without_touching_source()test_update_mirror_blocks_populates_and_idempotent()test_update_mirror_blocks_check_mode()test_update_mirror_blocks_imports_sibling_module()test_refresh_directives_cli_refreshes_mirror_blocks()test_refresh_directives_cli_reads_and_writes_a_page_once()test_python_render_mirror_show_source_still_single_table()
tests.sphinx.test_sphinx_tablesmoduletests.sphinx.test_sphinx_tabsmoduletests.sphinx.test_sphinx_todosmoduleTODO_SOURCE_RECOMPOST_MODULEGRAFTING_MODULEVARIETIES_MODULEVARIETIES_INITBOWLS_MODULEUNRENDERED_REbuild_project()build_orchard()unrendered_todos()automodule()fake_node()test_todo_identity_ignores_the_import_path_of_a_docstring()test_todo_identity_reads_the_source_form_without_a_file()test_is_reexport()test_full_api_page_and_feature_page_yield_one_entry_each()test_reexported_members_yield_one_entry_each()test_surviving_entry_attributes_to_the_defining_module()test_reexported_attribute_yields_one_entry()test_attributes_sharing_an_object_keep_an_entry_each()test_dedupe_switched_off_restores_every_rendering()test_distinct_todos_all_survive()test_build_succeeds_without_the_todo_extension()test_docstring_todos()test_docstring_todos_rejects_invalid_source()test_package_modules_walks_without_importing()test_unrendered_todos_warn_at_their_line()test_rendered_todos_do_not_warn()test_todo_rendered_under_another_path_does_not_warn()test_unrendered_todo_check_stays_silent()test_listed_unrendered_todo_fails_the_build()
tests.sphinx.test_sphinx_viewcodemodule
Submodules¶
tests.conftest module¶
Fixtures, configuration and helpers for tests.
- tests.conftest.httpserver_listen_address()[source]¶
Bind the local HTTP server to the loopback address, not to a name.
Overrides
pytest-httpserver’s default of("localhost", 0). Resolving that name is the one thing thetests/test_config.pycases serving a configuration file over HTTP need from the host, and a build sandbox is exactly where it is unavailable: the Nix one on macOS denies the lookup, so every one of them errors out withsocket.gaierror: [Errno 8] nodename nor servname provided, or not known.Binding the literal address asks nothing of the resolver. Whether a sandbox additionally gates the loopback socket is its own policy, and a separate question: this only removes the lookup that failed first.
- tests.conftest.requires_process_table()[source]¶
Skip a test where the process table does not list the test’s own process.
The kill paths of
run_clifind a command’s descendants in the process table. Linux reads it from/proc. Elsewhere it comes fromps, which a build sandbox can deny: the MacPorts test phase gets an empty listing, so the kill tests would fail on what the sandbox hides rather than on the code.The probe runs
psitself instead of_posix_process_table(), so a regression that empties the table still fails those tests.
- tests.conftest.skip_windows_colors = MarkDecorator(mark=Mark(name='skipif', args=(<extra_platforms.pytest._DeferredCondition object>,), kwargs={'reason': 'Click overstrip colors on Windows'}))¶
Skips color tests on Windows as
click.testing.invokeoverzealously strips colors.See: - https://github.com/pallets/click/issues/2111 - https://github.com/pallets/click/issues/2110
- tests.conftest.TRANSIENT_STATUS_CODES = frozenset({408, 425, 429, 500, 502, 503, 504})¶
Status codes a host answers with when it is refusing this request rather than reporting something about the resource: a rate limit, a proxy hiccup, or a service that is briefly down.
- tests.conftest.fetch_or_skip(url, timeout=60)[source]¶
Fetch
url, skipping the test when the failure says nothing about it.A network-dependent test asserts something about what a host serves. It cannot assert it while the host is rate-limiting, timing out, or down, and a bare
assert response.okthere reportsassert False: a red run that looks like the finding the test exists to make, with nothing naming the cause. GitHub throttles anonymous archive downloads, so a full-suite run hits this on its own schedule and reads as an order-dependent flake.A response that is about the resource still fails, loudly and with its status: a 404 means the URL these tests build no longer resolves, which is the finding, not the weather.
- Return type:
Response
- tests.conftest.walk_commands(command, ctx=None, path=())[source]¶
Yield every
(path, command)pair undercommand, itself included.pathholds the subcommand names leading to the command, the root’s own name excluded, so joining it names the invocation a user would type.- Parameters:
- Return type:
- class tests.conftest.TTYStringIO(initial_value='', newline='\n')[source]¶
Bases:
StringIOAn in-memory text buffer that claims to be an interactive terminal.
- class tests.conftest.StallingTTY[source]¶
Bases:
TTYStringIOA terminal whose writes block once
stallis set, untilreleaseis.Stands in for a terminal paused by flow control (Ctrl+S), or a full pipe.
tests.test_accessibility module¶
- tests.test_accessibility.report_cli()[source]¶
An extra command that echoes the resolved color flag and table format, then renders a table with a styled cell.
- tests.test_accessibility.test_accessible_precedence(invoke, report_cli, extra_args, expected_color, expected_format)[source]¶
- tests.test_accessibility.test_cli_envvar_outranks_accessible_envvar(invoke, report_cli)[source]¶
The variable of one CLI is more specific than the machine-wide one.
- tests.test_accessibility.test_accessible_envvar_outranks_configuration(invoke, report_cli, create_config, configured, envvar, expected_color, expected_format)[source]¶
Unlike the other machine-wide variables,
ACCESSIBLEoutranks a configuration file: the mode is settled before the file loads.
- tests.test_accessibility.test_explicit_flag_overrides_accessible_envvar(invoke, report_cli)[source]¶
- tests.test_accessibility.test_standalone_decorator_on_plain_click_command(invoke)[source]¶
--accessiblelowers the defaults of the sibling color and table options, so they must be composed alongside it on a plainclick.command.
- tests.test_accessibility.test_accessible_flag_published_to_context(invoke, args, expected)[source]¶
set_accessible publishes the resolved intent at ctx.meta[ACCESSIBLE].
- tests.test_accessibility.test_clear_is_noop_under_accessible(invoke, monkeypatch)[source]¶
clear() defers to click.clear normally, but no-ops under –accessible.
tests.test_ansi module¶
Tests for click_extra.ansi, the ANSI escape grammar and its decoders.
tests.test_blocks module¶
Test the dependency-light self-updating block toolkit.
- tests.test_blocks.DEPRECATED_BLOCKS_NAMES = ('FenceSpan', 'fence_spans', 'iter_markdown_files', 'marker_res', 'region_markers', 'replace_region', 'rewrite_fenced_regions', 'skip_region', 'split_options', 'update_blocks')¶
Public names of the
9.xclick_extra.blocksmodule that10.xstill defines.
- tests.test_blocks.REGION = 'intro\n\n<!-- t -->\nOLD\n<!-- t-end -->\n\nend\n'¶
A minimal document carrying a single
<!-- t --> / <!-- t-end -->region.
- tests.test_blocks.test_blocks_module_needs_no_sphinx_extra()[source]¶
click_extra.markdown.blocksimports without pulling in thesphinxextra.The whole point of hosting the toolkit outside
click_extra.sphinxis that a release pipeline or standalone script can reuse it without installing Sphinx or docutils. Assert it in a fresh interpreter so an already-loaded Sphinx (from another test) cannot mask a regression.
- tests.test_blocks.test_deprecated_blocks_alias_serves_9x_name(name)[source]¶
click_extra.blocksserves each name it kept from9.x, with a warning.
- tests.test_blocks.test_deprecated_blocks_alias_star_import()[source]¶
A star import binds each name the alias serves, with one warning per name.
- tests.test_blocks.test_deprecated_blocks_alias_serves_no_other_name(name)[source]¶
A name the alias does not serve stays missing, and sets off no warning.
- tests.test_blocks.test_marker_res_grammar()[source]¶
The open marker is bare; the close marker carries the
-endsuffix.
- tests.test_blocks.test_replace_region(content, pad, expected)[source]¶
replace_regionswaps the marked body under both padding modes.
- tests.test_blocks.QUOTED_REGION = '<!-- t -->\n\nOLD\n\n```markdown\n<!-- t-end -->\n```\n\n<!-- t-end -->\n'¶
A region whose body quotes its own closing marker inside a code fence.
- tests.test_blocks.test_skip_region_steps_over_a_quoted_end_marker()[source]¶
A closing marker quoted in a fence of the region does not end it.
- tests.test_blocks.test_rewrite_fenced_regions_keeps_a_quoted_end_marker()[source]¶
A region holding a fence that quotes its closing marker round-trips.
A search for the closing marker that does not step over fences ends the region on the quoted one, and each refresh then leaves the rest of the stale region below the new one.
- tests.test_blocks.test_replace_region_ignores_markers_quoted_in_fences()[source]¶
Markers shown in a code example are not the live region.
- tests.test_blocks.test_replace_region_missing_markers_is_noop()[source]¶
A document without the markers is returned untouched.
- tests.test_blocks.test_fence_spans_treats_nested_fences_as_opaque()[source]¶
A fence nested inside a longer fence never starts a span of its own.
- tests.test_blocks.test_fence_spans_close(lines, close, end)[source]¶
A fence closes on the first bare backtick run that is long enough.
- tests.test_blocks.test_fence_span_body(lines, body)[source]¶
The body of a span is everything between its fence lines.
- tests.test_blocks.test_fence_spans_chars(chars, spans)[source]¶
Colon fences are fences only on request, and close on colons alone.
- tests.test_blocks.test_walk_fences_steps_over_fences()[source]¶
Lines outside fences come one by one, each fence as one unit.
- tests.test_blocks.test_rewrite_fences_keeps_what_visit_leaves()[source]¶
A step
visitpasses on is copied, an unterminated fence included.
- tests.test_blocks.test_split_options(lines, options, content)[source]¶
Leading option lines are split from the content of a fence body.
tests.test_carapace module¶
- class tests.test_carapace.FruitType[source]¶
Bases:
ParamTypeA param type with a custom, dynamic
shell_completeoverride.- shell_complete(ctx, param, incomplete)[source]¶
Return a list of
CompletionItemobjects for the incomplete value. Most types do not provide completions, but some do, and this allows custom types to provide custom completions as well.- Parameters:
ctx – Invocation context for this command.
param – The parameter that is requesting completion.
incomplete – Value being completed. May be empty.
Added in version 8.0.
- tests.test_carapace.complete_city(ctx, param, incomplete)[source]¶
A param-level
shell_completecallback.
A hidden flag is exported marked, never silently.
Carapace completes what a spec lists, so a hidden option stays reachable like a hidden command does;
&is what keeps a reader able to tell.
- tests.test_carapace.test_required_flag_key_carries_the_modifier(opts, value, repeatable, expected)[source]¶
!before&, the ordercarapace-specwrites them in.
- tests.test_carapace.test_required_boolean_pair_is_left_unmarked()[source]¶
Either spelling satisfies Click, and the spec cannot say “one of these”.
Marking both would demand both; marking the positive alone would hide that the negative answers too.
- tests.test_carapace.test_description_drops_the_no_rewrap_marker()[source]¶
Click’s
marker never reaches the YAML.It is a control character telling Click’s own formatter not to rewrap, not prose: a description carrying it would hand Carapace a stray byte.
- tests.test_carapace.test_command_description_loses_its_backticks_like_a_flag_does()[source]¶
A command’s description and its flags share one normalizer.
A reST inline literal reads the same in both: its text is kept, and the backticks that only HTML docs render are dropped.
- tests.test_carapace.test_operand_help_reaches_the_documentation_block()[source]¶
An operand’s help has one home in the spec, and it is not
completion.completion.positionalholds actions, so the prose rides the siblingdocumentationblock, indexed the same way.
- tests.test_carapace.test_undocumented_operand_leaves_no_documentation_block()[source]¶
Nothing to say means no block at all, rather than a row of blanks.
- tests.test_carapace.test_documentation_stays_aligned_with_its_actions()[source]¶
A documented operand behind an undocumented one keeps its index.
The two lists are read positionally, so a dropped blank would shift the prose onto the wrong operand.
- tests.test_carapace.test_option_computing_its_help_still_describes_itself()[source]¶
An option leaving
helpatNonestill reaches the spec described.Click Extra’s own
-vand-qbuild their text from the resolved base verbosity, so reading the attribute hands back nothing.
- tests.test_carapace.test_enum_choice_completes_by_member_name()[source]¶
An
Enumchoice completes by the member names Click accepts.str()of a member readsFruit.APPLE, which Click rejects as a value: the spec has to carry the spelling the shell can type.
- tests.test_carapace.test_completion_variable_follows_clicks_derivation()[source]¶
The callback exports the variable Click reads for the program name.
Click turns both
-and.into_before upper-casing, so a script run asgarden.pylistens to_GARDEN_PY_COMPLETE. Any other spelling is a variable nothing reads, and the callback silently completes nothing.
- tests.test_carapace.test_no_flag_is_persistent(spec)[source]¶
Click parses an option only at the level of its own command.
A
persistentflagsentry would offer an option after a subcommand, where Click rejects it, so no node of the spec carries one.
- tests.test_carapace.test_spec_agrees_with_click_on_flag_scope(invoke)[source]¶
The spec offers a root option after a subcommand exactly when Click takes it.
- tests.test_carapace.test_dump_requires_pyyaml(monkeypatch)[source]¶
A missing PyYAML surfaces as a pointed error at dump time, not at import.
- tests.test_carapace.test_help_format_carapace_without_pyyaml(monkeypatch)[source]¶
--help-format carapacenames the extra to install, with no traceback.
- tests.test_carapace.test_subcommand_dynamic_completion_resolves_end_to_end(monkeypatch)[source]¶
The COMP_WORDS a subcommand flag’s generated macro builds must let the backend resolve that option’s value, not the root command.
When completing a subcommand flag’s value, Carapace forwards an empty word list to the macro: it strips the parent command names, and keeps the flag being completed in its own state rather than passing it through. So the macro bakes both the command path and the flag spelling, and
$*expands to nothing. This reconstructs the resulting COMP_WORDS from the generated macro and confirms the backend resolves the forecast callback. A root-only, flag-less macro would reconstruct bareweatherand complete the subcommand list instead.
- tests.test_carapace.test_wrap_carapace_install(runner, greet_script, tmp_path, monkeypatch)[source]¶
- tests.test_carapace.test_xdg_dir_resolves_at_call_time(tmp_path, monkeypatch, environ, expected)[source]¶
The variable wins when set and non-empty, else the default under
HOME.
- tests.test_carapace.test_get_cache_dir(monkeypatch, platform_id, environ, expected)[source]¶
An absolute
XDG_CACHE_HOMEcomes first, except on Windows.
- tests.test_carapace.test_install_resolves_home_at_call_time(tmp_path, monkeypatch)[source]¶
The spec lands under the
HOMEin force at the call, not at import.With no
XDG_CONFIG_HOMEset, the spec directory derives from the home directory. A test fixture or a packaging script pointingHOMEelsewhere for one invocation is honored, which a path expanded at import cannot do.
- tests.test_carapace.test_wrap_carapace_mutually_exclusive(runner, greet_script, extra_flags, message)[source]¶
- tests.test_carapace.test_wrap_install_requires_an_installable_format(runner, greet_script)[source]¶
A destination only means something for a rendering someone installs.
- tests.test_carapace.test_spec_drives_real_carapace_engine(tmp_path)[source]¶
Feed a generated spec to the real carapace-spec binary, end-to-end.
The tests above simulate Carapace at the CarapaceComplete level. This one downloads a pinned carapace-spec release and drives it through each dynamic shape: a root option, a subcommand option, and a subcommand argument. It guards the contract between the emitted macros and Carapace’s real traverse/exec/parse behavior, which the simulation cannot see: Carapace strips parent command names from the words it forwards, and withholds the flag being completed entirely, so the macros must bake both back in.
Attention
Downloads the carapace-spec binary from GitHub and executes it, hence the
networkmark (andonce, to run on a single CI runner). The dynamic macro shells out, so the test only runs on POSIX platforms.
tests.test_cli_wrapper module¶
Tests for the CLI wrapper feature.
- tests.test_cli_wrapper.GREET_SCRIPT = 'import click\n\n@click.command()\n@click.option("--name", default="World", help="Name to greet.")\ndef hello(name):\n """Greet someone."""\n click.echo(f"Hello, {name}")\n\nif __name__ == "__main__":\n hello()\n'¶
Plain
@click.command()script: patched via decorator defaults.
- tests.test_cli_wrapper.CUSTOM_CLS_SCRIPT = 'import click\n\nclass RecipeGroup(click.Group):\n """Custom group like Flask\'s FlaskGroup."""\n\n@click.command(cls=RecipeGroup)\ndef kitchen():\n """Manage recipes and ingredients."""\n\n@kitchen.command()\n@click.option("--servings", default=4, help="Number of servings.")\ndef bake(servings):\n """Bake a cake."""\n click.echo(f"Baking for {servings}")\n\nif __name__ == "__main__":\n kitchen()\n'¶
Script with explicit
cls=RecipeGroup: patched via method patching.
- tests.test_cli_wrapper.MULTI_OPTION_SCRIPT = 'import click\n\n@click.command()\n@click.option("--city", default="Paris", help="City name.")\n@click.option("--unit", default="celsius", help="Temperature unit.")\n@click.option("--verbose", is_flag=True, help="Show details.")\ndef weather(city, unit, verbose):\n """Check the weather."""\n msg = f"{city}: 22 {unit}"\n if verbose:\n msg += " (detailed)"\n click.echo(msg)\n\nif __name__ == "__main__":\n weather()\n'¶
Script with multiple options for config passthrough tests.
- tests.test_cli_wrapper.PACKAGE_GROUP_SRC = 'import click\n\n@click.group()\ndef cli():\n """Manage a produce stand."""\n\n@cli.command()\ndef restock():\n """Restock the shelves."""\n click.echo("Restocked")\n'¶
Module-level Click group, exposed by a package inside a project directory.
- tests.test_cli_wrapper.ORCHARD_SCRIPT = 'import click\n\n@click.group(context_settings={"auto_envvar_prefix": "ORCHARD"})\ndef orchard():\n """Tend the orchard."""\n\n@orchard.command(context_settings={"auto_envvar_prefix": "PRESS"})\n@click.option("--batch", type=int, default=3, help="Apples per batch.")\ndef press(batch):\n """Press apples into cider."""\n click.echo(f"Pressing {batch} apples")\n\nif __name__ == "__main__":\n orchard()\n'¶
Group whose subcommand sets an environment variable prefix of its own.
- tests.test_cli_wrapper.greet_script(tmp_path)[source]¶
A minimal Click CLI script for wrapping tests.
- tests.test_cli_wrapper.custom_cls_script(tmp_path)[source]¶
A Click CLI with explicit
cls=CustomGroup(like Flask’s FlaskGroup).
- tests.test_cli_wrapper.weather_script(tmp_path)[source]¶
A Click CLI with multiple options for config tests.
- tests.test_cli_wrapper.make_project(tmp_path)[source]¶
Build a local project directory exposing a Click group at module level.
The returned factory writes packaging metadata (
pyproject.tomlorsetup.cfg) plus a package, in either the flat or src layout. On teardown it undoes thesys.pathandsys.modulesmutations that resolving a project directory triggers, keeping the tests isolated.
- tests.test_cli_wrapper.test_patched_command_no_extra_params()[source]¶
Patched commands carry no default_params.
- tests.test_cli_wrapper.test_resolve_py_file_missing(tmp_path)[source]¶
A .py path that doesn’t exist falls through to module resolution.
- tests.test_cli_wrapper.test_resolve_directory_flat_layout(make_project)[source]¶
A project directory resolves via its pyproject.toml entry point.
- tests.test_cli_wrapper.test_resolve_directory_src_layout(make_project)[source]¶
A src-layout project puts its src/ directory on sys.path.
- tests.test_cli_wrapper.test_resolve_directory_setup_cfg(make_project)[source]¶
console_scripts in setup.cfg resolve when no pyproject.toml is present.
- tests.test_cli_wrapper.test_resolve_directory_duplicate_scripts(make_project)[source]¶
Multiple script names pointing to one target resolve unambiguously.
- tests.test_cli_wrapper.test_resolve_directory_run(runner, make_project)[source]¶
The default run mode imports and executes a project directory’s CLI.
- tests.test_cli_wrapper.test_resolve_directory_introspect(make_project)[source]¶
Introspection discovers the command tree of a project directory.
- tests.test_cli_wrapper.test_resolve_directory_no_scripts(tmp_path)[source]¶
A project without console scripts raises an actionable error.
- tests.test_cli_wrapper.test_resolve_directory_multiple_scripts(make_project)[source]¶
Distinct entry-point targets cannot be disambiguated and raise.
- tests.test_cli_wrapper.test_resolve_directory_package_not_found(tmp_path)[source]¶
An entry point whose package is absent on disk raises.
- tests.test_cli_wrapper.test_run_invokes_target(runner, script_fixture, target_args, expected_text, request)[source]¶
The run subcommand forwards arguments to the target CLI.
- tests.test_cli_wrapper.test_run_colorizes(runner, script_fixture, target_args, request)[source]¶
Help output contains ANSI escape codes.
- tests.test_cli_wrapper.test_run_highlights_keywords_with_custom_cls(runner, custom_cls_script)[source]¶
Options and subcommands are individually styled, not just headings.
- tests.test_cli_wrapper.test_run_usage_line_ignores_the_launcher(runner, make_project, outer_package, monkeypatch)[source]¶
The wrapped target’s usage line names the script, not click-extra’s launcher.
A target whose Click command detects its own program name reads
__main__.__package__, which states how click-extra itself was launched. Left alone, a documentation build runningpython -m sphinxpaints a wrapped usage line aspython -m sphinx.flask. Pinning the detection on the script name keeps it identical whatever the launcher.
- tests.test_cli_wrapper.test_resolve_target_command_returns_command_and_context(greet_script)[source]¶
The shared resolver returns the target’s command object and a context.
- tests.test_cli_wrapper.test_resolve_target_command_drills_subcommand(custom_cls_script)[source]¶
Extra subcommands navigate into nested groups.
- tests.test_cli_wrapper.test_resolve_target_command_rejects_a_wrong_path(custom_cls_script, path, message)[source]¶
- tests.test_cli_wrapper.test_module_scan_finds_the_one_command(tmp_path, source, expected)[source]¶
A module without an entry point is scanned for its one command.
- tests.test_cli_wrapper.test_module_scan_refuses_an_ambiguous_module(tmp_path, source, message)[source]¶
- tests.test_cli_wrapper.test_params_follow_a_subcommands_own_context_settings(runner, tmp_path)[source]¶
A subcommand’s own
context_settingsshape the context it is described in.Click applies them when it invokes the subcommand, so the environment variable the parameter table names is the one the run reads.
- tests.test_cli_wrapper.test_wrapper_group_runs_a_lazy_subcommand(runner, tmp_path)[source]¶
A lazy subcommand not imported yet runs, rather than being wrapped.
- tests.test_cli_wrapper.test_params_follow_the_targets_own_context_settings(runner, tmp_path)[source]¶
Every
context_settingskey of the target reaches the parameter table.A target answering to
-has well as--helplists both spellings, as its own help screen does.
Leading words navigate, and everything from the first option is replayed.
- tests.test_cli_wrapper.test_wrap_man_reads_manual(runner, greet_script)[source]¶
click-extra wrap --man -- SCRIPTreads the target’s manual and exits.A CLI shipping no man page of its own still gets one to read, typeset from its own command tree.
- tests.test_cli_wrapper.test_wrap_help_format_man_emits_source(runner, greet_script)[source]¶
The roff a packager installs comes from the format, not from –man.
- tests.test_cli_wrapper.test_wrap_man_custom_class_group(runner, custom_cls_script)[source]¶
--manresolves a custom-class group target via the shared scanner.
- tests.test_cli_wrapper.test_wrap_man_drills_into_subcommand(runner, custom_cls_script)[source]¶
Extra arguments after SCRIPT render the nested subcommand’s page.
- tests.test_cli_wrapper.test_wrap_man_output_dir_writes_tree(runner, custom_cls_script, tmp_path)[source]¶
wrap --help-format man --output-dirwrites one .1 per (sub)command.--output-dirmust appear before SCRIPT, because wrap runs withallow_interspersed_args=Falseso that anything after SCRIPT is treated as a sub-command path rather than a click-extra flag.
- tests.test_cli_wrapper.test_wrap_man_output_dir_creates_missing_directory(runner, greet_script, tmp_path)[source]¶
--output-dircreates the target dir when it does not exist yet.
- tests.test_cli_wrapper.test_wrap_man_pages_reject_subcommand(runner, custom_cls_script, tmp_path, monkeypatch, destination)[source]¶
Writing man pages always emits the full tree; mixing in a SUBCOMMAND arg is rejected so the user cannot accidentally produce a tree of pages named after a partial path.
- tests.test_cli_wrapper.test_wrap_man_flag_refuses_a_destination(runner, greet_script, tmp_path)[source]¶
--manreads the manual: the roff to write somewhere is a format’s.
- tests.test_cli_wrapper.test_group_dispatches_to_wrap(runner, greet_script, args, expected)[source]¶
All invocation forms reach the target CLI.
- tests.test_cli_wrapper.test_group_options_work_with_wrap(runner, greet_script, group_opts)[source]¶
Default Group options are accepted alongside the wrap subcommand.
- tests.test_cli_wrapper.test_wrap_honors_group_theme(runner, greet_script, theme, styled_heading)[source]¶
A group-level
--themereaches the wrapped CLI’s help screen.ThemeOption records the pick on the shared context meta, but the wrapped target runs under its own fresh context; wrap must bridge the pick to the process default (through patch_click) for the target’s help to pick it up.
- tests.test_cli_wrapper.test_wrap_honors_theme_envvar(runner, greet_script, monkeypatch)[source]¶
CLICK_EXTRA_THEMEreaches a wrapped CLI’s help screen too.The group resolves the machine-wide variable like any other source of a palette, and wrap bridges whatever it settled on to the process default the wrapped target renders under.
- tests.test_cli_wrapper.test_wrap_pins_group_color_on_every_target_kind(runner, request, script_fixture, color_opt, colored)[source]¶
The group’s tri-state color decision reaches the wrapped target.
Both kinds of target must obey it: one built by the patched
@click.command()decorator (so it is a_HelpColorsMixin) and one carrying an explicitcls=(so it stays plain Click). They travel different code paths insidepatch_click, so each direction of the flag is checked on both.The runner is left on its own auto-detection here: forcing colors at the stream level would paint the output whatever the flag decided.
- tests.test_cli_wrapper.test_group_known_subcommands_not_wrapped(runner, subcommand)[source]¶
Known demo subcommands are dispatched directly, not to wrap.
- tests.test_cli_wrapper.test_cli_startup_leaves_the_test_tooling_alone()[source]¶
Loading the CLI imports neither the test-suite engine nor
click.testing.Only the
test-suitecommand needs them, andclick.testingdragspdbandasyncioin behind it: every other command would pay for them at startup.
- tests.test_cli_wrapper.test_unpatch_click_restores_the_default_theme(patch_themes)[source]¶
Unpatching hands back the process-wide theme patching replaced.
The default in place before the patch is not the built-in
darkone, so a restore to the built-in default fails here. A second patch must not save the theme of the first as the one to restore.
- tests.test_cli_wrapper.test_unpatch_click_keeps_a_default_theme_it_did_not_replace()[source]¶
A patch that sets no theme leaves the default theme alone on unpatching.
A default set while patched therefore outlives the patch.
- tests.test_cli_wrapper.test_config_verbosity(runner, greet_script, create_config)[source]¶
verbosity = "DEBUG"in pyproject.toml activates debug logging.
- tests.test_cli_wrapper.test_config_group_theme(runner, greet_script, create_config)[source]¶
A
[tool.click-extra]themekey sets the help-screen theme.
- tests.test_cli_wrapper.test_config_target_string(runner, greet_script, create_config)[source]¶
A string config value is forwarded as
--key value.
- tests.test_cli_wrapper.test_config_target_bool_true(runner, weather_script, create_config)[source]¶
A
trueconfig value is forwarded as--flag.
- tests.test_cli_wrapper.test_config_target_bool_false_is_noop(runner, weather_script, create_config)[source]¶
A
falseconfig value is skipped: the flag is simply not passed.
- tests.test_cli_wrapper.test_config_target_multiple_keys(runner, weather_script, create_config)[source]¶
Multiple config keys are all forwarded.
- tests.test_cli_wrapper.test_config_target_cli_overrides(runner, greet_script, create_config)[source]¶
Explicit CLI args override config target defaults.
- tests.test_cli_wrapper.test_config_target_wrong_section_ignored(runner, greet_script, create_config)[source]¶
Config for a different script name has no effect.
- tests.test_cli_wrapper.test_config_target_empty_section(runner, greet_script, create_config)[source]¶
An empty target section produces no extra args.
- tests.test_cli_wrapper.test_config_target_no_config(runner, greet_script)[source]¶
No config file at all: target runs with its own defaults.
- tests.test_cli_wrapper.test_config_target_invalid_option(runner, greet_script, create_config)[source]¶
An invalid config key is caught by the target CLI.
- tests.test_cli_wrapper.test_config_args_no_wrap_section()[source]¶
Config exists but has no wrap section.
- tests.test_cli_wrapper.test_wrap_help_format_describes_a_foreign_cli(runner, greet_script, help_format, expected)[source]¶
A CLI that never heard of Click Extra is rendered from the outside.
Same posture as
--paramsand--man: the target is loaded and walked, never asked to cooperate, so machine-readable help needs no opt-in from the author of the wrapped CLI.
- tests.test_cli_wrapper.test_wrap_help_format_json_is_parseable(runner, greet_script)[source]¶
The JSON rendering of a foreign CLI parses, and carries its options.
- tests.test_cli_wrapper.test_wrap_help_format_stays_colorless(runner, greet_script)[source]¶
Machine-readable output carries no ANSI, whatever the group asked for.
Every format here is meant to be piped into a parser, which has no use for escape codes:
--color=alwayspaints the help screen, not the export.
- tests.test_cli_wrapper.test_wrap_install_writes_where_the_consumer_looks(runner, greet_script, tmp_path, monkeypatch, help_format, artifact)[source]¶
--installputs each rendering where the tool reading it looks.One destination flag across both formats, each module resolving its own canonical directory: Carapace’s spec directory, and the user’s man directory. Both honor their XDG variable at call time.
- tests.test_cli_wrapper.test_wrap_destination_refused_for_documents(runner, greet_script, help_format)[source]¶
A document has nowhere canonical to be installed, and says so.
- tests.test_cli_wrapper.test_target_prog_name_is_what_a_user_would_type(script, expected)[source]¶
Every rendering is titled with the name the target runs under.
- tests.test_cli_wrapper.test_every_rendering_agrees_on_the_target_name(runner, tmp_path)[source]¶
One rule, so the man page, spec, Markdown and tree cannot disagree.
The script file is named after neither its command object nor the directory holding it, so a renderer picking the wrong source shows it.
tests.test_color module¶
- tests.test_color.test_standalone_color_option(invoke, option_decorator, param, expecting_colors, assert_output_regex)[source]¶
Check color option values, defaults and effects on all things colored, including verbosity option.
- tests.test_color.test_no_color_env_convention(invoke, env, env_vote, tty, param, param_expect_colors)[source]¶
Each color variable is read by its own convention, on a terminal and in a pipe.
A vote of
Nonemust leave the decision to the output stream: colored on a terminal, plain in a pipe. The pipe case is what tells a variable casting no vote apart from one forcing color.
- tests.test_color.test_resolve_color_env_term(monkeypatch, term, expected)[source]¶
A dumb/unknown TERM votes color-off, while any other value stays neutral.
- tests.test_color.test_is_dumb_terminal(monkeypatch, term, expected)[source]¶
Case does not matter, and an unset
TERMis no dumb terminal.
- tests.test_color.test_renamed_color_option_reads_the_color_envvars(invoke)[source]¶
The color variables reach a color option under another name.
- tests.test_color.test_resolve_color_env_force_color_beats_dumb_term(monkeypatch, term)[source]¶
An explicit FORCE_COLOR stays authoritative over a dumb/unknown TERM.
- tests.test_color.test_color_synonym_cli_resolution(invoke, when, ctx_color)[source]¶
Every canonical value and GNU synonym resolves –color to the right state.
- tests.test_color.test_color_synonym_invalid_value(invoke, when)[source]¶
Unknown values, including the git-style true/false, still error. The message lists only the canonical choices, never the hidden synonyms.
The GNU synonyms are accepted but never advertised: –help shows only the canonical metavar.
- tests.test_color.test_color_synonym_cli_beats_env(invoke, when, env_var, ctx_color)[source]¶
A synonym on the command line keeps the same env precedence as its canonical twin: a command-line choice outranks the color environment variables.
- tests.test_color.test_color_synonym_config_string(invoke, create_config, when, ctx_color)[source]¶
A configuration file accepts the GNU synonyms as strings, normalized exactly like on the command line.
- tests.test_color.test_color_synonym_config_boolean(invoke, create_config, filename, raw, ctx_color)[source]¶
A configuration boolean maps true -> always and false -> never, including YAML’s coercion of yes/no/on/off, so a value means the same across formats.
- tests.test_color.test_color_synonym_config_beats_env(invoke, create_config, rhs, env_var, ctx_color)[source]¶
A config synonym or boolean beats the color environment variables, matching the documented precedence of canonical config values.
- tests.test_color.test_integrated_color_option(invoke, param, expecting_colors, ctx_color, assert_output_regex)[source]¶
Check effect of color option on all things colored, including verbosity option.
Also checks the color option in subcommands is inherited from parent context.
- tests.test_color.test_color_settles_before_eager_help_and_version(invoke, args, expecting_colors)[source]¶
–color / –no-color colorize the eager –help and –version screens whatever their position on the command line.
Click processes eager options in command-line order, so a –color sitting after –help or –version would otherwise pin
ctx.coloronly once the screen had already printed and exited.Command.parse_argssettles the color options in a pre-pass to close that gap. SeeCommand._resolve_presentation_eagerly.
- tests.test_color.test_cli_color_envvars_reach_eager_screens(invoke, screen, env, tty, expecting_colors)[source]¶
<CLI>_COLORand<CLI>_NO_COLORcolor the--helpand--versionscreens.Neither puts a flag on the command line, so the presentation pre-pass has to be triggered by the variables themselves, as it is for
<CLI>_THEME.
- tests.test_color.test_forced_color_sets_and_restores_env(monkeypatch)[source]¶
forced_colorforcesFORCE_COLORand clears Click Extra’s disabling vars.Inside the context the capture sees
FORCE_COLOR=1with every flag that would disable color (NO_COLOR,LLM, …) removed; on exit the prior environment, including any pre-existing values, is restored untouched.
- tests.test_color.test_resolve_background(monkeypatch, env, expected)[source]¶
Environment-variable precedence for the dark/light background decision.
- tests.test_color.test_terminal_background_names_the_theme_of_its_shade()[source]¶
A shade selects the built-in theme of the same name, as a plain string does.
- tests.test_color.test_resolve_background_query_is_opt_in(monkeypatch)[source]¶
The OSC 11 query runs only when
query_backgroundis set, below env vars.
- tests.test_color.test_resolve_background_query_below_explicit_env(monkeypatch)[source]¶
An explicit env signal outranks the live query even when querying is allowed.
- tests.test_color.test_parse_osc_rgb(response, expected)[source]¶
An OSC 11 reply is parsed into an 8-bit RGB tuple, or None when malformed.
- tests.test_color.test_is_dark_rgb(rgb, is_dark)[source]¶
Perceived-lightness classification of background colors.
- tests.test_color.test_is_a_tty()[source]¶
The probe reads
isatty, guarding streams that do not expose it.- Return type:
- tests.test_color.test_query_osc_background_without_tty(monkeypatch)[source]¶
The OSC query is a no-op when stdin or stdout is not a terminal.
- tests.test_color.test_query_osc_background_without_streams(monkeypatch)[source]¶
A detached process (no __stdin__/__stdout__) cannot query the terminal.
- tests.test_color.test_query_osc_background_pty(monkeypatch)[source]¶
A full OSC 11 round-trip over a pseudo-terminal yields the parsed color.
- tests.test_color.test_invocation_color_reaches_background_threads(invoke, args, expected)[source]¶
The resolved color tri-state is mirrored process-wide, so a background thread (which has no reachable Click context) can still honor it; the mirror resets once the invocation closes.
- tests.test_color.test_resilient_context_does_not_publish_invocation_color(monkeypatch)[source]¶
Building an introspection context must leave the color mirror untouched.
A resilient context is never closed, so publishing from one would pin the process-wide mirror to the build environment’s NO_COLOR, stripping ANSI from the output of any later CLI carrying no color option of its own.
- tests.test_color.test_invocation_color_never_outlives_its_invocation(invoke, args)[source]¶
However an invocation ends, it must leave the color mirror on auto.
publish_invocation_color()queues its reset as a context-close callback, so an option callback raising during parameter processing aborts before the context is entered and the reset never fires. The mirror then stays pinned to that run’s--no-colorfor the rest of the process, stripping ANSI from any later CLI carrying no color option of its own.
- tests.test_color.test_invocation_color_expires_with_a_plain_click_command(invoke)[source]¶
A plain
click.commandborrowing the color options must not leak either.It has no
ExtraCommand.main()to reset the mirror in afinally, so the cleanup rides the context finalizer. Click’sUsageErrorholds that context inside a reference cycle, so the collection is forced here rather than waited on: in a running program ordinary allocation churn reaches it on its own.
- tests.test_color.test_a_stale_finalizer_does_not_clobber_a_live_invocation()[source]¶
The finalizer of a dead context must leave a newer invocation’s value alone.
Tying the reset to the context’s lifetime introduces this hazard: a context collected after the next invocation published would otherwise wipe a value that is still in use. The token comparison is what prevents it.
tests.test_command_doc module¶
- tests.test_command_doc.test_no_rewrap_marker_becomes_no_fill()[source]¶
Click’s
\bmarker must produce a roff.nf/.fiblock (click-man #9).
- tests.test_command_doc.test_no_rewrap_marker_does_not_leak_into_surrounding_paragraphs()[source]¶
A
\bparagraph must not switch the whole DESCRIPTION to preformatted mode: prose before and after stays filled, only the marked paragraph lands between.nf/.fi.
- tests.test_command_doc.test_name_line_keeps_full_short_help()[source]¶
The NAME
.SHline carries the canonical description without Click’s 45-char terminal truncation.
- tests.test_command_doc.test_inline_literal_in_short_help_renders_as_bold()[source]¶
Inline reST literals (
..) in the first docstring line land on the NAME.SHline as\fB..\fR, not as raw backticks (which mandoc renders as quote characters).
- tests.test_command_doc.test_inline_literal_in_description_renders_as_bold()[source]¶
Inline reST literals in the description body become
\fB..\fRso mandoc’s HTML output shows them in bold rather than wrapped in Unicode quote characters.
- tests.test_command_doc.test_boolean_flag_renders_both_spellings()[source]¶
--ascii/--no-asciimust both appear (click-man #41).
- tests.test_command_doc.test_option_groups_become_subsections()[source]¶
An explicit option group renders as a roff
.SSsubsection of OPTIONS, with the ungrouped remainder gathered underOther options.
- tests.test_command_doc.test_default_option_groups_close_the_options_section(subject, own_section)[source]¶
The sections of
DEFAULT_OPTION_GROUPStrail a command’s own options.They land past the ungrouped remainder, in the order the taxonomy declares, matching what
--helpdraws.
- tests.test_command_doc.test_ungrouped_command_has_no_subsections()[source]¶
A command carrying no option group at all keeps a flat OPTIONS list.
- tests.test_command_doc.test_dynamic_subcommand_discovered()[source]¶
Dynamically-resolved subcommands must be generated (click-man #14 / #56).
- tests.test_command_doc.test_environment_section_deduplicated()[source]¶
A shared env var (
--config/--no-config) appears only once.
- tests.test_command_doc.test_authors_omitted_without_metadata()[source]¶
No distribution matches the command name, so AUTHORS is dropped rather than synthesized from a fallback.
- tests.test_command_doc.test_man_option_reads_the_manual()[source]¶
--mantypesets the page for reading, the waymanitself does.
- tests.test_command_doc.test_strip_overstrike(text, expected)[source]¶
Bold and underline pairs both collapse to their plain character.
- tests.test_command_doc.test_find_typesetters_fills_each_installed_one(monkeypatch)[source]¶
Each installed program yields its resolved path and its filled arguments.
- tests.test_command_doc.test_man_option_falls_back_to_the_source(monkeypatch)[source]¶
With no typesetter installed, the source beats an error.
- tests.test_command_doc.test_accessible_manual_drops_overstrike()[source]¶
Accessible mode strips the emphasis a screen reader would voice as noise.
- tests.test_command_doc.test_generated_roff_passes_groff_lint(cli, prog_name)[source]¶
Every generated page must parse cleanly under groff (no warnings).
forecastexercises the.SSoption-group subsections.
- tests.test_command_doc.test_render_help_rejects_unknown_format()[source]¶
An unknown format names the ones that exist instead of failing blankly.
- tests.test_command_doc.test_help_formats_describe_every_format()[source]¶
Every
HelpFormathas a description, and members run alphabetically.
- tests.test_command_doc.test_render_help_takes_a_member_or_its_value(help_format)[source]¶
A
HelpFormatmember and its string value render the same document.
- tests.test_command_doc.test_render_help_names_carapace_spec_after_prog_name(prog_name, spec_name)[source]¶
A passed
prog_namenames a Carapace spec, over the command’s own name.
- tests.test_command_doc.test_json_carries_every_section()[source]¶
The JSON document covers what the man page covers, as native types.
- tests.test_command_doc.test_json_carries_choices_a_short_metavar_hides()[source]¶
A consumer reads the accepted values off
choices, never off the metavar.An option overriding the metavar with a short placeholder leaves nothing to parse in
metavar, which is the whole reason the key is separate.
- tests.test_command_doc.test_json_reports_no_choices_for_an_open_type()[source]¶
choicesisNonefor a type that enumerates nothing.
- tests.test_command_doc.test_a_short_metavar_lists_its_values_in_the_render(help_format)[source]¶
Shortening the metavar moves the values below the help, never drops them.
- tests.test_command_doc.test_a_default_choice_metavar_does_not_repeat_its_values(help_format)[source]¶
A
[a|b]metavar already shows them, so nothing is appended below it.
- tests.test_command_doc.test_every_enumerated_default_option_reaches_the_render(help_format)[source]¶
No default option hides its accepted values behind a short metavar.
--table-formatand--export-configboth trade the[a|b|c]metavar for a one-word placeholder to keep the help screen readable. That is a help-screen decision, and it must not reach the man page or the Markdown render, which are what a reader consults precisely to learn what a value may be.
- tests.test_command_doc.test_json_lists_subcommands_by_name_only()[source]¶
Progressive disclosure: children are named, never recursively expanded.
- tests.test_command_doc.test_subcommand_aliases_reach_every_backend()[source]¶
A generated page names the short spelling the help screen advertises.
An alias is invocable, so a reader who only ever sees the man page or the JSON export would otherwise never learn it exists.
- tests.test_command_doc.test_json_full_walks_the_whole_tree()[source]¶
The -full variant is the one that expands every command of the tree.
- tests.test_command_doc.test_markdown_renders_every_section()[source]¶
The Markdown document carries the same sections as headings.
- tests.test_command_doc.test_markdown_full_walks_the_whole_tree()[source]¶
Every command of the tree gets its own title in one document.
- tests.test_command_doc.test_no_rewrap_marker_keeps_its_shape_in_markdown()[source]¶
A
\bregion is fenced rather than reflowed into a paragraph.
- tests.test_command_doc.test_markdown_escapes_html_characters_outside_code_spans()[source]¶
Help prose shows as written, where Markdown would read it as HTML.
A browser shows nothing for
<guests>, which Markdown takes for a tag. A code span keeps its content as written, whether a reST literal made it or the author wrote it.
- tests.test_command_doc.test_examples_render_in_every_backend()[source]¶
One
examples=declaration feeds the help screen, roff, Markdown and JSON.
- tests.test_command_doc.test_command_without_examples_grows_no_section()[source]¶
A command declaring no example renders no section for them.
- tests.test_command_doc.test_malformed_examples_fail_at_construction(malformed)[source]¶
A bad pair surfaces on import, not on the first –help a user runs.
- tests.test_command_doc.test_dynamic_help_is_extracted()[source]¶
Options computing their help from the context are not left blank.
-v/-qleaveOption.helpat None and build their sentence inget_help_record(). Every backend here reads the extracted model, so the text has to be resolved once, at extraction.
- tests.test_command_doc.test_registered_name_names_the_page()[source]¶
A subcommand registered under another name is documented under that name.
The COMMANDS section lists
predict, so the page a reader looks for isweather-predict.1, and its NAME line saysweather predict: the name the parent registered wins over the command’s own.
- class tests.test_command_doc.Fruit(*values)[source]¶
Bases:
EnumChoices an option can take as
Enummembers.- APPLE = 'apple'¶
- BANANA = 'banana'¶
- tests.test_command_doc.test_enum_choices_are_spelled_as_click_completes_them(help_format, expected)[source]¶
An
Enumchoice lists its member names, the spelling Click accepts.str()of a member readsFruit.APPLE, which no invocation can type. The short metavar hides the values, so the render adds them back and shows which spelling the document carries.
- tests.test_command_doc.test_install_manpages_resolves_home_at_call_time(tmp_path, monkeypatch)[source]¶
The pages land under the
HOMEin force at the call, not at import.With no
XDG_DATA_HOMEset, the default directory derives from the home directory. A test fixture or a packaging script pointingHOMEelsewhere for one invocation is honored, which a path expanded at import cannot do.
tests.test_commands module¶
Test defaults of our custom commands, as well as their customizations and attached options, and how they interact with each others.
- tests.test_commands.test_module_root_declarations()[source]¶
Verify
click_extra.__all__is a superset of click and cloup.Sort order is enforced by
ruff(RUF022).
- tests.test_commands.test_deprecated_click_exports_are_forwarded(name)[source]¶
Click’s deprecated names resolve to Click’s objects, never bound eagerly.
A binding would bypass Click’s deprecation shim, and fetching it at import time would warn in every program importing click-extra. Whether Click warns on access depends on its version, so only the forwarding is checked.
- tests.test_commands.test_import_raises_no_deprecation_warning(statement)[source]¶
Importing the package sets off no deprecation warning.
Cloup lists Click’s deprecated names in its
__all__, so a plain star import of cloup fetches each of them, and Click warns once per name. A star import of click-extra would do the same for any such name its own__all__declared.Runs on every cell of the matrix, not as a
oncetest: a deprecation shows first on the cells tracking the development branches of Click and Cloup, like the warning Click raises when a class is created with an override of a member it retired.
- tests.test_commands.test_public_namespace_integrity()[source]¶
The package namespace and
__all__agree: nothing missing or extra.click ships no
__all__, so its star import would otherwise leak every submodule bound by click’s__init__(click.core,click.globals, …) into the package namespace, handing out click’s un-enhanced classes and shadowing theglobalsbuiltin. Those bindings are scrubbed at the end ofclick_extra/__init__.py: verify the scrub held, that every public binding is declared in__all__, and that every declared name resolves (eagerly, lazily, or as one of our own submodules).
- tests.test_commands.test_no_debugger_ballast_on_import()[source]¶
Importing the package must not load the test tooling nor the debugger stack.
click_extra.testingimportsclick.testing, whose module-levelimport pdbcascades into asyncio and the_pyreplmachinery on Python 3.13+. Loaded eagerly, that chain ends up bundled into every Nuitka-compiled CLI binary, so the test tooling is exported lazily: check from a pristine interpreter that none of it leaks at import time.
- tests.test_commands.CLOUP_METADATA_PROBE_VERSION = (4, 0, 0)¶
Last cloup release importing
importlib.metadataat module scope.Up to this release, cloup reads Click’s version at import time, which loads
importlib.metadataand theemailtree behind it. janluke/cloup#242 removes that probe, so any later cloup keeps both off the startup path.
- tests.test_commands.test_no_heavy_dependency_ballast_on_startup()[source]¶
Importing the package and rendering a
--helpmust not load the dependencies the package defers.Each module below is imported where it is used rather than at module scope, so that a CLI pays for it only on the code path that needs it: see the deferred-imports house rule in
AGENTS.md. The probe runs a hello-world through decoration and--helpin a pristine interpreter, since pytest itself has most of these loaded already.importlib.metadataand theemailtree it drags in join the list aboveCLOUP_METADATA_PROBE_VERSIONonly: up to that release, cloup imports them at module scope itself.
- tests.test_commands.test_import_module_budget()[source]¶
import click_extrastays within a module-count budget.A coarse net under the denylist above: a new eager import of a dependency with a deep tree shows up here as a jump of tens of modules, whichever package it comes from. Measured at 264 modules on Python 3.14 and cloup 4.0.0; the budget leaves room for the platform and version differences of the stdlib.
- tests.test_commands.STARTUP_RATIO_TO_CLICK = 3.0¶
Most a hello-world
--helpof click-extra may take, as a multiple of Click’s.Measured at 2.4 on Python 3.14 with Click 8.5.0, for the
10.0.0release.
- tests.test_commands.HELLO_CLICK = 'import click\n\n\n@click.command()\n@click.option("--name", default="World", help="The person to greet.")\ndef hello(name):\n """Simple program that greets NAME."""\n click.echo(f"Hello, {name}!")\n\n\nif __name__ == "__main__":\n hello()\n'¶
The hello-world CLI the startup comparison times, written with Click.
- tests.test_commands.test_startup_time_stays_within_a_ratio_of_click(tmp_path)[source]¶
A hello-world
--helptakes at most a few times as long as Click’s own.Milliseconds depend on the runner, so the bound is relative: both CLIs run in alternation in fresh interpreters, and the best run of each is compared, as the one a loaded runner disturbs least. The children run without the color and coverage variables, which would add work to one side only.
- tests.test_commands.test_lazy_test_tooling_exports()[source]¶
The lazy test-tooling names resolve, cache, and show up in
dir().
- tests.test_commands.test_lazy_converter_exports()[source]¶
The converter names resolve on first access, off the startup path.
Checked in a pristine interpreter, since another test may have imported the converters already.
- tests.test_commands.LAZY_REGISTRIES = ('_LAZY_CAPTURES', '_LAZY_CONVERTERS', '_LAZY_DISPLAYS', '_LAZY_TEST_TOOLING')¶
The registries of names the package root serves through its
__getattr__.
- tests.test_commands.test_lazy_exports_are_declared_and_typed(registry)[source]¶
Each lazy name is in
__all__, typed, and resolves to its source object.A lazy name has no binding until its first access, so the
TYPE_CHECKINGimport inclick_extra/__init__.pyis what shows it to type checkers.
- tests.test_commands.test_lazy_export_modules_stay_off_startup()[source]¶
import click_extraloads none of the modules the lazy names live in.Checked in a pristine interpreter, since other tests have imported them.
- tests.test_commands.test_lazy_export_modules_are_visible_to_freezers()[source]¶
Each module a lazy name lives in has an import statement in a function.
Nuitka bundles what a live import statement names. It drops the
TYPE_CHECKINGblock as dead code, and cannot follow a computedimport_module()call, so a module missing here is missing from every binary built on click-extra, which then crashes on the lazy name.
- tests.test_commands.all_command_cli()[source]¶
A CLI that is mixing all variations and flavors of subcommands.
- tests.test_commands.test_short_option_error_enhancement(invoke, cli_options, args, exit_code, expected_fragment)[source]¶
Command.parse_argsimproves error messages for single-dash multi-character tokens whose first character is not a registered short option. Vanilla Click would split-dbgwrongcharacter by character and report “No such option: -d”; we re-raise with the full token and close-match suggestions instead.The enhancement must not interfere with valid
-abc-style combining or with the per-character diagnostic when a later character is unknown.Upstream context: https://github.com/pallets/click/issues/2779
- tests.test_commands.test_group_help(invoke, all_command_cli, param, exit_code, assert_output_regex)[source]¶
- tests.test_commands.test_help_eagerness(invoke, all_command_cli, params, exit_code, expect_help, expect_empty_stderr, assert_output_regex)[source]¶
See: https://click.palletsprojects.com/en/stable/click-concepts/#callback-evaluation-order
- tests.test_commands.test_help_custom_name(invoke)[source]¶
Removes the
-hshort option as we reserve it for a custom-h/--headeroption.See: https://github.com/kdeldycke/mail-deduplicate/issues/762
- tests.test_commands.test_subcommand_help(invoke, all_command_cli, cmd_id, param, assert_output_regex)[source]¶
- tests.test_commands.test_colored_bare_help(invoke, cmd_decorator, param)[source]¶
Extra decorators are always colored.
Even when stripped of their default parameters, as reported in: https://github.com/kdeldycke/click-extra/issues/534 https://github.com/kdeldycke/click-extra/pull/543
- tests.test_commands.test_duplicate_option(invoke)[source]¶
See: - https://kdeldycke.github.io/click-extra/commands.html#change-default-options - https://github.com/kdeldycke/click-extra/issues/232
- tests.test_commands.test_no_option_leaks_between_subcommands(invoke, assert_output_regex)[source]¶
As reported in https://github.com/kdeldycke/click-extra/issues/489.
- tests.test_commands.test_show_envvar_parameter(invoke, cmd_decorator, ctx_settings, expected_help)[source]¶
- tests.test_commands.test_show_choices_parameter(ctx_settings, expected)[source]¶
The show_choices context setting is forced on every option when set.
- tests.test_commands.test_raw_args(invoke)[source]¶
Raw args are expected to be scoped in subcommands.
- tests.test_commands.test_lazy_group(invoke, tmp_path, lazy_cmd_decorator, lazy_group_decorator)[source]¶
Test extends the snippet from Click documentation.
- tests.test_commands.write_produce_modules(tmp_path)[source]¶
Write the command modules the sectioned lazy-group tests import.
- tests.test_commands.test_lazy_group_sections(invoke, tmp_path)[source]¶
A
LazySubcommandfiles its command under the section it declares.
- tests.test_commands.test_lazy_group_lists_declared_help_without_importing(invoke, tmp_path)[source]¶
A lazy subcommand declaring its help is listed without being imported.
- tests.test_commands.test_lazy_group_keeps_a_listed_subcommand_in_place(invoke, tmp_path)[source]¶
Importing a subcommand listed by its declared help keeps its place.
- tests.test_commands.test_cli_help_imports_no_lazy_subcommand()[source]¶
The root help of the click-extra CLI lists its lazy subcommands unimported.
- tests.test_commands.test_cli_lazy_subcommand_lists_as_its_command(name)[source]¶
The help a lazy subcommand declares lists it the way the command does.
The declared text and the command’s own help are two copies of one sentence: this fails the moment they drift apart.
A
Sectioncan hold both eagerly and lazily registered subcommands.
- tests.test_commands.test_lazy_group_no_default_section(invoke, tmp_path)[source]¶
fallback_to_default_section=Falsehides a subcommand but keeps it invocable.
- tests.test_commands.test_lazy_subcommand_normalizes_bare_import_paths(tmp_path)[source]¶
A bare import path is normalized into a
LazySubcommand.
- tests.test_commands.test_decorator_overrides()[source]¶
Ensure our decorators are not just alias of Click and Cloup ones.
- tests.test_commands.test_decorator_cls_parameter(klass, should_raise)[source]¶
Decorators accept custom cls parameters.
- tests.test_commands.test_help_shows_group_help(invoke)[source]¶
mycli helpproduces the same output asmycli --help.
- tests.test_commands.test_help_shows_subcommand_help(invoke)[source]¶
mycli help greetmatchesmycli greet --help.
- tests.test_commands.test_help_nested_group(invoke)[source]¶
mycli help sub leafresolves through nested groups.
- tests.test_commands.test_help_nonexistent_subcommand(invoke)[source]¶
mycli help nosuchreports an error.
- tests.test_commands.test_help_subcommand_of_non_group(invoke)[source]¶
mycli help leaf deepererrors when leaf is not a group.
- tests.test_commands.test_help_disabled(invoke)[source]¶
help_command=Falsesuppresses auto-injection.
- tests.test_commands.test_help_user_override(invoke)[source]¶
User-defined
helpsubcommand replaces the auto-injected one.
- tests.test_commands.test_help_appears_in_listing(invoke)[source]¶
The
helpsubcommand is visible in the group’s command list.
- tests.test_commands.test_help_search(invoke)[source]¶
mycli help --search termfinds matching subcommands.
A hidden subcommand stays out of the search, as it stays out of
help.
- tests.test_commands.test_help_search_no_match(invoke)[source]¶
mycli help --search termwith no matches.
- tests.test_commands.test_help_in_all_command_cli(invoke, all_command_cli)[source]¶
The help subcommand works on the fixture CLI.
- tests.test_commands.test_help_for_subcommand_in_all_command_cli(invoke, all_command_cli)[source]¶
help default-subcommandworks on the fixture CLI.
- tests.test_commands.kitchen_group(**kwargs)[source]¶
A group whose subcommands are declared out of alphabetical order.
- tests.test_commands.listed_subcommands(help_screen)[source]¶
Names listed under the
Commands:heading of a help screen.
- tests.test_commands.test_subcommand_order_in_help_screen(invoke, group_kwargs, expected)[source]¶
Cloup renders the help screen from sections, never from
list_commands().
- tests.test_commands.test_subcommand_order_in_list_commands(group_kwargs, expected)[source]¶
The flat listing feeding
--tree, man pages, specs and completion.
- tests.test_commands.test_subcommand_order_agrees_across_renderers(invoke)[source]¶
Every rendering of a command tree lists subcommands in the same order.
A renderer reaching for its own accessor would let a group’s help screen disagree with its man page or its completion spec. They all go through
list_commands(), and this pins that down for the whole population rather than one renderer at a time.
- tests.test_commands.test_iter_subcommand_contexts_builds_a_context_per_subcommand()[source]¶
Each subcommand comes with a context named after it, under its parent’s.
- tests.test_commands.ghost_pantry()[source]¶
A lazy group also listing a name it cannot resolve, like a broken plugin.
- tests.test_commands.test_tree_walks_agree_on_lazy_and_unresolvable_subcommands(invoke, tmp_path)[source]¶
Every walk of a command tree imports a lazy subcommand and skips a dead name.
A group can list a name it cannot resolve, like a plugin whose module broke. The walks all go through
iter_subcommands, so none of them crashes on that name, and none drops a lazy subcommand it has not imported yet. Each walk gets a fresh group, so no walk finds the subcommand imported by another.
- tests.test_commands.test_option_order_agrees_across_renderers(invoke)[source]¶
Every rendering of a command lists options in the same order.
Cloup draws the ungrouped section last, and Click Extra sends its own groups past it, so a renderer reaching for its own accessor would let a help screen disagree with its man page or its completion spec. They all resolve the section order through
split_option_groups().Pins the taxonomy too: each rendering is filtered down to the flags
DEFAULT_OPTION_GROUPSdeclares, so the order they are declared in is the order a reader sees.
- tests.test_commands.test_argument_help_agrees_across_renderers(invoke, argument_decorator)[source]¶
An argument’s help reaches every rendering, whatever its
Argumentclass.The help screen, the man page and the JSON export all resolve an operand’s description through the same helper, so a plain
click.argumentcarries the same text everywhere.
- tests.test_commands.test_deprecated_command_label_matches_click(invoke, deprecated, label)[source]¶
A deprecated command carries Click’s own marker, reason string included.
The marker is checked on the help screen and on
full_short_help(), which feeds--tree, the man page, the JSON export and the completion specs.
- tests.test_commands.test_full_short_help_drops_the_no_rewrap_marker()[source]¶
A first paragraph opening on Click’s
\bloses the marker, as in Click.full_short_help()feeds--tree, the man page, the completion specs and theclick:treesummary table: a stray\x08there moves the terminal cursor back one column, and turns the man page entry into a no-fill block.
- tests.test_commands.test_every_default_option_lands_in_a_section()[source]¶
No option
default_params()returns escapesDEFAULT_OPTION_GROUPS.An option no section claims stays ungrouped, which draws it in the command’s own
Optionsblock where it reads as one the CLI author declared. Nothing else reports that: the help screen renders, and every cross-renderer check still agrees, because they all read the same wrong layout.
- tests.test_commands.test_default_option_groups_name_no_stale_flag()[source]¶
Every flag
DEFAULT_OPTION_GROUPSdeclares is onedefault_params()returns.A renamed or dropped option otherwise leaves a dead entry behind. And a flag landing in two sections is silently taken by the later one, since
_assign_option_groupswrites them in order.
- tests.test_commands.test_option_swapped_by_a_params_hook_keeps_its_section()[source]¶
An option a
paramshook replaces stays in the section of the original.default_paramsfiles the instances it builds, so a replacement built after that call carries none, and cloup draws it in the command’s ownOptionsblock where it reads as one the CLI author declared. Each default option is swapped in turn, sections being declared one flag at a time: an option losing its own would pass unseen while its siblings still hold theirs.
- tests.test_commands.test_params_hook_runs_once_per_decorated_command()[source]¶
A
paramshook builds one set of options per command, and never shares it.A decorator built once and applied twice still gives each command its own option instances, the second application calling the hook again.
- tests.test_commands.test_lazy_group_subcommand_order_is_stable_across_loading(tmp_path, monkeypatch)[source]¶
A lazy subcommand holds its slot before and after it is imported.
Importing appends the command to
self.commands, so registration order read off that dictionary alone would reshuffle mid-run.
- tests.test_commands.test_lazy_group_defaults_to_alphabetical_order()[source]¶
Sorting moved from
__init__to listing time, with the same result.
- tests.test_commands.test_option_priorities_leave_processing_order_alone(invoke)[source]¶
The help screen reorders while
paramsand the callbacks do not.click.core.iter_params_for_processingbreaks eager-option ties on declaration order, which is why--timemeasures everything and--accessiblelowers the--colordefault before it resolves. Reordering the help screen must not disturb any of that.
- tests.test_commands.test_option_priorities_match_flags_then_destination()[source]¶
A flag pair sharing one destination stays addressable one flag at a time.
- tests.test_commands.test_option_priorities_never_reorder_positional_arguments()[source]¶
Argument order is part of the command’s grammar, not of its presentation.
- tests.test_commands.test_command_listing_is_not_cut_by_an_abbreviation()[source]¶
A subcommand’s line in its parent’s list holds its whole first sentence.
Click ends the listing at the first word closing on a period, so an abbreviation mid-sentence (
vs.,e.g.,etc.) cuts it into a fragment:gradientonce read “Render 24-bit RGB gradients vs.”, which says nothing.The guard is narrower than “the listing reads well”: it only catches a cut landing before the first sentence ends, which is the one failure a docstring can cause without anyone noticing.
- tests.test_commands.test_no_help_screen_leaks_a_no_rewrap_marker()[source]¶
A
\bmarker never reaches the rendered help of any command.Click strips the marker only where it opens a paragraph, and paragraphs are split on blank lines. An epilog writing one between two indented blocks without a blank line around it therefore ships the raw backspace character: a terminal hides it, an HTML page does not.
tests.test_config module¶
- tests.test_config.DOCS_CONFIG_PAGE = PosixPath('/home/runner/work/click-extra/click-extra/docs/config-discovery.md')¶
The documentation page transcribing part of
ConfigFormat.
- tests.test_config.FULL_SEARCH_FLAGS = 285504¶
All search flags
ConfigOptionforces on, used as the baseline in tests.
- tests.test_config.NO_DOTGLOB_FLAGS = 285440¶
FULL_SEARCH_FLAGSminusDOTGLOB, to exercise the dotfile warnings.
- tests.test_config.SQLITE_DATA = {'config-cli1': {'default': {'int_param': 3, 'random_stuff': 'will be ignored'}, 'dummy_flag': True, 'my_list': ['pip', 'npm', 'gem']}}¶
The shared reference configuration, as
SQLITE_CONFIG_TABLErows.Keys are dotted parameter paths, values are JSON-encoded. This mirrors
TOML_DATA, minus the verbosity bump and the sections the other formats use to exercise their own quirks.
- tests.test_config.flatten_sqlite_keys(data, prefix='')[source]¶
Flatten a nested mapping into dotted keys, the SQLite config layout.
- Return type:
- tests.test_config.make_sqlite_config(path, data=None, *, create_table=True)[source]¶
Write a nested mapping into a SQLite configuration database.
Skips the calling test on a Python whose SQLite bindings are missing, the same interpreter on which
ConfigFormat.SQLITEreports itself disabled.- Return type:
- tests.test_config.test_unset_conf_debug_message(invoke, simple_config_cli, assert_output_regex)[source]¶
- tests.test_config.test_conf_chosen_formats_displayed(invoke, file_format_patterns, expected_pattern)[source]¶
A format set chosen by the developer is displayed in full.
Only an inherited set collapses to its folder, so the help screen keeps showing the effect of
file_format_patterns, which is whatdocs/config-discovery.mddemonstrates.
- tests.test_config.test_conf_show_file_patterns(invoke, kwargs, shows_patterns)[source]¶
show_file_patternsoverrides the display in both directions.
- tests.test_config.test_conf_default_url_displayed_as_given(invoke, url)[source]¶
A URL default keeps the double slash after its scheme in the help screen.
- tests.test_config.test_conf_unparsable(invoke, simple_config_cli, create_config)[source]¶
Explicit –config pointing to a file with garbage content.
- tests.test_config.test_conf_blank_source(invoke, simple_config_cli, create_config, filename, content)[source]¶
Explicit –config with nothing to read loads an empty configuration.
/dev/null(nulon Windows) is the Unix way to point a tool at no configuration. An empty file means the same, even where its format calls it invalid, like JSON, and where its parser reads the file from its path, like plist and SQLite. Every parameter then keeps its default.
- tests.test_config.test_conf_parses_to_nothing(invoke, simple_config_cli, create_config, filename, content, label)[source]¶
Explicit –config parsing to nothing names its format, not a parse error.
- tests.test_config.test_conf_blank_file_never_shadows_a_configuration(invoke, tmp_path, explicit)[source]¶
A blank file loses to a configuration the same pattern matches after it.
- tests.test_config.test_conf_named_pipe(invoke, simple_config_cli, tmp_path)[source]¶
A named pipe is read like a file: its content is the configuration.
- tests.test_config.test_standalone_no_config_option(invoke)[source]¶
@no_config_option cannot work without @config_option.
- tests.test_config.test_strict_conf(invoke, create_config, conf_text, expect_error)[source]¶
Strict mode rejects unknown params but accepts clean configs.
- tests.test_config.test_kebab_case_keys(invoke, create_config)[source]¶
Kebab-case config keys reach the snake_case-named CLI parameters.
- tests.test_config.test_kebab_case_spelling_collision(invoke, create_config)[source]¶
Both spellings of the same key: last one wins, a warning names both.
- tests.test_config.test_strict_conf_ignores_foreign_sections(invoke, create_config)[source]¶
Other tools’ sections in a shared config file do not trip strict mode.
- tests.test_config.test_command_forwards_config_strict(invoke, create_config)[source]¶
@command(config_strict=True) activates strict mode on the default option.
- tests.test_config.test_command_forwards_config_cascade(cascade)[source]¶
@command(config_cascade=…) sets the cascade of the default option.
- tests.test_config.test_command_excluded_params_additive(invoke, create_config)[source]¶
@command(excluded_params=…) extends the default blocklist.
The forwarded exclusion applies on top of the built-in ones, and a blocked parameter found in a config file is reported as blocked, not unknown.
- tests.test_config.test_export_config_includes_unset_params(invoke)[source]¶
Parameters without a default are exported instead of silently dropped.
TOML has no null type so unset parameters are commented out; multi-value parameters read as empty lists; JSON renders unset parameters as null.
- tests.test_config.test_export_config_includes_unset_params_of_a_plain_click_command(invoke)[source]¶
A plain Click command exports a parameter without a default as null too.
Such a command does not capture its raw arguments, so the export reads each parameter’s default instead, which Click leaves as its
UNSETsentinel.
- tests.test_config.test_serialize_toml_with_unset_layout(tree, expected)[source]¶
A commented-out key sits under the header of its own table.
A table with no key of its own keeps its header, else its commented-out keys read as keys of the table above.
- tests.test_config.test_export_config_reads_a_repeatable_flag_as_a_toggle(invoke)[source]¶
A repeatable boolean flag exports as the boolean a configuration spells.
It collects occurrences of a toggle rather than values, so dumping an unset one as the empty list of a multi-value parameter advertises a shape no user writes. A repeatable option taking values still reads as a list.
- tests.test_config.test_export_config_skips_an_opaque_subtree(invoke)[source]¶
A subcommand sharing a name with an opaque schema field is not exported.
The loader hands the whole sub-tree to the app’s own validator, so the subcommand’s options cannot be read back from there. Exporting them writes a file the same loader refuses.
- tests.test_config.test_export_config_kebab_case_keys(invoke, tmp_path)[source]¶
Exported keys use the kebab-case spelling, the canonical form for files.
Either spelling loads back to the same parameter, so the kebab-cased export still round-trips through –config.
- tests.test_config.test_introspection_flags_load_config_first(invoke, create_config)[source]¶
–params and –export-config reflect the config file regardless of the order in which Click processes the eager options.
Click processes eager parameters given on the command line ahead of eager parameters left at their defaults, so these flags used to render before the configuration file was discovered and loaded.
- tests.test_config.test_conf_file_overrides_defaults(invoke, simple_config_cli, create_config, httpserver, conf_name, conf_text, conf_data, assert_output_regex)[source]¶
- tests.test_config.test_auto_envvar_conf(invoke, simple_config_cli, create_config, httpserver, conf_name, conf_text, conf_data)[source]¶
- tests.test_config.test_conf_file_overridden_by_cli_param(invoke, simple_config_cli, create_config, httpserver, conf_name, conf_text, conf_data)[source]¶
- tests.test_config.test_config_status_line_follows_verbosity(invoke, create_config, before, after, shown)[source]¶
--configannounces the file it loads, unless the command line asks for less than the default verbosity, whatever the position of the verbosity flags.
- tests.test_config.test_no_config_status_line_follows_verbosity(invoke, args, shown)[source]¶
--no-configannounces it skips loading, unless asked for quiet output.
- tests.test_config.test_conf_metadata(invoke, create_config, httpserver, conf_name, conf_text, conf_data)[source]¶
- tests.test_config.test_conf_metadata_no_config(invoke)[source]¶
ctx.meta entries are not set when –no-config skips loading.
- tests.test_config.test_format_from_mime_restricted_to_candidates()[source]¶
formatsnarrows the resolution, likeformat_from_pathdoes.
- tests.test_config.test_jwcc_resolves_to_the_json5_parser(tmp_path)[source]¶
A
*.jwccfile is read by theJSON5parser, which is a superset of it.
- tests.test_config.test_jwcc_conf(invoke, simple_config_cli, tmp_path)[source]¶
A JWCC document loads: JSON plus comments and trailing commas.
- tests.test_config.test_conf_key_reaches_a_case_preserving_param_name(invoke, create_config)[source]¶
A parameter whose name kept its case is still addressed by that case.
Click takes an identifier declaration verbatim, so this parameter is named
Explicit_Name. No fold produces that spelling, so the template has to stay the authority on it.
- tests.test_config.test_conf_key_case_folds_onto_the_param_name(invoke, create_config, spelling)[source]¶
Every spelling Click could have derived
foo_barfrom reaches it.
- tests.test_config.test_conf_key_folding_onto_two_params_is_skipped(invoke, create_config, caplog)[source]¶
A key folding onto two parameter names picks neither, and warns.
Click allows
foo_barandFoo_Baron one command, and nothing in the folded spelling says which was meant.
- tests.test_config.test_strict_conf_accepts_a_folded_key(invoke, create_config)[source]¶
Strict mode no longer rejects a spelling the fold resolves.
- tests.test_config.test_mime_types_are_unambiguous()[source]¶
No media type is claimed by two formats.
A duplicate would leave
format_from_mimeresolving onConfigFormatdeclaration order alone, silently handing the media type to whichever format happens to be declared first.
- tests.test_config.test_docs_media_types_table_matches_formats()[source]¶
The media-type table in the docs is
ConfigFormat.mime_types, transcribed.A format gaining or losing a media type otherwise leaves the table stale, and that table is the only place a user reads the mapping from.
- tests.test_config.test_remote_conf_typed_by_content_type(invoke, simple_config_cli, httpserver, media_type, conf_text)[source]¶
An extension-less URL is typed by the media type its server advertises.
- tests.test_config.test_remote_conf_falls_back_to_the_url_name(invoke, simple_config_cli, httpserver, media_type)[source]¶
A generic or plain wrong media type still leaves the URL name to match on.
- tests.test_config.test_remote_conf_content_type_never_widens_the_format_set(invoke, httpserver, file_format_patterns, exit_code, stdout)[source]¶
A media type is resolved against
file_format_patternsalone.The same
application/jsondownload feeds the CLI whenJSONis accepted, and is rejected outright when the option only declaresTOML.
- tests.test_config.test_unparsable_conf_message_enumerates_formats(invoke, create_config, caplog, file_format_patterns, expected)[source]¶
The “error parsing” message never dangles its conjunction.
- tests.test_config.test_ini_subcommand_section_is_typed(invoke, create_config)[source]¶
An INI section addressing a subcommand reads its values as the options’ types.
A list is JSON-encoded in the file, like in the root section.
- tests.test_config.test_argfile_conf_file_overrides_defaults(invoke, simple_config_cli, create_config, assert_output_regex)[source]¶
An argfile feeds CLI tokens into the same default_map pipeline.
- tests.test_config.test_argfile_cli_overrides_conf(invoke, create_config)[source]¶
Command-line parameters take precedence over argfile values.
- tests.test_config.test_argfile_switches_sharing_a_destination(invoke, create_config, preparation)[source]¶
Each feature switch of one destination is read from an argfile.
The two options share a name, so the parameter structure lists both under one leaf. Keeping only the last one there leaves
--peeledunmatched.
An INI item a name of two option types addresses is refused, not guessed.
- tests.test_config.test_argfile_strict_conf(invoke, create_config, conf_text, expected_key)[source]¶
Strict mode rejects unknown options with the standard error.
An unmatched declaration is named the way Click names a parameter it derives from one, case fold included, so
--Unknown-Optionis reported asunknown_option.
- tests.test_config.test_argfile_unknown_option_ignored_when_not_strict(invoke, create_config)[source]¶
- tests.test_config.test_argfile_unparsable_conf(invoke, create_config, conf_text, message)[source]¶
An argfile that produces no option is rejected, saying how it was read.
- tests.test_config.test_argfile_export_config_rejected(invoke)[source]¶
Argfile has no serializer, so –export-config rejects it.
- tests.test_config.test_sqlite_conf_file_overrides_defaults(invoke, simple_config_cli, tmp_path, ext)[source]¶
- tests.test_config.test_sqlite_read_and_parse_conf(tmp_path)[source]¶
The default format patterns discover SQLite databases by extension.
- tests.test_config.test_sqlite_conf_unparsable(invoke, simple_config_cli, tmp_path, make_db, message)[source]¶
A SQLite file that cannot yield a configuration is rejected.
- tests.test_config.SPLITTABLE_STDLIB_MODULES = frozenset({'curses', 'dbm', 'readline', 'sqlite3', 'tkinter'})¶
Standard library modules a distribution can ship apart from its base Python.
Each wraps a system library, so a packager can leave it out of the interpreter: FreeBSD serves
sqlite3as a separatepyXXX-sqlite3package, and Debian servestkinteraspython3-tk. A module-level import of any of them kills every CLI built onclick_extraat import time, on an interpreter that is otherwise complete, so each one is probed and imported at its point of use.
- tests.test_config.eager_imports(tree)[source]¶
Top-level packages a module imports as soon as it is loaded.
Skips function bodies, which import at call time, and
tryblocks, which guard against a missing module. Those are the two shapes that survive an interpreter without the module.
- tests.test_config.test_no_splittable_stdlib_module_imported_at_load_time()[source]¶
No module of the package imports a splittable module unconditionally.
- tests.test_config.test_parser_probes_stay_off_the_import()[source]¶
Importing the package probes no parser: building a
ConfigOptiondoes.
- tests.test_config.test_sqlite3_not_imported_by_the_package()[source]¶
Importing the package leaves
sqlite3out ofsys.modules.Covers the whole import graph, dependencies included, where
test_no_splittable_stdlib_module_imported_at_load_timeonly reads the package’s own sources.
- tests.test_config.test_sqlite_support_gates_the_format()[source]¶
SQLITEis enabled exactly when the standard library ships its bindings.
- tests.test_config.test_plist_conf_file_overrides_defaults(invoke, simple_config_cli, assert_output_regex, tmp_path, plist_variant)[source]¶
Both the XML and the binary plist variants load through –config.
- tests.test_config.test_plist_read_and_parse_conf(tmp_path)[source]¶
The default format patterns discover plist files by extension.
- tests.test_config.test_validate_config_sqlite_valid(invoke, tmp_path)[source]¶
–validate-config accepts a valid SQLite configuration database.
- tests.test_config.test_default_map_populated(invoke, create_config)[source]¶
Verify default_map structure when config values match CLI parameters.
Complements test_conf_metadata which only checks the empty default_map case (where no config values match the CLI’s parameter structure).
- tests.test_config.test_merge_default_map_standalone(invoke)[source]¶
merge_default_map filters a config into default_map on its own.
load_conf bypasses this method by installing the merged config the validation pipeline already produced, so it is exercised here directly to cover the standalone entry point external callers rely on.
- tests.test_config.test_merge_default_map_keeps_dotted_keys_in_opaque_subtree(invoke, caplog)[source]¶
merge_default_map leaves a dotted key inside an opaque sub-tree whole.
themesis the extension point everyConfigOptionregisters. A theme name that ends in a dot is data, so the merge logs no empty-segment warning.
- tests.test_config.test_default_map_none_without_config(invoke)[source]¶
Verify default_map is left alone when –no-config is used.
- tests.test_config.test_nested_subcommand_config(invoke, create_config)[source]¶
Config propagates through group -> subgroup -> leaf command.
Two CLIs sharing the same configuration file.
- tests.test_config.test_params_template_not_mutated_across_invocations(invoke, create_config)[source]¶
Back-to-back invocations of the same CLI must not cross-contaminate via
ConfigOption.params_template.params_templateis a@cached_propertyof theConfigOptioninstance bound at decoration time, so it lives for the lifetime of the CLI object._merge_into_templatemutates its first argument in place; without a defensive copy, the cached template would accumulate values from earlier--configloads and leak them intodefault_mapon subsequent invocations.
- tests.test_config.test_lazy_group_config(invoke, create_config, tmp_path)[source]¶
Test that lazy groups work with config files.
- tests.test_config.test_lazy_group_config_no_config_flag(invoke, create_config, tmp_path)[source]¶
Test that –no-config works with lazy groups.
- tests.test_config.orchard_modules(tmp_path)[source]¶
Put the
orchard_appleandorchard_bananacommand modules onsys.path.Both modules leave
sys.modulesbefore and after the test, so the test sees which of them its CLI imports.
- tests.test_config.test_lazy_group_included_params_imports(invoke, create_config, orchard_modules, included_params, expected_stdout)[source]¶
A configuration loads without importing the lazy subcommands the allowlist leaves out.
The walk building the parameter template skips each subcommand no allowed ID lies under. The section of a subcommand the allowlist leaves out does not reach it either, when the invocation imports it later.
- tests.test_config.test_lazy_group_empty_allowlist_imports_no_subcommand(invoke, create_config, orchard_modules)[source]¶
included_params=()loads a configuration without importing any lazy subcommand.The group runs on its own, so no dispatch imports a subcommand behind the walk’s back: a module in
sys.modulesis one the configuration load brought in.
- tests.test_config.test_lazy_group_included_params_introspection(invoke, create_config, orchard_modules)[source]¶
--params,--export-configand--validate-confighonor an allowlist for the subcommands its template walk skips.The walk skips
banana, soexcluded_paramslists none of its parameters.
- tests.test_config.test_file_pattern(file_format_patterns, expected_pattern)[source]¶
Test the file_pattern property with different file format configurations.
- tests.test_config.test_default_pattern_roaming_force_posix(roaming, force_posix, current_platform, expected_path, monkeypatch)[source]¶
Test that roaming and force_posix affect the default pattern generation.
- tests.test_config.test_default_pattern_xdg_config_home(force_posix, tmp_path, monkeypatch)[source]¶
Test that default_pattern respects XDG_CONFIG_HOME on Linux.
- tests.test_config.cascade_tree(tmp_path, monkeypatch)[source]¶
Point auto-discovery at a temporary app dir nested inside
tmp_path.Returns
(tmp_path, app_dir): a config file dropped inapp_diris the most local source, one intmp_pathsits one level up the parent walk.
- tests.test_config.test_cascade_merges_files_local_wins(invoke, cascade_tree)[source]¶
With cascade=True, local values win and parent values fill the gaps.
- tests.test_config.test_cascade_list_local_wins(invoke, cascade_tree)[source]¶
A list two layers both set resolves to the most local one, like any value.
- tests.test_config.test_cascade_merges_subcommand_sections(invoke, cascade_tree)[source]¶
A section two files both define resolves key by key, at every depth.
The local file wins each key it sets, and the keys only the parent file sets in the same section still apply. A list comes whole from the local file.
- tests.test_config.test_no_cascade_first_file_wins(invoke, cascade_tree)[source]¶
Without cascade, the first parseable file wins entirely.
- tests.test_config.test_cascade_single_layer_from_parent(invoke, cascade_tree)[source]¶
A lone file found up the walk is applied as-is.
- tests.test_config.test_cascade_explicit_config_does_not_cascade(invoke, cascade_tree)[source]¶
An explicit –config pins a single source, even with cascade=True.
- tests.test_config.test_cascade_conf_sources_metadata(invoke, cascade_tree)[source]¶
ctx.meta[CONF_SOURCES] lists every loaded file, highest precedence first.
- tests.test_config.test_cascade_params_names_the_file_of_each_key(invoke, tmp_path, monkeypatch)[source]¶
The
config_filecolumn of--paramsnames the file each value comes from.With three files, the file furthest up the walk is named for the key only it sets: each
default_maplayer keeps the position of its file inCONF_SOURCES.
- tests.test_config.test_cascade_conf_full_is_merged_view(invoke, cascade_tree)[source]¶
ctx.meta[CONF_FULL] exposes the deep-merged document.
- tests.test_config.test_cascade_validation_error_names_file(invoke, cascade_tree, caplog)[source]¶
A strict-check failure in one layer names that file and exits 1.
- tests.test_config.test_cascade_strict_check_skips_extension_points(invoke, cascade_tree)[source]¶
Each file of a cascade passes the strict check with an extension point in it.
The built-in
themestable is one: its keys name themes, not parameters.
- tests.test_config.test_read_and_parse_all_conf_orders_local_first(tmp_path)[source]¶
All parseable files are yielded, deepest first; unparsable ones skip.
- tests.test_config.test_read_and_parse_conf_returns_first(tmp_path)[source]¶
read_and_parse_conf keeps its first-match contract on top of the generator.
- tests.test_config.test_config_option_default_no_config(invoke, create_config)[source]¶
ConfigOption with default=NO_CONFIG disables autodiscovery.
- tests.test_config.test_no_config_explicit_with_default_no_config(invoke)[source]¶
–no-config still prints the skip message even when NO_CONFIG is the default.
- tests.test_config.test_excluded_params(invoke, create_config)[source]¶
Custom excluded_params prevents config values from being applied.
- tests.test_config.test_included_params(invoke, create_config)[source]¶
Only parameters in included_params are loaded from config.
- tests.test_config.test_included_params_empty(invoke, create_config)[source]¶
An empty included_params excludes all params from config.
- tests.test_config.test_included_and_excluded_params_conflict()[source]¶
Providing both included_params and excluded_params raises ValueError.
- tests.test_config.test_multiple_files_matching_glob(invoke, create_config, tmp_path)[source]¶
When multiple files match a glob, only the first parseable one is used.
- tests.test_config.test_forced_flags_warnings(caplog)[source]¶
Warnings fire when SPLIT, BRACE or NODIR flags are missing.
The flags resolve on first use, not at construction, so reading them is what fires the warnings.
- tests.test_config.test_brace_multi_format_search(invoke, tmp_path, ext)[source]¶
All format extensions are found in the search directory.
Regression test: before BRACE expansion, only the first format in the default pattern got the directory prefix: others were searched in CWD.
- tests.test_config.test_root_dir_parent_search_finds_non_toml(invoke, tmp_path)[source]¶
Parent search with root_dir correctly finds non-TOML config in parents.
Before the root_dir refactoring, SPLIT patterns like
*.toml|*.yamlonly applied the directory prefix to the first sub-pattern. Now with root_dir, all sub-patterns are scoped to the correct directory.
- tests.test_config.test_no_enabled_formats_raises()[source]¶
ValueError raised when all formats are disabled.
- tests.test_config.test_pyproject_toml_in_defaults()[source]¶
ConfigOption() with default file_format_patterns includes PYPROJECT_TOML.
- tests.test_config.test_pyproject_toml_tool_extraction(simple_config_cli)[source]¶
parse_conf with PYPROJECT_TOML returns the [tool] subsection.
- tests.test_config.test_pyproject_toml_no_tool_section(simple_config_cli)[source]¶
pyproject.toml without [tool] returns empty dict.
- tests.test_config.test_file_pattern_with_pyproject_toml()[source]¶
Explicit file_format_patterns with PYPROJECT_TOML works.
- tests.test_config.test_pyproject_toml_overrides_defaults(invoke, create_config)[source]¶
End-to-end: a CLI with default formats reads from pyproject.toml.
- tests.test_config.test_validate_config_valid(invoke, create_config)[source]¶
–validate-config with a valid config file exits 0.
- tests.test_config.test_validate_config_accepts_a_glob(invoke, create_config)[source]¶
–validate-config takes every location
--configtakes, glob included.
- tests.test_config.test_validate_config_accepts_a_url(invoke, httpserver)[source]¶
A configuration a CLI can load from a URL is one it can also validate.
- tests.test_config.test_validate_config_accepts_the_null_device(invoke)[source]¶
/dev/nullis a valid configuration, as--configloads it.
- tests.test_config.test_validate_config_invalid_keys(invoke, create_config)[source]¶
–validate-config with unrecognized keys exits 1.
- tests.test_config.test_extensionless_config(invoke, create_config, default_pattern, expected_help_default)[source]¶
Both broad and exact default patterns resolve the same .commandrc file.
The
defaultparameter is printed as-is on the help screen, so an exact path is more informative than a broad glob, but both locate the same file.
- tests.test_config.test_validate_config_unparsable(invoke, create_config)[source]¶
–validate-config with garbage content exits 2.
- tests.test_config.test_validate_config_parses_to_nothing(invoke, create_config)[source]¶
–validate-config names the format a file parses to nothing with.
- tests.test_config.test_validate_config_missing_file(invoke, tmp_path)[source]¶
–validate-config reports a nonexistent location from its own callback.
- tests.test_config.test_validate_config_requires_config_option(invoke, tmp_path)[source]¶
–validate-config without @config_option raises RuntimeError.
- tests.test_config.test_validate_config_pyproject_toml(invoke, create_config)[source]¶
–validate-config works with pyproject.toml [tool.*] sections.
- tests.test_config.test_export_config_to_stdout(invoke)[source]¶
–export-config writes the resolved configuration and exits 0.
- tests.test_config.test_export_config_captures_overrides(invoke)[source]¶
Command-line values are reflected in the dump.
- tests.test_config.test_export_config_captures_environment(invoke)[source]¶
Values resolved from environment variables are reflected in the dump.
- tests.test_config.test_export_config_numeric_values_keep_their_type(invoke)[source]¶
A command-line numeric scalar is dumped as a number, not a quoted string.
- tests.test_config.test_export_config_round_trip(invoke, tmp_path, fmt)[source]¶
A dumped configuration reloads to the same values through –config.
- tests.test_config.test_export_config_invalid_format(invoke)[source]¶
An unsupported format token is rejected by the Choice.
- tests.test_config.test_export_config_without_its_writer(invoke, monkeypatch)[source]¶
A format whose writer is not installed fails with the install command.
- tests.test_config.test_export_config_requires_config_option(invoke)[source]¶
–export-config without @config_option raises RuntimeError.
- tests.test_config.test_export_config_standalone_falls_back_to_defaults(invoke)[source]¶
Without a captured command line (vanilla Command), defaults are dumped.
- class tests.test_config.PlainGroupSubclass(name=None, commands=None, invoke_without_command=False, no_args_is_help=None, subcommand_metavar=None, chain=False, result_callback=None, **kwargs)[source]¶
Bases:
GroupA bare
click.Groupsubclass, like third-party plugin frameworks produce.
- tests.test_config.SUBCOMMAND_GROUP_FACTORIES = (((<function group>,), (), 'click-group'), ((<function group>,), (), 'cloup-group'), ((<function decorator_factory.<locals>.decorator>,), (), 'click-extra-group'), ((functools.partial(<function group>, cls=<class 'tests.test_config.PlainGroupSubclass'>),), (), 'click-group-subclass'))¶
Every group flavor a
--configoption can be attached to.The reserved subcommand keys are declared by
config_option, so they must be honored by whichever group class carries that option, not only by click-extra’s ownGroup.
- tests.test_config.make_subcommand_group(group_factory, *, chain, **kwargs)[source]¶
Build a
subcmdcligroup of group_factory, carrying--config.Registers a
debugand asyncsubcommand, each echoing its own name. click-extra groups already ship--config, so the option is only added to the other flavors. Extra keyword arguments go to group_factory.
- tests.test_config.test_default_subcommands_on_any_group_class(invoke, create_config, group_factory)[source]¶
_default_subcommandsfires whatever group class carries--config.
- tests.test_config.test_prepend_subcommands_on_any_group_class(invoke, create_config, group_factory)[source]¶
_prepend_subcommandsfires whatever group class carries--config.
- tests.test_config.SUBGROUP_CLASSES = (((<class 'click.core.Group'>,), (), 'click-subgroup'), ((<class 'cloup._commands.Group'>,), (), 'cloup-subgroup'), ((<class 'click_extra.commands.Group'>,), (), 'click-extra-subgroup'), ((<class 'tests.test_config.PlainGroupSubclass'>,), (), 'click-subgroup-subclass'))¶
Every group class a subgroup can be built on.
A subgroup reached through an ancestor’s
--configholds no option of its own, so its class is all that could keep it from reading its own section.
- tests.test_config.test_subcommands_on_a_subgroup(invoke, create_config, group_factory, subgroup_class, reserved_key, cli_args)[source]¶
A subgroup applies the reserved keys of its own configuration section.
Only the root group carries
--config, so a subgroup is never visited as that option is processed. It reads[parent.subgroup]all the same, whatever classes the root and the subgroup are built on.
- tests.test_config.app_dir_conf(tmp_path, monkeypatch)[source]¶
Point auto-discovery at a temporary app dir, and write a config into it.
Lets a test invoke a CLI with no argument at all, which is the only way to reach Click’s
no_args_is_helpshort-circuit: passing--configwould make the invocation non-empty.
- tests.test_config.test_subcommands_beat_no_args_is_help(invoke, app_dir_conf, group_factory, reserved_key, chain)[source]¶
A configured subcommand outranks Click’s no-args help screen.
Click raises that screen before it runs any parameter, so
--configalone never sees a bare invocation, whatever group class carries it.
- tests.test_config.test_no_args_is_help_survives_a_silent_config(invoke, app_dir_conf, group_factory)[source]¶
The help screen stays when the configuration names no subcommand.
- tests.test_config.test_bare_invocation_loads_the_configuration_once(invoke, create_config, group_factory)[source]¶
A bare invocation running a configured subcommand reads its document once.
--configis processed ahead of the no-args help screen, then again by Click’s parameter loop. A location set on purpose, here through the environment, makes each load print its status line.
- tests.test_config.test_no_args_is_help_stays_without_a_config_option(invoke, app_dir_conf)[source]¶
A group carrying no
--confignever reads a configuration to dispatch on.
- tests.test_config.test_group_parse_args_is_wrapped_once()[source]¶
Each new
ConfigOptionleaves the wrapper ofclick.Groupas it found it.
- tests.test_config.test_default_subcommand_selection(invoke, create_config, cli_subcmd, expected, unexpected)[source]¶
Config default is used when no subcommand given; CLI wins otherwise.
- tests.test_config.test_default_subcommand_chained(invoke, create_config)[source]¶
chain=True group runs multiple config-listed subcommands in order.
- tests.test_config.test_default_subcommand_config_errors(invoke, create_config, conf_value, error_fragment)[source]¶
Bad _default_subcommands values produce clear errors.
- tests.test_config.test_default_subcommand_strict_mode_tolerance(invoke, create_config)[source]¶
strict=True config with _default_subcommands doesn’t raise.
- tests.test_config.test_default_subcommand_validate_config_tolerance(invoke, create_config)[source]¶
–validate-config with _default_subcommands reports valid.
- tests.test_config.test_default_subcommand_with_options(invoke, create_config)[source]¶
Default subcommand receives its config-provided options.
- tests.test_config.test_default_subcommand_no_config(invoke)[source]¶
Normal behavior when no config file is loaded.
- tests.test_config.test_default_subcommand_duplicates_warning(invoke, create_config)[source]¶
Duplicate entries in _default_subcommands are deduplicated with a warning.
- tests.test_config.test_default_subcommand_cli_override_debug_log(invoke, create_config)[source]¶
Debug log emitted when CLI subcommands override config defaults.
- tests.test_config.test_prepend_subcommand_selection(invoke, create_config, cli_subcmd, expected, unexpected)[source]¶
Prepend fires regardless of whether a CLI subcommand is given.
- tests.test_config.test_prepend_subcommand_with_defaults(invoke, create_config, cli_subcmd, expect_backup)[source]¶
Prepend always applies; defaults only fire when no CLI subcommand given.
- tests.test_config.test_prepend_subcommand_non_chained_error(invoke, create_config)[source]¶
Error on non-chained group.
- tests.test_config.test_prepend_subcommand_config_errors(invoke, create_config, conf_value, error_fragment)[source]¶
Bad _prepend_subcommands values produce clear errors.
- tests.test_config.test_prepend_subcommand_strict_mode_tolerance(invoke, create_config)[source]¶
strict=True config with _prepend_subcommands doesn’t raise.
- tests.test_config.test_prepend_subcommand_validate_config_tolerance(invoke, create_config)[source]¶
–validate-config with _prepend_subcommands reports valid.
- tests.test_config.test_prepend_subcommand_duplicates_warning(invoke, create_config)[source]¶
Duplicate entries in _prepend_subcommands are deduplicated with a warning.
- tests.test_config.test_prepend_subcommand_info_log(invoke, create_config)[source]¶
INFO log emitted when _prepend_subcommands are injected.
- tests.test_config.test_prepend_subcommand_multiple(invoke, create_config)[source]¶
Multiple prepend subcommands run in order.
- tests.test_config.test_sanity_checks(caplog, default, file_format_patterns, flags, present, absent)[source]¶
_check_pattern_sanityemits (or suppresses) debug logs per pattern config.
- tests.test_config.test_dotted_keys_in_config(invoke, simple_config_cli, create_config, conf_name, conf_text)[source]¶
Dotted keys in config files are expanded into nested structures.
- tests.test_config.test_expand_dotted_keys_conflict_warning(caplog, input_conf, warning_fragment)[source]¶
Scalar/dict conflicts on the same key emit a warning.
- tests.test_config.test_expand_dotted_keys_empty_segments(caplog, input_conf)[source]¶
Dotted keys with empty segments are skipped with a warning.
- tests.test_config.test_expand_dotted_keys_strict_conflict(input_conf, error_fragment)[source]¶
Strict mode raises ValueError on type conflicts.
- tests.test_config.test_expand_dotted_keys_strict_empty_segments(input_conf)[source]¶
Strict mode raises ValueError on dotted keys with empty segments.
- tests.test_config.test_strict_conf_dotted_key_conflict(invoke, create_config, flag)[source]¶
Strict mode rejects a dotted-key type conflict with a validation error.
The error is reported like an unknown key, not raised as a traceback.
- tests.test_config.test_expand_dotted_keys_keeps_a_key_that_is_not_a_string()[source]¶
YAML reads
1:as a number: such a key holds no dot to expand.
- tests.test_config.test_conf_key_that_is_not_a_string(invoke, create_config, strict, flag, exit_code, message)[source]¶
A YAML key that reads as a number is a configuration error, not a traceback.
Strict mode and
--validate-configreject it, and lax mode skips it.
- tests.test_config.test_parse_jsonc_reports_the_line_of_an_error()[source]¶
A blanked block comment keeps its newlines, so the line numbers hold.
tests.test_config_discovery module¶
Test the search for configuration files, in click_extra.config.discovery.
- tests.test_config_discovery.test_parent_patterns(tmp_path, search_parents, subdirs, create_file, expected_start)[source]¶
- tests.test_config_discovery.test_parent_patterns_with_magic_pattern(tmp_path, pattern_factory, expected_factory)[source]¶
Test parent_patterns with glob patterns containing magic characters.
- tests.test_config_discovery.test_parent_patterns_magic_no_search(tmp_path)[source]¶
Magic pattern with search_parents=False yields only the original.
- tests.test_config_discovery.test_parent_patterns_relative_path(tmp_path)[source]¶
Test parent_patterns resolves relative paths to absolute.
- tests.test_config_discovery.test_parent_patterns_stop_at_path(tmp_path)[source]¶
stop_at as a path limits the parent directory walk.
- tests.test_config_discovery.test_parent_patterns_stop_at_vcs(tmp_path, has_vcs, expected_bounded)[source]¶
stop_at=VCS stops at VCS root, or walks to filesystem root if none.
- tests.test_config_discovery.test_parent_patterns_inaccessible_directory(tmp_path)[source]¶
Walk stops at an inaccessible directory.
- tests.test_config_discovery.test_find_vcs_root(tmp_path, vcs_dir, expected)[source]¶
Test _find_vcs_root with .git, .hg, and no VCS markers.
- tests.test_config_discovery.test_default_magic_characters_match_wcmatch()[source]¶
The characters
_literal_basesplits on are the ones wcmatch reads as magic.Checked under the default search flags, over every printable character.
- tests.test_config_discovery.test_literal_base(tmp_path, pattern_factory, expected_factory)[source]¶
_literal_basestops at the first segment holding glob syntax.
- tests.test_config_discovery.test_literal_base_needs_the_default_search_flags()[source]¶
Custom search flags take wcmatch to read, so no folder is derived.
- tests.test_config_discovery.test_search_skips_the_glob_of_a_missing_folder(tmp_path, monkeypatch)[source]¶
No file can match in a folder that does not exist, so it is never globbed.
- tests.test_config_discovery.test_parent_search_goes_on_above_a_missing_folder(tmp_path)[source]¶
The parent walk still finds a file above a folder that does not exist.
- tests.test_config_discovery.test_plain_run_loads_no_search_dependency(tmp_path)[source]¶
A plain run without a configuration file loads neither
extra_platformsnorwcmatch.Every invocation searches the default configuration folder, which rarely exists: a missing folder is ruled out before wcmatch is imported. The probe runs in a pristine interpreter, since pytest itself has both loaded already. The default folder is pointed at a place that does not exist, on every platform
click.get_app_dir()knows, and the working directory holds nopyproject.toml.
tests.test_config_flags module¶
tests.test_config_schema module¶
Tests for typed configuration schemas, validation, and extension points.
- tests.test_config_schema.test_normalize_config_keys_keeps_a_key_that_is_not_a_string()[source]¶
YAML reads
1:as a number: such a key has no hyphen to replace.
- tests.test_config_schema.test_config_schema_dataclass(invoke, create_config)[source]¶
Dataclass schemas are auto-detected and instantiated with normalized keys.
- tests.test_config_schema.test_config_schema_cascade(invoke, tmp_path, monkeypatch)[source]¶
With cascade=True, the schema is built from the merged view.
- tests.test_config_schema.test_config_schema_callable(invoke, create_config)[source]¶
A plain callable can be used as config_schema.
- tests.test_config_schema.test_config_schema_no_config_file(invoke)[source]¶
When no config file is found, schema defaults are used.
- tests.test_config_schema.test_config_schema_dataclass_defaults(invoke, create_config)[source]¶
Dataclass defaults are used for fields not present in the config file.
- tests.test_config_schema.test_fallback_sections(invoke, create_config)[source]¶
Legacy section names are recognized with a deprecation warning.
- tests.test_config_schema.test_fallback_section_warns_once(invoke, create_config)[source]¶
A legacy section loaded with its themes is reported once, not per reader.
- tests.test_config_schema.test_fallback_sections_prefers_current(invoke, create_config)[source]¶
When both current and legacy sections exist, current wins.
- tests.test_config_schema.test_config_schema_multiple_formats(invoke, create_config, conf_name, conf_text)[source]¶
Config schema works with YAML and JSON, not just TOML.
- tests.test_config_schema.test_config_schema_on_config_option_directly(invoke, create_config)[source]¶
Config schema can be set directly on ConfigOption via the decorator.
- tests.test_config_schema.test_get_tool_config_defaults_to_current_context(invoke, create_config)[source]¶
get_tool_config() works without passing ctx explicitly.
- tests.test_config_schema.test_flatten_config_keys_with_normalize()[source]¶
flatten + normalize maps nested kebab-case config to flat snake_case fields.
- tests.test_config_schema.test_config_schema_nested_toml(invoke, create_config)[source]¶
Nested TOML sub-tables map to flat dataclass fields via flattening.
- tests.test_config_schema.test_config_schema_strict_rejects_unknown(invoke, create_config)[source]¶
schema_strict=True raises ValueError on unrecognized config keys.
- tests.test_config_schema.test_config_schema_strict_passes_when_valid(invoke, create_config)[source]¶
schema_strict=True does not raise when all config keys are known.
- tests.test_config_schema.test_config_schema_strict_with_nested(invoke, create_config)[source]¶
schema_strict=True validates flattened keys from nested sub-tables.
- tests.test_config_schema.test_config_schema_lax_warns_unknown_when_schema_only(invoke, create_config)[source]¶
A schema-only section warns on unknown keys instead of dropping them silently.
included_params=()means no CLI parameter is merged from the app’s section, so any key the schema does not know can only be a typo: lax mode then logs a warning while still loading the known fields.
- tests.test_config_schema.SCHEMA_ONLY_CONF = '[schema-only-cli]\nknown-field = "good"\ntypo-field = "oops"\n'¶
A schema-only section, holding one key its schema does not know.
- tests.test_config_schema.test_schema_only_section_loads_in_strict_mode(invoke, create_config)[source]¶
The CLI-parameter strict check leaves a schema-only section to the schema.
No parameter is read from it, so the schema alone reports the key it does not know, which lax mode only warns about.
- tests.test_config_schema.test_validate_config_warns_unknown_when_schema_only(invoke, create_config)[source]¶
--validate-configaccepts a schema-only section, naming its unknown key.The file stays valid, as it loads: lax mode only warns.
- tests.test_config_schema.test_config_schema_lax_silent_when_params_merged(invoke, create_config)[source]¶
Without
included_params=(), lax mode stays silent on unknown keys.The section may legitimately mix CLI parameter keys with schema fields, so a key unknown to the schema is not necessarily a typo.
- class tests.test_config_schema.MixedConfig(extra_stuff='', nested=MixedNestedConfig(), sub_section_key=0)[source]¶
Bases:
object- nested: MixedNestedConfig¶
- tests.test_config_schema.MIXED_CONF = '[mixed-cli]\nfruit = "plum"\nextra-stuff = "x"\n\n[mixed-cli.nested]\ndepth = 3\n\n[mixed-cli.sub-section]\nkey = 2\n'¶
A section mixing the
--fruitparameter with the fields ofMixedConfig.Each field takes a shape a schema reads: a scalar, a nested dataclass table, and a table flattened into a field name.
- tests.test_config_schema.test_strict_check_leaves_schema_keys_to_the_schema(invoke, create_config, kwargs, args, expected)[source]¶
The CLI-parameter strict check accepts the keys the dataclass schema reads.
--validate-configalways runs the check, andconfig_strict=Trueruns it as the file loads.
- tests.test_config_schema.test_strict_check_rejects_a_key_neither_reads(invoke, create_config)[source]¶
A key matching neither a parameter nor a schema field stays an error.
- class tests.test_config_schema.PinnedConfig(extra_stuff='', nested=MixedNestedConfig(), sub_section_key=0, pinned=dict(), axes=dict())[source]¶
Bases:
object- nested: MixedNestedConfig¶
- tests.test_config_schema.test_schema_keys(section, expected)[source]¶
_schema_keysreturns the keys of a section the dataclass reads in full.
- tests.test_config_schema.test_schema_keys_of_a_callable_schema()[source]¶
A callable schema does not tell which keys it reads.
- tests.test_config_schema.test_make_schema_callable_warn_unknown(caplog)[source]¶
warn_unknown=Truelogs unknown keys, recursing into nested dataclasses.
- tests.test_config_schema.test_make_schema_callable_lax_default_is_silent(caplog)[source]¶
Without
warn_unknown, lax mode keeps dropping unknown keys silently.
- tests.test_config_schema.test_pyproject_toml_cwd_discovery(invoke, tmp_path, monkeypatch)[source]¶
pyproject.toml in CWD is discovered automatically without –config.
- tests.test_config_schema.test_pyproject_toml_cwd_discovery_walks_up(invoke, tmp_path, monkeypatch)[source]¶
pyproject.toml discovery walks up from CWD to parent directories.
- tests.test_config_schema.test_pyproject_toml_explicit_config_skips_cwd(invoke, create_config, tmp_path, monkeypatch)[source]¶
Explicit –config skips CWD pyproject.toml discovery.
A pyproject.toml without [tool.<cli_name>] is skipped.
Regression: a pyproject.toml carrying only unrelated [tool.X] sections (like a dotfiles repo’s [tool.ruff]) used to shadow the user’s app-dir config. It must now be ignored so the CLI falls back to its defaults instead of inheriting an unrelated project’s settings.
CWD walk continues past a pyproject.toml lacking [tool.<cli_name>].
A nearer pyproject.toml that only carries unrelated [tool.X] sections must not stop the upward walk: a parent pyproject.toml with a matching [tool.<cli_name>] section should still be discovered.
- tests.test_config_schema.test_pyproject_toml_cwd_mixed_tool_sections(invoke, tmp_path, monkeypatch)[source]¶
[tool.<cli_name>] is picked from a pyproject.toml that also has others.
An unrelated pyproject.toml falls through to the app-dir config.
Directly exercises the documented intent of the fix: when CWD contains a pyproject.toml whose only [tool.X] sections are unrelated to the CLI, the walk must give up so the standard app-dir search can find the user’s actual config and apply it.
- tests.test_config_schema.test_flatten_config_keys_opaque()[source]¶
opaque_keys stops flattening at matching key boundaries.
- tests.test_config_schema.test_flatten_config_keys_opaque_nested()[source]¶
opaque_keys works at deeper nesting levels.
- tests.test_config_schema.test_schema_type_aware_flattening(invoke, create_config)[source]¶
dict-typed dataclass fields stop flattening automatically.
- tests.test_config_schema.test_schema_field_metadata_config_path(invoke, create_config)[source]¶
click_extra.config_path extracts a value at a dotted TOML path.
- tests.test_config_schema.test_schema_field_metadata_normalize_keys_true(invoke, create_config)[source]¶
click_extra.normalize_keys defaults to True: keys are normalized.
- tests.test_config_schema.test_schema_nested_dataclass(invoke, create_config)[source]¶
Nested dataclass fields are recursively instantiated.
- tests.test_config_schema.test_schema_nested_dataclass_with_opaque_fields(invoke, create_config)[source]¶
Nested dataclass with dict-typed fields preserves opaque keys.
- tests.test_config_schema.test_schema_nested_dataclass_defaults(invoke, create_config)[source]¶
Nested dataclass uses defaults when config section is absent.
- tests.test_config_schema.test_strict_skips_opaque_dict_field(invoke, create_config)[source]¶
Strict mode does not reject keys inside a
dict[str, X]schema field.A field typed as
dict[str, dict]is user-controlled: the keys are data, not CLI flag names. Click-extra strips that sub-tree before running its unknown-key check so app extensions don’t trip strict mode.
- tests.test_config_schema.test_strict_skips_opaque_metadata_field(invoke, create_config)[source]¶
Strict mode also honors the
EXTENSION_METADATA_KEYmarker on a field whose Python type is not a mapping.
- tests.test_config_schema.test_validate_config_skips_opaque_field(invoke, create_config)[source]¶
–validate-config also skips opaque sub-trees, so a config that the runtime accepts also passes validation.
- tests.test_config_schema.test_opaque_field_keeps_dotted_keys(invoke, create_config, caplog, strict)[source]¶
A dot in a key of an opaque sub-tree is part of the key, not a path.
Dotted-key expansion skips extension points, whose keys are data. A key with a trailing, inner or doubled dot reaches the schema whole, logs no empty-segment warning and passes strict mode.
- tests.test_config_schema.test_config_validator_runs_and_fails_under_validate_config(invoke, create_config)[source]¶
A registered
ConfigValidatorruns during--validate-configand surfaces itsValidationErrorwith a path rooted at the config file.
- tests.test_config_schema.test_config_validator_runs_during_normal_load(invoke, create_config)[source]¶
A misconfigured opaque sub-tree fails fast during normal config loading, not only under
--validate-config.
- tests.test_config_schema.test_config_validator_extension_path_strips_strict_check(invoke, create_config)[source]¶
A ConfigValidator(extension_path=…) registration alone is enough to skip strict-check on that path, even when the schema doesn’t have the field.
- tests.test_config_schema.test_config_validator_collects_all_errors(invoke, create_config)[source]¶
--validate-configreports every detected error in one pass.A config with both an unknown CLI flag key and a validator-flagged field should surface both messages before the run exits non-zero, so the user sees the full punch list.
- tests.test_config_schema.test_collect_opaque_paths_from_schema()[source]¶
Schema introspection picks up dict-typed fields, metadata-marked fields, and nested-dataclass opaque fields with dotted prefixes.
- tests.test_config_schema.test_schema_strict_honors_extension_metadata_on_non_mapping_field(invoke, create_config)[source]¶
schema_strict must not descend into an EXTENSION_METADATA_KEY-marked field whose Python type is not a mapping.
Before the opaque-path unification, the outer strip honored the marker but the dataclass adapter’s own flatten boundary inspected only the type hint, so the marked sub-tree was flattened into dotted keys and rejected as unknown.
- tests.test_config_schema.test_run_config_validation_valid_document()[source]¶
A clean document yields an ok report with the schema instance built and every opaque sub-tree extracted.
- tests.test_config_schema.test_run_config_validation_exposes_merged_conf()[source]¶
A passing strict check carries the template-filtered config as merged_conf, with recognized values merged in and unknown keys dropped.
- tests.test_config_schema.test_run_config_validation_collects_all_then_short_circuits()[source]¶
collect_all=True gathers errors from every stage in order; collect_all=False stops after the first.
- tests.test_config_schema.test_run_config_validation_wraps_schema_errors()[source]¶
A schema_strict failure is recorded as a ValidationError with the schema_error code, and the message is preserved verbatim.
- tests.test_config_schema.test_run_config_validation_no_schema_no_template()[source]¶
With neither a template nor a schema, the report is ok and carries no schema instance.
- tests.test_config_schema.test_select_app_section_of_an_empty_app_name_is_the_document()[source]¶
An empty app name reads the whole document as the section.
_rooted_pathand_scope_app_sectionsalready read it that way.
- tests.test_config_schema.test_run_config_validation_of_an_empty_app_name()[source]¶
An empty app name feeds the whole document to the schema and validators.
- tests.test_config_schema.test_run_config_validation_reports_a_key_that_is_not_a_string()[source]¶
A key YAML reads as a number is an invalid key to the strict check, and an unknown option to a strict schema, never a crash.
- tests.test_config_schema.test_make_schema_callable_coerces_dict_to_dataclass()[source]¶
The public make_schema_callable turns a raw config dict into a dataclass.
- tests.test_config_schema.test_field_docstrings_returns_full_text()[source]¶
Attribute docstrings are recovered whole, paragraph breaks preserved.
- tests.test_config_schema.test_field_docstrings_degrades_without_source()[source]¶
A class defined through exec has no source: the mapping is empty.
- tests.test_config_schema.test_schema_field_infos_flat_schema()[source]¶
Keys are kebab-cased and sorted; defaults, types, and summaries surface.
- tests.test_config_schema.test_schema_field_infos_nested_and_config_path()[source]¶
Nested dataclasses expand to dotted keys; config_path metadata wins.
- tests.test_config_schema.test_schema_field_infos_sorts_segment_wise()[source]¶
A sub-table’s options stay contiguous when a sibling shares their prefix.
Plain string sort would interleave
pear-cellarbetweenpear.cratesandpear.pickers(in ASCII-sorts before.); segment-wise sort keeps thepeartable’s options together.
tests.test_context module¶
Tests for click_extra.context, and the context class that carries it.
Covers four surfaces:
The registry of
ctx.metakey constants.Context, Click Extra’scloup.Contextsubclass, defined inclick_extra.commands._LazyMetaDict, the lazyctx._metaproxy used byVersionOption.
- tests.test_context.KEY_CONSTANTS: tuple[tuple[str, str], ...] = (('RAW_ARGS', 'click_extra.raw_args'), ('INVOCATION_NAME', 'click_extra.invocation_name'), ('CONF_SOURCE', 'click_extra.conf_source'), ('CONF_FULL', 'click_extra.conf_full'), ('CONF_SOURCES', 'click_extra.conf_sources'), ('TOOL_CONFIG', 'click_extra.tool_config'), ('SUBCOMMANDS_APPLIED', 'click_extra.subcommands_applied'), ('VERBOSITY_LEVEL', 'click_extra.verbosity_level'), ('VERBOSITY', 'click_extra.verbosity'), ('DEBUG', 'click_extra.debug'), ('VERBOSE', 'click_extra.verbose'), ('QUIET', 'click_extra.quiet'), ('START_TIME', 'click_extra.start_time'), ('JOBS', 'click_extra.jobs'), ('TABLE_FORMAT', 'click_extra.table_format'), ('SORT_BY', 'click_extra.sort_by'), ('TABLE_SORT_KEY', 'click_extra.table_sort_key'), ('COLUMNS', 'click_extra.columns'), ('THEME', 'click_extra.theme.active'), ('THEME_OVERRIDES', 'click_extra.theme.overrides'), ('TELEMETRY', 'click_extra.telemetry'), ('PROGRESS', 'click_extra.progress'), ('ACCESSIBLE', 'click_extra.accessible'), ('ZERO_EXIT', 'click_extra.zero_exit'))¶
Pairs of
(attribute name, raw string key)for every registeredctx.metaentry. Single source of truth used by every parametrized registry test below.
- tests.test_context.test_key_constant_value(attr, expected)[source]¶
Each registered constant binds to its documented string value.
Pins the spelling of every key so a rename of the literal does not silently break downstream code that reads
ctx.meta["click_extra.X"].- Return type:
- tests.test_context.test_key_uses_namespace_prefix(key)[source]¶
Every registered key sits under
META_NAMESPACE.- Return type:
- tests.test_context.test_keys_are_unique()[source]¶
No two registry constants share a string value.
- Return type:
- tests.test_context.test_registry_covers_all_module_constants()[source]¶
KEY_CONSTANTSlists every public namespace-prefixed constant.Drift detector: when a new
ctx.metakey is added toclick_extra.contextbut not toKEY_CONSTANTS, this test fails so the maintainer remembers to update both sides.- Return type:
- tests.test_context.NON_META_KEYS = frozenset({'click_extra.config_docstring_heading', 'click_extra.config_path', 'click_extra.extension', 'click_extra.normalize_keys', 'click_extra.tabs_vertical', 'click_extra.todo_unrendered'})¶
Literals sharing
META_NAMESPACEthat name noctx.metakey.
- tests.test_context.test_meta_keys_are_spelled_in_context_only()[source]¶
No module but
click_extra.contextspells actx.metakey.Every other module reads its keys from there: none builds one from
META_NAMESPACE, or writes one as a literal.- Return type:
- tests.test_context.test_internal_keys_are_private_and_unique()[source]¶
The keys only Click Extra reads sit under
click_extra._, apart from the public ones.- Return type:
- tests.test_context.test_get_returns_default_for_missing_key()[source]¶
get()mirrorsdict.getsemantics.- Return type:
- tests.test_context.test_set_then_get_round_trip()[source]¶
set()is observable throughget().- Return type:
- tests.test_context.test_setdefault_stores_the_factory_value_once()[source]¶
setdefault()creates a missing value, then keeps it.- Return type:
- tests.test_context.test_context_uses_help_formatter()[source]¶
Contextinstalls Click Extra’s colorized formatter.- Return type:
- tests.test_context.test_context_meta_kwarg_seeds_ctx_meta()[source]¶
The
meta=kwarg populatesctx.metaat construction time.- Return type:
- tests.test_context.test_context_meta_kwarg_omitted_leaves_meta_empty()[source]¶
Without
meta=,ctx.metais an empty dict, notNone.- Return type:
- tests.test_context.test_context_color(monkeypatch, parent_color, child_color, expected)[source]¶
Contextcolor resolution covers every parent/child path.Root contexts without an explicit
color=resolve the GNU auto default: with no color environment variable they stay atNone(TTY detection). Child contexts inherit the parent’s color unless they override it explicitly.- Return type:
- tests.test_context.test_context_posixly_correct(monkeypatch, env_present, expected)[source]¶
POSIXLY_CORRECTflipsallow_interspersed_argsoff when present.A plain
click.Commanddefaults the flag toTrue, so the unset case leaves interspersing enabled and the set case disables it.- Return type:
- tests.test_context.test_posixly_correct_presence_overrides_explicit_true(monkeypatch)[source]¶
An empty
POSIXLY_CORRECTstill wins over an explicitTrue.Presence alone triggers POSIX parsing (matching GNU getopt), regardless of value, and it takes precedence over a developer-supplied
allow_interspersed_args=True.- Return type:
- tests.test_context.test_posixly_correct_stops_option_parsing_at_first_argument()[source]¶
End-to-end: parsing stops at the first positional under POSIXLY_CORRECT.
Without the variable, the option interleaves with arguments (GNU style). With it set, the first positional ends option parsing, so the option keeps its default and the remaining tokens fall into the variadic argument.
- Return type:
- tests.test_context.test_lazy_meta_dict_resolves_on_first_access()[source]¶
Reading a lazy key triggers exactly one source attribute access.
- Return type:
- tests.test_context.test_lazy_meta_dict_caches_after_resolution()[source]¶
Subsequent reads of the same lazy key do not re-hit the source.
- Return type:
- tests.test_context.test_lazy_meta_dict_get_resolves_lazy_key()[source]¶
.get(lazy_key)triggers resolution like__getitem__.- Return type:
- tests.test_context.test_lazy_meta_dict_get_returns_default_for_unknown_key()[source]¶
.get(unknown, default)returns the default without touching source.- Return type:
- tests.test_context.test_lazy_meta_dict_contains_includes_lazy_keys_without_resolving()[source]¶
inreturnsTruefor declared lazy keys before any resolution.- Return type:
- tests.test_context.test_lazy_meta_dict_preserves_base_entries()[source]¶
Entries from the wrapped base dict remain accessible.
- Return type:
- tests.test_context.test_lazy_meta_dict_independent_keys_resolve_independently()[source]¶
Resolving one lazy key does not resolve sibling lazy keys.
- Return type:
- tests.test_context.LAZY_ENTRIES = {'click_extra.alpha': 'alpha-value', 'click_extra.beta': 'beta-value', 'existing.key': 'existing-value'}¶
Every entry of the lazy dicts below once resolved, in iteration order.
- tests.test_context.test_lazy_meta_dict_lists_pending_keys_without_resolving()[source]¶
len(), iteration andkeys()count the keysinclaims.Listing a key does not read its value, so nothing resolves.
- Return type:
- tests.test_context.test_lazy_meta_dict_reads_pending_values(read)[source]¶
Whatever reads every value resolves the pending keys, and sees them all.
- Return type:
- tests.test_context.test_lazy_meta_dict_equality_reads_pending_values()[source]¶
Equality compares every entry, pending ones included, on both sides.
- Return type:
- tests.test_context.test_lazy_meta_dict_copy_keeps_pending_keys_pending()[source]¶
A copy lists the pending keys, and resolves them only when read.
- Return type:
- tests.test_context.test_lazy_meta_dict_removed_key_stays_removed(remove, removed)[source]¶
A pending key removed from the mapping does not come back.
- Return type:
- tests.test_context.test_lazy_meta_dict_pop_returns_pending_values()[source]¶
pop()andpopitem()resolve the pending key they remove.- Return type:
- tests.test_context.test_lazy_meta_dict_setdefault_resolves_pending_key()[source]¶
setdefault()keeps the value of a pending key rather than the default.- Return type:
- tests.test_context.test_lazy_meta_dict_wraps_another_without_resolving()[source]¶
Wrapping a lazy dict, as each
--versionof a command tree does, keeps its pending keys pending.- Return type:
- tests.test_context.test_pass_context_typed_for_enhanced_context(invoke)[source]¶
@pass_context accepts a handler typed with the enhanced Context.
Click’s own pass_context is typed for the base click.Context, so annotating the handler with click-extra’s Context (to reach its extra helpers) would fail static type checks by parameter contravariance. This must both type-check (mypy covers this file) and forward the active enhanced Context at runtime.
tests.test_docs_anchors module¶
Every Markdown fragment link resolves in a raw-Markdown reader.
Three slug algorithms compete over the same headings. docutils.nodes.make_id
derives the ids Sphinx renders, and myst_heading_slug_func in docs/conf.py
points MyST at it, so a link written parameters.md#params-option is correct
on the published site. GitHub, and the lychee checker that models it, slug
the raw Markdown instead. They keep the leading -- and the underscores that
make_id strips or folds, drop a dot where it writes a hyphen, and write one
hyphen per space where it writes one per run. The same link thus finds nothing
when the page is read on GitHub.
Neither reader can be dropped, so a heading whose two slugs disagree carries
an <a name="…"> companion holding the make_id spelling. That HTML anchor
is invisible to Sphinx (it renders no id, so the page keeps one id per
section) and authoritative for GitHub and lychee.
Docstrings take a third path to the site. click_extra.sphinx.myst_docstrings
passes their link targets to reST unchanged. A page.md#anchor target, which
myst-parser resolves in a page, thus ships as written, and the site serves no
.md file. So a docstring links a page with :doc: and a section with :ref:.
The check runs on text alone: no build, no network, no platform floor. Its
counterpart on the built HTML is test_sphinx_crossrefs.py, which is skipped
off Linux, and the check-broken-links CI job, which files an issue instead
of failing a run. Both let a broken anchor sit for a full cycle.
- tests.test_docs_anchors.lychee_excludes()[source]¶
Read the link patterns
[tool.lychee]waives, so both checkers agree.The palette anchors of
docs/theme.mdare the ones that matter here: a{python:render}loop emits their headings at build time, so no reader of the raw Markdown can see them.
- tests.test_docs_anchors.closes_fence(marker, opening)[source]¶
Tell whether a line’s fence
markercloses the blockopeningstarted.- Return type:
- tests.test_docs_anchors.uncoded_lines(content, keep_directive_bodies=False)[source]¶
Number and return the lines sitting outside a code fence.
lychee ignores fenced content, so a link in an example is not a link. The docstring converter renders the body of a directive fence like
:note:, sokeep_directive_bodiesreturns those lines, without the fence markers.
- tests.test_docs_anchors.uncoded_spans(line)[source]¶
Drop a line’s code spans, so a link or anchor quoted in prose is not read.
Headings are slugged from the raw line instead: GitHub keeps the text of a code span in a heading’s slug.
- Return type:
- tests.test_docs_anchors.github_slug(heading)[source]¶
Slug a heading the way GitHub does, which is what lychee models.
Lowercase, drop every character that is neither a word character, a space nor a hyphen, then replace each space with a hyphen. Backticks vanish with the rest of the punctuation, so a leading
--and an underscore survive, and a dot goes with no separator. Each space gives its own hyphen: a/dropped from between two spaces leaves--, wheremake_idwrites one-.- Return type:
- tests.test_docs_anchors.readable_anchors(path)[source]¶
Collect every fragment a raw-Markdown reader can reach on a page.
Duplicate heading slugs are numbered the way GitHub numbers them, so a heading repeated three times answers to
slug,slug-1andslug-2.
- tests.test_docs_anchors.unresolved_fragments(paths=(PosixPath('/home/runner/work/click-extra/click-extra/AGENTS.md'), PosixPath('/home/runner/work/click-extra/click-extra/changelog.md'), PosixPath('/home/runner/work/click-extra/click-extra/readme.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/benchmark.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/binaries.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/carapace.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/changelog.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/cli.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/click_extra.capture.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/click_extra.config.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/click_extra.markdown.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/click_extra.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/click_extra.sphinx.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/code-of-conduct.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/colorize.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/commands.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/config-discovery.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/config-formats.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/config-schema.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/config-validation.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/config.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/context.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/contributing.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/decorators.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/envvar.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/execution.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/index.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/install.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/layout.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/license.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/logging.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/machine-readable.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/man-page.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/mkdocs.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/multicall.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/myst-docstrings.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/packaging.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/parameters.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/pygments.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/pytest.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/python-directives.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/screenshots.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/snippets.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/sphinx.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/spinner.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/styling.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/table.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/telemetry.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/test-suite.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/testing.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/tests.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/tests.mkdocs.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/tests.sphinx.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/theme.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/todolist.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/tree.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/tutorial.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/typer.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/types.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/upstream.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/version.md'), PosixPath('/home/runner/work/click-extra/click-extra/docs/wrap.md')))[source]¶
Report every fragment link no raw-Markdown reader can follow.
Each entry names the source location, the target and the fragment, so the failure says where to add the missing
<a name="…">.
- tests.test_docs_anchors.rendered_prose(path)[source]¶
Collect the prose autodoc renders from a module, with the line it starts on.
Every bare string statement counts, so attribute docstrings join the module, class and function ones, and so does every
#:comment. A docstring loses its indentation after the first line, so a fence opens at the margin as it does in a page.
- tests.test_docs_anchors.docstring_page_links(paths=(PosixPath('/home/runner/work/click-extra/click-extra/click_extra/__init__.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/__main__.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/_utils.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/accessibility.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/ansi.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/blocks.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/capture/__init__.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/capture/html.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/capture/presets.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/capture/recording.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/capture/screenshot.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/capture/snippet.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/capture/svg.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/carapace.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/cli.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/cli_capture.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/cli_prebake.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/cli_wrapper.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/color.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/columns.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/command_doc.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/commands.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/__init__.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/builtin.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/discovery.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/export.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/flags.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/formats.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/option.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/schema.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/subcommands.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/context.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/decorators.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/envvar.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/execution.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/highlight.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/humanize.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/jobs.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/layout.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/logging.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/logo.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/markdown/__init__.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/markdown/blocks.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/markdown/myst.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/mkdocs.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/multicall.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/output.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/param_tools.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/parameters.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/prebake.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/progress.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/project.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/pygments.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/pytest.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/show_params.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/__init__.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/_base.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/announcement.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/changelog.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/click.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/click_reference.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/click_runner.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/crosshair.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/doctrees.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/fail_on_warnings.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/linkcheck.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/manpages.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/matrix.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/mermaid.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/myst_docstrings.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/python.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/tables.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/tabs.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/theme_docs.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/todos.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/viewcode.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/spinner.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/spinner_presets.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/styling.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/table.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/table_options.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/telemetry.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/test_suite.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/testing.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/theme.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/trail.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/tree.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/types.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/version.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/version_facts.py')))[source]¶
Report every docstring link that names a page by its
.mdfile.
- tests.test_docs_anchors.test_markdown_files_are_collected()[source]¶
The glob feeding the check still finds the documentation.
- tests.test_docs_anchors.test_fragment_links_resolve_in_raw_markdown()[source]¶
No fragment link dangles for a reader of the raw Markdown.
A failure means the target heading slugs differently on GitHub than
make_idspells it. Add<a name="{fragment}"></a>above that heading, rather than rewording the link: themake_idspelling is what the built site renders.
- tests.test_docs_anchors.test_github_slug(heading, expected)[source]¶
The slugger reproduces the spellings that diverge from
make_id.Every case is a heading of this documentation whose two slugs disagree, which is what makes its
<a name="…">companion necessary.
- tests.test_docs_anchors.test_uncoded_spans(line, expected)[source]¶
A span goes whatever its backtick count, and a lone backtick stays.
- tests.test_docs_anchors.test_code_spans_hold_no_link_and_no_anchor(tmp_path)[source]¶
A link or an anchor quoted in a code span is prose, as lychee reads it.
- tests.test_docs_anchors.test_package_sources_are_collected()[source]¶
The glob feeding the docstring check still finds the package.
tests.test_docs_api module¶
Every public module is indexed on the API page of its package.
sphinx-apidoc writes the API pages: docs/click_extra.md and one page per
subpackage, plus the same set for tests. It writes each page once. A later
run keeps the page already in place, as
click_extra.markdown.myst.convert_rst_files_in_directory() does, so a
module added afterwards never reaches its API page. Its only automodule block
then tends to land on a guide page, and the build accepts that without a
warning.
So the API page owns the index entry of each module, and every guide-page block
carries :no-index:. This test reads the source trees and the API pages as
text, and fails naming each public module that no API page indexes. The check
imports nothing, so it needs none of the optional dependencies a module pulls
in.
- tests.test_docs_api.public_modules(package)[source]¶
Name every public module of
package, read from its source tree.A name with an underscore-prefixed part is private, and so is everything below it:
click_extra._utils,click_extra.sphinx._baseandclick_extra.__main__are all skipped. A package is named by its__init__.py.
- tests.test_docs_api.api_pages(package)[source]¶
Return the API pages of
package, in the documentation tree.
- tests.test_docs_api.indexed_modules(pages)[source]¶
Collect each module an
automoduledirective indexes onpages.A block carrying
:no-index:registers no index entry, so it does not count.
- tests.test_docs_api.test_package_is_collected(package)[source]¶
The globs feeding the check still find the modules and their API pages.
- tests.test_docs_api.test_deprecation_shims_still_exist()[source]¶
An exemption goes in the same change that deletes its shim.
- tests.test_docs_api.test_every_module_is_indexed_on_an_api_page(package)[source]¶
No public module is left to a guide page, or to no page at all.
A failure names a module that no API page indexes. Add its section to the API page of its package, in alphabetical order with the others, and give any guide-page block of the same module
:no-index:.
tests.test_docs_directives module¶
Every {click:run} block pins the outcome of the CLI it invokes.
The directive executes its body at build time through a bare exec()
(click_extra.sphinx.click_runner), so any exception a block raises aborts
sphinx-build. That makes the documentation build a real gate, but only over
the blocks that raise: a block asserting nothing renders whatever the CLI
printed, and a broken invocation prints a usage error into the published page
while the build stays green. A stale option has shipped that way.
So the build cannot be the only check, and this test supplies the half it
misses: it reads the Markdown as text and requires each block to carry at
least one assertion. What that assertion pins is the author’s call. An exact
result.exit_code is the strongest form, a containment check on
result.stdout still fails when the CLI errors instead of running, and either
one turns a silent page-level regression into a red build.
The check runs on text alone: no build, no network, no platform floor. It
parses the fences through click_extra.markdown.blocks.fence_spans(), which
consumes a fence as an opaque unit, so a {click:run} example quoted inside a
longer code-block fence is never mistaken for a live directive.
- tests.test_docs_directives.run_blocks(path)[source]¶
Return each
{click:run}body inpath, as(line number, source).The leading
:option:lines the directive consumes are dropped, so the remainder parses as plain Python. Line numbers are 1-based, naming the opening fence.
- tests.test_docs_directives.test_markdown_files_are_collected()[source]¶
The glob feeding the check still finds the documentation.
tests.test_envvar module¶
- tests.test_envvar.test_machine_envvars_apply_to_the_built_in_default_only(invoke, args, envvar, default_map, expected)[source]¶
A machine-wide variable fills in only for a value nothing else set.
- tests.test_envvar.test_show_auto_envvar_help(invoke, cmd_decorator, option_help)[source]¶
Check that the auto-generated envvar appears in the help screen with the extra variants.
Checks that https://github.com/pallets/click/issues/2483 is addressed.
- tests.test_envvar.test_auto_envvar_parsing(invoke, cmd_decorator, envvars, expected_flag)[source]¶
This test highlights the way Click recognize and parse envvars.
It shows that the default behavior is not ideal, and covers how
commandimproves the situation by normalizing the envvar name.
- tests.test_envvar.test_empty_envvar_falls_back_to_the_default(invoke, cmd_decorator, default)[source]¶
An empty variable is read as unset, so the flag keeps its own default.
Pins the rule the
""row ofenvvars_test_cases()relies on:Parameter.resolve_envvar_valueguards its lookup with a bareif rv, so an empty value never reaches the flag’s type. TheFalsethat row expects is the option’s default showing through, which is why flipping the default flips the outcome with it.
- tests.test_envvar.test_env_copy_removes_on_none(monkeypatch)[source]¶
A
Nonevalue drops its variable, the way Click’sCliRunnerreads it.The only way to hide an inherited variable from a child: assigning the empty string leaves it set, which
parse_envvar_flag()counts as activation.
tests.test_execution module¶
Tests for the execution-control options (–time, -0/–zero-exit) and the subprocess-execution primitives (run_cli and the interrupt machinery).
- tests.test_execution.test_die_by_sigint_skips_a_stream_refusing_a_reentrant_flush(monkeypatch)[source]¶
The process still dies when a stream is mid-write in the interrupted code.
The handler of a second Ctrl+C can run inside a write to
stderr, whose buffer then refuses the flush with aRuntimeError.
- tests.test_execution.NAP_CLI = '\nimport time\n\nimport click_extra\n\n\n@click_extra.command\ndef nap():\n print("ready", flush=True)\n time.sleep(30)\n\n\nnap()\n'¶
A CLI that announces it is running, then sleeps until interrupted.
- tests.test_execution.PARSE_NAP_CLI = '\nimport time\n\nimport click_extra\n\n\ndef soak(ctx, param, value):\n print("ready", flush=True)\n time.sleep(30)\n\n\n@click_extra.command\n@click_extra.option("--beans", expose_value=False, callback=soak)\ndef stew():\n pass\n\n\nstew()\n'¶
A CLI that sleeps while it parses its options, before its context is entered.
- tests.test_execution.interrupt_cli(tmp_path, script, presses=1, launcher=())[source]¶
Run
scriptin a child, press Ctrl+Cpressestimes once it is ready.launcherholds the interpreter arguments that go before the script, to run it through another CLI.A further press waits for the child to say what one does, so it lands once the child handles it. A child that never says so is killed.
- tests.test_execution.test_ctrl_c_ends_the_process_by_sigint(tmp_path, script)[source]¶
A real Ctrl+C prints Click’s
Aborted!, then ends the process bySIGINT, which a calling shell needs to stop its own loop: a status of1, or even130, tells the shell the program handled the interrupt itself.
- tests.test_execution.PLAIN_NAP_CLI = '\nimport time\n\nimport click\n\n\n@click.command()\ndef nap():\n print("ready", flush=True)\n time.sleep(30)\n\n\nif __name__ == "__main__":\n nap()\n'¶
A plain Click CLI that sleeps until interrupted, for the
wrapcommand to run.
- tests.test_execution.test_ctrl_c_in_a_wrapped_cli_ends_the_process_by_sigint(tmp_path)[source]¶
A CLI run by
wrapaborts under Click’s ownmain(), and the wrapping process still ends bySIGINT.
- tests.test_execution.SLOW_JOBS_CLI = '\nimport threading\nimport time\n\nimport click_extra\nfrom click_extra.jobs import run_jobs\n\nBOTH_IN = threading.Barrier(2)\n\n\ndef bake(tray):\n # Reports once both trays are in, so the interrupt finds two running tasks.\n if BOTH_IN.wait(timeout=10) == 0:\n print("ready", flush=True)\n time.sleep(30)\n return tray\n\n\n@click_extra.command\ndef oven():\n list(run_jobs(bake, [0, 1], jobs=2))\n\n\noven()\n'¶
A CLI whose two parallel tasks outlast any reasonable wait.
- tests.test_execution.SLOW_THREAD_CLI = '\nimport threading\nimport time\n\nimport click_extra\n\n\n@click_extra.command\ndef kettle():\n threading.Thread(target=time.sleep, args=(30,)).start()\n print("ready", flush=True)\n time.sleep(30)\n\n\nkettle()\n'¶
A CLI whose single background thread outlasts any reasonable wait.
- tests.test_execution.test_second_ctrl_c_quits_while_tasks_finish(tmp_path, script, notice)[source]¶
After a Ctrl+C, the run says it waits for its running tasks, and what a second Ctrl+C does: quit at once, instead of waiting for the tasks.
- tests.test_execution.LOCKED_PRESS = '\nimport signal\n\nimport click\n\nfrom click_extra import execution\n\n{setup}\nwith execution._LIVE_PROCESSES_LOCK:\n signal.raise_signal(signal.SIGINT)\n'¶
A Ctrl+C that lands while the main thread holds the process registry lock.
- tests.test_execution.test_ctrl_c_while_the_registry_lock_is_held(tmp_path, setup)[source]¶
A Ctrl+C ends the process even when the main thread holds the registry lock.
Both handlers stop the running subprocesses, which takes that lock. The main thread takes it too, as when the grace period of an interrupted run ends, and a plain lock would block the handler of a press landing in that moment for good.
- tests.test_execution.STUBBORN_CHILDREN_CLI = '\nimport os\nimport sys\nimport threading\nimport time\n\nimport click_extra\nfrom click_extra import execution\nfrom click_extra.execution import run_cli\nfrom click_extra.jobs import run_jobs\n\nexecution._TERMINATE_GRACE = 0.5\n\nCHILD = (\n "import os, signal, sys, time;"\n "signal.signal(signal.SIGINT, signal.SIG_IGN);"\n "signal.signal(signal.SIGTERM, signal.SIG_IGN);"\n "open(sys.argv[1], \'w\', encoding=\'UTF-8\').write(str(os.getpid()));"\n "time.sleep(30)"\n)\nPID_FILES = [os.path.join(FOLDER, f"child-{n}.pid") for n in range(2)]\n\n\ndef spawn(pid_file):\n return run_cli((sys.executable, "-c", CHILD, pid_file))\n\n\ndef announce():\n while not all(os.path.exists(path) for path in PID_FILES):\n time.sleep(0.05)\n print("ready", flush=True)\n\n\n@click_extra.command\ndef stubborn():\n threading.Thread(target=announce, daemon=True).start()\n list(run_jobs(spawn, PID_FILES, jobs=2))\n\n\nstubborn()\n'¶
A CLI fanning out two children that ignore both
SIGINTandSIGTERM.
- tests.test_execution.test_interrupted_run_kills_children_ignoring_sigterm(tmp_path)[source]¶
One Ctrl+C is enough: the aborting run sends
SIGTERMto the children ofrun_cli, thenSIGKILLto the ones still running after the grace.
- tests.test_execution.STUBBORN_GRANDCHILDREN_CLI = '\nimport os\nimport sys\nimport threading\nimport time\n\nimport click_extra\nfrom click_extra import execution\nfrom click_extra.execution import run_cli\nfrom click_extra.jobs import run_jobs\n\nexecution._TERMINATE_GRACE = 0.5\n\nGRANDCHILD = (\n "import os, signal, sys, time;"\n "signal.signal(signal.SIGINT, signal.SIG_IGN);"\n "signal.signal(signal.SIGTERM, signal.SIG_IGN);"\n "open(sys.argv[1] + \'.tmp\', \'w\', encoding=\'UTF-8\').write(str(os.getpid()));"\n "os.replace(sys.argv[1] + \'.tmp\', sys.argv[1]);"\n "time.sleep(30)"\n)\nCHILD = (\n "import signal, subprocess, sys, time;"\n "signal.signal(signal.SIGINT, signal.SIG_IGN);"\n "signal.signal(signal.SIGTERM, signal.CHILD_ON_SIGTERM);"\n "subprocess.Popen((sys.executable, \'-c\', sys.argv[2], sys.argv[1]));"\n "time.sleep(30)"\n)\nPID_FILES = [os.path.join(FOLDER, f"grandchild-{n}.pid") for n in range(2)]\n\n\ndef spawn(pid_file):\n return run_cli(\n (sys.executable, "-c", CHILD, pid_file, GRANDCHILD),\n start_new_session=NEW_SESSION,\n )\n\n\ndef announce():\n while not all(os.path.exists(path) for path in PID_FILES):\n time.sleep(0.05)\n print("ready", flush=True)\n\n\n@click_extra.command\ndef stubborn():\n threading.Thread(target=announce, daemon=True).start()\n list(run_jobs(spawn, PID_FILES, jobs=2))\n\n\nstubborn()\n'¶
A CLI fanning out two children, each with a grandchild that ignores both
SIGINTandSIGTERMand holds the output its child was given.
- tests.test_execution.test_interrupted_run_kills_grandchildren_ignoring_sigterm(tmp_path, new_session, child_on_sigterm)[source]¶
The
SIGKILLof an aborting run reaches what the children ofrun_clistarted, so no grandchild holds the run open or outlives it.
- tests.test_execution.SELF_INTERRUPT_CLI = '\nimport signal\n\nimport click_extra\n\n\n@click_extra.command\ndef nap():\n signal.raise_signal(signal.SIGINT)\n\n\nnap()\n'¶
A CLI that interrupts itself, as a Ctrl+C does, on every platform.
- tests.test_execution.SECOND_INTERRUPT_SCRIPT = '\nimport signal\n\nfrom click_extra import execution\n\nexecution._second_interrupt(signal.SIGINT, None)\n'¶
A process handling the Ctrl+C pressed while an interrupted run waits.
- tests.test_execution.OUTLASTED_GRACE_SCRIPT = '\nimport threading\nimport time\n\nfrom click_extra import execution\n\nexecution._TERMINATE_GRACE = 0.1\nthreading.Thread(target=time.sleep, args=(1,)).start()\nexecution._exit_interrupted()\n'¶
An interrupted run whose only task outlasts the grace given to children.
- tests.test_execution.test_interrupted_run_exits_like_an_unhandled_ctrl_c(tmp_path, script, notice)[source]¶
An interrupted run ends the way an unhandled Ctrl+C does, on Windows too.
The process dies by
SIGINTon POSIX, and exits with the status Windows reports for a Ctrl+C. The tests sending a real Ctrl+C to a child are skipped on Windows, so each script here reaches one step of that exit on its own: the whole path of a command, the second Ctrl+C, and the kill of the children still running after the grace, for which Windows has noSIGKILL.
- tests.test_execution.PORTABLE_SIGNALS = frozenset({'SIGABRT', 'SIGFPE', 'SIGILL', 'SIGINT', 'SIGSEGV', 'SIGTERM'})¶
The signal numbers Python defines on every platform, Windows included.
- tests.test_execution.POSIX_BRANCHES = {'is_windows()': 'orelse', 'not is_windows()': 'body'}¶
The branch only POSIX takes, for each platform check an
ifcan test.
- tests.test_execution.unguarded_posix_signals(source)[source]¶
Lines of
sourcethat read a signal number Windows does not define.signal.SIGKILLraisesAttributeErroron Windows, as every signal number outsidePORTABLE_SIGNALSdoes. So a module may read one only where Windows never goes: in theelseof anis_windows()check, or under anot is_windows()one, of anifstatement or a conditional expression.
- tests.test_execution.test_posix_only_signals_are_read_under_a_platform_check()[source]¶
No module of the package reads a POSIX-only signal number on Windows.
Windows runs none of the tests that send a real signal, so only the sources can tell.
unguarded_posix_signalsstates the rule.
- tests.test_execution.test_posix_only_signal_scan_finds_the_package_and_an_unguarded_read()[source]¶
The scan above reads the package, and tells a guarded read from a bare one.
- tests.test_execution.test_simulated_interrupt_keeps_click_exit_status(invoke)[source]¶
A
KeyboardInterruptraised by code, as a test runner does, keeps Click’sAborted!and status1: only a real Ctrl+C ends the process bySIGINT.
- tests.test_execution.test_abort_notice_precedes_close_callbacks(invoke, abort, notice)[source]¶
Aborted!prints as soon as the abort leaves the command: before the close callbacks of its context and of its parents, where Click prints it after them.
- tests.test_execution.test_abort_caught_around_an_invoked_command_prints_no_notice(invoke)[source]¶
A command run through
ctx.invoke()leaves its caller free to catch the abort: a run that goes on prints noAborted!.
- tests.test_execution.test_abort_without_standalone_mode_prints_no_notice(invoke)[source]¶
Without standalone mode, Click raises the abort to the caller, which reports it its own way: no
Aborted!is printed.
- tests.test_execution.test_standalone_timer_option(invoke, cmd_decorator, option_decorator, assert_output_regex)[source]¶
- tests.test_execution.test_nested_run_of_a_timed_command_keeps_the_outer_start(invoke)[source]¶
A run started from within another run of the same command times itself, and the outer run reports the time since its own start.
- tests.test_execution.test_time_with_short_circuit_sibling_still_prints(invoke)[source]¶
--time --versionstill emits a duration.--versionis an eager option that callsctx.exit()before the user command body runs, but--timeis intentionally measured even on short-circuit paths so it can probe the cost of Click Extra’s own machinery (eager callbacks, config loading, option parsing).
- tests.test_execution.test_standalone_zero_exit_option(invoke, cmd_decorator, option_decorator)[source]¶
- tests.test_execution.test_run_cli_returns_completed_process(caplog)[source]¶
run_cli mirrors subprocess.run’s result shape, with separate streams.
- tests.test_execution.test_run_cli_cwd_moves_the_child(tmp_path)[source]¶
cwdruns the child elsewhere; omitted, it inherits the caller’s.
- tests.test_execution.test_run_cli_flattens_nested_args(caplog)[source]¶
Nested iterables are flattened, None dropped, and elements stringified.
- tests.test_execution.test_run_cli_discloses_command_at_info(caplog)[source]¶
The invocation is logged up front at INFO, with its forced env vars, and the output stays out of the INFO records.
- tests.test_execution.test_run_cli_command_level_override(caplog)[source]¶
A caller can lower the disclosure line to DEBUG for internal probes.
- tests.test_execution.test_run_cli_streams_output_at_debug_with_label(caplog)[source]¶
Every output line is forwarded to the logger, tagged with the label.
The tag rides the record’s
labelattribute, not the message text: the defaultclick_extra.logging.Formatterrenders it glued to the level name (debug:probe: line1). The prompt line carries it too, so the command and its output read as one call when several children interleave.
- tests.test_execution.test_highlight_bin_name()[source]¶
Only the binary’s own name is styled; its directory stays plain, whichever separator convention the path uses.
- tests.test_execution.test_format_cli_prompt_styles_token_families()[source]¶
Each token family gets the theme slot it holds elsewhere in a CLI’s output: dim sigil, envvar/default assignment pairs, the binary name as an invoked command (directory plain), option-styled flags, plain arguments.
- tests.test_execution.test_format_cli_prompt_arguments_survive_a_shell_round_trip(argv)[source]¶
A drawn command line parses back to the arguments that produced it.
The line advertises itself as copy-pasteable, so every argument carrying a space or a shell metacharacter has to reach the shell as the single argument it started as, instead of spilling into the line as several.
- tests.test_execution.test_format_cli_prompt_environment_values_survive_a_shell_round_trip()[source]¶
An assignment prefixing the command is quoted like an argument.
A value holding a space ends the assignment early otherwise, and the rest of it reads as the command to run.
- tests.test_execution.test_format_cli_prompt_unsets_a_variable_mapped_to_none()[source]¶
A
Nonevalue removes its variable from the child, whichenv -usays.Drawn as an assignment, it read
LANGUAGE=None, and a pasted copy then set the variable to the stringNoneinstead of removing it.
- tests.test_execution.test_format_cli_prompt_honors_an_explicit_theme()[source]¶
A caller drawing the line onto a surface of its own picks the palette.
Every slot follows, the binary name included: that one is styled a level down, in
highlight_bin_name(), which would otherwise read the active theme on its own and leave a light capture’s prompt in the dark theme’s near-white.
- tests.test_execution.test_run_cli_merged_streams()[source]¶
merge_stderr interleaves stderr into stdout and nulls the stderr field.
- tests.test_execution.test_run_cli_timeout_kills_child_and_attaches_partial_output()[source]¶
An overrun raises TimeoutExpired carrying what was captured so far, and leaves no zombie in the live registry.
- tests.test_execution.test_run_cli_handles_an_interrupt_tripped_as_the_wait_began()[source]¶
A Ctrl+C the interpreter noted as the main thread blocked on the child is handled within the poll period, not when the child exits.
interrupt_main()leaves the interrupt pending without waking the main thread: the state aSIGINTleaves when it lands after the interpreter last checked for one and before the thread blocked in the C library. The child never receives it, like a child in a session of its own, or one with no console on Windows.
- tests.test_execution.test_run_cli_kill_survives_a_second_interrupt(monkeypatch, code, stop_signals)[source]¶
A Ctrl+C landing while the timeout reads the process table waits for the signals to land, so the child never outlives the interrupt.
The timeout sends
SIGTERMfirst, which a child may die of. A child ignoring it dies of theSIGKILLthat the interrupt brings forward.
By default the child stays in the caller’s process group: it keeps the controlling terminal (an interactive
sudoraised from inside the child must be able to prompt on/dev/tty) and receives the terminal’s signals with the rest of the foreground group.
- tests.test_execution.test_run_cli_new_session_makes_child_group_leader()[source]¶
With start_new_session the child leads its own session and process group, whose ID is its own PID: the property every group-kill path relies on.
- tests.test_execution.GRANDCHILD_SESSIONS = MarkDecorator(mark=Mark(name='parametrize', args=('grandchild_session', (ParameterSet(values=(False,), marks=(), id='same-group'), ParameterSet(values=(True,), marks=(), id='own-session'))), kwargs={}))¶
Run a group-kill test on a grandchild inside the child’s group and outside it.
- tests.test_execution.test_run_cli_timeout_new_session_kills_grandchildren(grandchild_session)[source]¶
A timed-out start_new_session child takes its whole process tree down: the grandchild is reaped along with it instead of surviving as an orphan holding the inherited output pipe open.
- tests.test_execution.test_run_cli_registers_live_process_then_discards_it()[source]¶
run_cli tracks its subprocess while it runs, and drops it once done.
A background call parks in a real subprocess. Once it is registered, terminate_live_processes() unblocks it, and run_cli’s
finallyclears the registry: this is the exact path the SIGINT handler drives on Ctrl+C.
- tests.test_execution.test_terminate_live_processes_signals_whole_group(tmp_path, grandchild_session)[source]¶
Interrupting a start_new_session child reaps its grandchild too: the group never received the terminal’s SIGINT (it left the foreground group), so terminate_live_processes() is its only kill path and must cover the descendants.
The child reports its grandchild’s PID through a sentinel file, polled before tearing down, so the group is never signalled mid-spawn. The sentinel is published by rename: a bare
open(..., "w")creates the file before the write is flushed, so polling for existence could tear the group down between creation and flush, leaving an empty file behind forint()to choke on.
- tests.test_execution.test_kill_signal_reaches_a_grandchild_holding_the_output(tmp_path)[source]¶
The kill signal ends what a child started too, on every platform.
The grandchild holds the pipe
run_clireads, so the call returns only once the grandchild is gone: a prompt return is the evidence.
- tests.test_execution.test_run_cli_timeout_kills_grandchildren_without_a_session(grandchild_session)[source]¶
A timed-out child sharing the caller’s process group still takes its grandchild down, killed one PID at a time since no group can be signalled without hitting the test process too.
This is the shape of a shell script running commands of its own. An escalated call has it too, but the command
sudoruns as root is out of reach of the per-PID kill: seetest_run_cli_timeout_sends_sigterm_first.
- tests.test_execution.test_run_cli_timeout_sends_sigterm_first(tmp_path)[source]¶
A timed-out child gets
SIGTERMbefore anySIGKILL, so it can clean up.This is what stops an escalated command:
sudoforwards aSIGTERMto the command it runs as root, which aSIGKILLfrom the caller cannot reach.
- tests.test_execution.test_run_cli_timeout_kills_a_child_ignoring_sigterm()[source]¶
A timed-out child ignoring
SIGTERMis killed once the grace runs out.
- tests.test_execution.test_descendant_pids_walks_the_whole_tree_deepest_first()[source]¶
Every descendant is found whatever group it moved to, and a parent never precedes its own child.
The IDs sit above the largest PID any platform allocates, so none of them can be a process the test runner owns.
- tests.test_execution.test_escaped_process_groups()[source]¶
Only the groups of descendants outside the leader’s group are returned.
The IDs sit above the largest PID any platform allocates, so none of them can be the group of the running test process.
- tests.test_execution.test_terminate_live_processes_ignores_already_reaped()[source]¶
A process gone between snapshot and signal is skipped, not raised on.
- tests.test_execution.test_install_interrupt_handler_terminates_children_and_reraises()[source]¶
The installed SIGINT handler SIGTERMs live children, then raises to abort.
tests.test_highlight module¶
- tests.test_highlight.test_theme_definition()[source]¶
Ensure we do not leave any property we would have inherited from cloup and logging primitives.
- tests.test_highlight.test_cloup_theme_lends_its_slots_to_the_active_theme(invoke)[source]¶
A plain
cloup.HelpThemein the formatter settings renders a help screen.Cloup’s slots come from it, and the slots click-extra adds from the active theme.
- class tests.test_highlight.Priority(*values)[source]¶
Bases:
Enum- LOW = 'low-priority'¶
- HIGH = 'high-priority'¶
- tests.test_highlight.test_option_highlight(opt, expected_outputs)[source]¶
Test highlighting of all option variations: types, defaults, ranges, envvars, choices, flags, metavars, deprecated messages.
- tests.test_highlight.test_extra_deprecated_markers_are_painted()[source]¶
A CLI’s own marker takes the
deprecatedslot, beside Click’s spelling.DEPRECATED_REis the worddeprecatedand nothing else, so a project marking a parameter in its own vocabulary gets no color for it until it declares the marker.
- tests.test_highlight.test_extra_deprecated_marker_overlapping_click_is_painted_once()[source]¶
A declared marker Click also writes is styled by one pass, not two.
- tests.test_highlight.test_deprecation_marker_stays_painted_around_a_keyword(category, keyword)[source]¶
A keyword quoted in a deprecation reason keeps the marker painted.
Cross-reference passes run over text the deprecation pass already painted, and the style each one applies closes with a reset. Unless the surrounding style is re-opened after it, the marker renders plain from the keyword to its closing parenthesis. One case per keyword category: the passes run in sequence, so a hole one of them punches says nothing about the others.
- tests.test_highlight.test_bracket_field_full_combination_styling()[source]¶
Document the runtime styling of a fully-loaded bracket field.
Covers the canonical
[env var: NAME; default: VAL; required]block that combines all three trailing-field labels. The bracket slot styles only the structural tokens: the outer[/], theenv var: `` / ``default: `` labels, and the ``; `` separators, while the value tokens (``NAME,VAL) and therequiredlabel get their own slot styling layered on top. This is the contract referenced inbracket’s docstring.
- tests.test_highlight.test_range_field_does_not_leak_into_following_choice()[source]¶
A range field must not bleed its bracket styling into the next option.
Regression guard: the
[x>=1]field emitted for anIntRange(min=1)option is immediately followed by aChoiceoption whose[apple|mango]metavar supplies a closing]. When the range bound matched\S+it absorbed the field’s own], letting the bracket regex run past the field and dim everything up to the choice metavar’s]. Assert the range field is self-contained and closed, and the following choice keeps its own styling.
- tests.test_highlight.test_bracket_field_inner_slot_fallback_to_bracket()[source]¶
Inner bracket-field slots fall back to
bracketwhen at identity.Contract: a theme that styles only
bracketand leavesenvvar/default/required/range_labelatidentityshould still render the whole bracket field with the bracket styling: value tokens inherit it rather than rendering plain. Specific inner slots override piecemeal.
- tests.test_highlight.test_bracket_field_inner_slot_override_takes_precedence()[source]¶
When a theme sets one inner slot, only that token uses it.
Complements
test_bracket_field_inner_slot_fallback_to_bracket(): a theme that stylesbracket+ onlyrequired(leavingenvvaranddefaultat identity) should render therequiredlabel with the dedicated style and fall back tobracketfor the envvar and default values.
Ensure hidden options are not highlighted.
- tests.test_highlight.test_cross_ref_highlight_disabled()[source]¶
When
cross_ref_highlightisFalse, only structural elements are styled (bracket fields, deprecated messages, subcommands, choice metavars). Options, choices in free-form text, metavars, arguments, and CLI names are left plain.
- tests.test_highlight.test_choice_does_not_override_default_style()[source]¶
Choice cross-ref highlighting must not restyle text inside bracket fields.
When a default value contains a substring that matches a choice keyword (like
outlinefromrounded-outline), the choice style must not override the default value style. Regression test for the case where line-wrapping splits a hyphenated default so the second word starts a new line and passes the lookbehind.
- tests.test_highlight.test_keyword_wrapped_at_its_dash_is_highlighted_whole()[source]¶
A keyword the renderer wraps after its dash is painted as one run.
- tests.test_highlight.test_choice_collection_case(params, expected, forbidden)[source]¶
Choice keywords must use the original-case strings from the type definition, not the normalized (lowercased) forms produced by
normalize_choice().
- tests.test_highlight.test_argument_highlight(params, expected, forbidden)[source]¶
Argument metavars get the
argumentstyle, distinct from option metavars.
- tests.test_highlight.test_no_false_positive_highlight(params, help_text, expected_present, expected_absent)[source]¶
Verify that highlighting does not leak into compound words, URLs, dotted names, already-styled regions, or partial-word matches.
- tests.test_highlight.test_parent_keywords_highlighted_in_subcommand_help()[source]¶
Parent group names, options, and choices must be highlighted in subcommand help text.
- tests.test_highlight.test_parent_choice_case_with_custom_metavar()[source]¶
Parent choices with custom metavar must use original-case strings in subcommand help, not normalized (lowercased) forms.
- tests.test_highlight.test_command_aliases_collected()[source]¶
Command aliases are collected as keywords for highlighting.
- tests.test_highlight.test_command_aliases_highlighted(invoke)[source]¶
Aliases inside parenthetical groups take the alias slot, and the parentheses and separators the alias_secondary slot.
- tests.test_highlight.test_single_alias_highlighted(invoke)[source]¶
A command with exactly one alias still gets highlighted.
- tests.test_highlight.test_alias_no_false_positive_in_description(invoke)[source]¶
An alias name appearing in a description must not be highlighted when it does not sit inside alias parentheses.
- tests.test_highlight.test_alias_substring_not_highlighted(invoke)[source]¶
An alias that is a substring of the subcommand name must not cause double-highlighting or partial matches.
- tests.test_highlight.test_help_keywords_merge(base_kwargs, other_kwargs, checks)[source]¶
HelpKeywords.merge() unions every field.
- tests.test_highlight.test_help_keywords_subtract(base_kwargs, removals_kwargs, checks)[source]¶
HelpKeywords.subtract() removes matching entries per field.
- tests.test_highlight.test_extra_keywords_merged()[source]¶
extra_keywords injects additional strings into the collected set.
- tests.test_highlight.test_excluded_keywords_preserved_in_collection()[source]¶
excluded_keywords does not remove from collect_keywords().
Exclusion is deferred to highlight_extra_keywords() so that choice metavars can be styled with the full choices set before the excluded choices are removed for cross-ref passes.
- tests.test_highlight.test_excluded_keywords_via_constructor()[source]¶
excluded_keywords can be passed through the Command constructor.
- tests.test_highlight.test_excluded_keywords_suppresses_highlighting()[source]¶
Excluded keywords do not appear styled in the rendered help text.
- tests.test_highlight.test_style_choice_metavar(metavar, choices, expected)[source]¶
style_choice_metavar styles known choices inside bracket-delimited metavar strings, styles non-choice parts (type placeholders) as metavars, and returns None for non-bracket strings.
- tests.test_highlight.test_multiple_choice_options_metavar_styled()[source]¶
Each choice option gets its metavar individually styled.
- tests.test_highlight.test_jobs_hybrid_metavar_highlights_keywords_and_placeholder()[source]¶
The hybrid
--jobs [auto|max|INTEGER]metavar highlights every part.Regression guard:
JobCountis a customParamType, not aclick.Choice, so the keyword collector learns itsauto/maxkeywords only through thechoicesattribute it exposes. With no other option contributing these tokens (a bareJobsOptioncommand pulls in no--color),auto/maxcan only be styled viaJobCount, and theINTEGERplaceholder renders as a metavar rather than staying plain.
- tests.test_highlight.test_excluded_multiple_choices_styled_in_metavar_only()[source]¶
Multiple excluded choices appear styled in their own metavar but not in free-text descriptions.
- tests.test_highlight.test_excluded_keywords_inheritance(parent_excluded, child_excluded, word, expect_styled)[source]¶
excluded_keywords propagate from parent groups to subcommands.
Parent choices are collected for subcommand help screens (cross-ref highlighting). The parent’s excluded_keywords must follow, otherwise excluded choices bleed into subcommand descriptions.
- tests.test_highlight.test_excluded_keywords_grandparent_propagation()[source]¶
excluded_keywords propagate through multiple nesting levels.
- tests.test_highlight.test_excluded_keywords_plain_click_group_parent()[source]¶
A plain click.Group parent without excluded_keywords does not crash.
- tests.test_highlight.test_excluded_keywords_not_mutated()[source]¶
Calling format_help must not mutate the command’s excluded_keywords.
- tests.test_highlight.test_standalone_help_option(invoke, cmd_decorator, cmd_type, option_decorator)[source]¶
- tests.test_highlight.test_invoked_subcommand_is_highlighted_and_prose_is_not()[source]¶
An example line running the CLI paints the subcommands it names.
The same word in the prose describing that subcommand stays unpainted: a subcommand often carries the name of what it does, and reading a whole help screen for it colors plain English.
- tests.test_highlight.test_subcommand_options_are_highlighted_on_the_group_screen()[source]¶
A group screen knows the options of the subcommands it lists.
An example invoking one names options the group itself never declares.
- tests.test_highlight.test_end_of_options_separator_is_styled()[source]¶
A lone
--takes the separator slot; an option name keeps its own.
- tests.test_highlight.test_bracket_field_closes_on_a_bracketed_default()[source]¶
A default value that is itself bracketed closes its own field.
A content pattern stopping at the first
]on screen would leave the field’s real closing bracket outside the styled run.
- tests.test_highlight.test_enumerated_metavar_is_styled_part_by_part()[source]¶
A hand-written metavar that enumerates its parts is painted as one.
Click renders nothing structured for a hybrid type, so
INTEGER|autois a plain string: the value takes the choice slot and the type placeholder beside it the metavar slot.
tests.test_humanize module¶
- tests.test_humanize.test_format_size_iec(size, expected)[source]¶
IEC binary units are the default.
- Return type:
tests.test_hygiene module¶
Conventions the whole code base follows, checked file by file.
- tests.test_hygiene.ROOT = PosixPath('/home/runner/work/click-extra/click-extra')¶
Root of the repository.
- tests.test_hygiene.PYTHON_FILES = (PosixPath('/home/runner/work/click-extra/click-extra/click_extra/__init__.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/__main__.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/_utils.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/accessibility.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/ansi.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/blocks.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/capture/__init__.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/capture/html.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/capture/presets.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/capture/recording.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/capture/screenshot.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/capture/snippet.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/capture/svg.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/carapace.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/cli.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/cli_capture.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/cli_prebake.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/cli_wrapper.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/color.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/columns.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/command_doc.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/commands.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/__init__.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/builtin.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/discovery.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/export.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/flags.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/formats.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/option.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/schema.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/config/subcommands.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/context.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/decorators.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/envvar.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/execution.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/highlight.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/humanize.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/jobs.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/layout.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/logging.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/logo.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/markdown/__init__.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/markdown/blocks.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/markdown/myst.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/mkdocs.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/multicall.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/output.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/param_tools.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/parameters.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/prebake.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/progress.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/project.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/pygments.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/pytest.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/show_params.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/__init__.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/_base.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/announcement.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/changelog.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/click.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/click_reference.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/click_runner.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/crosshair.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/doctrees.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/fail_on_warnings.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/linkcheck.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/manpages.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/matrix.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/mermaid.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/myst_docstrings.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/python.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/tables.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/tabs.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/theme_docs.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/todos.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/sphinx/viewcode.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/spinner.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/spinner_presets.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/styling.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/table.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/table_options.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/telemetry.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/test_suite.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/testing.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/theme.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/trail.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/tree.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/types.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/version.py'), PosixPath('/home/runner/work/click-extra/click-extra/click_extra/version_facts.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/__init__.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/conftest.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/mkdocs/__init__.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/mkdocs/conftest.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/mkdocs/test_mkdocs.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/__init__.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/conftest.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_announcement.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_changelog.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_click.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_click_config.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_click_tree.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_crosshair.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_doctrees.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_fail_on_warnings.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_linkcheck.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_manpages.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_matrix.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_mermaid.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_myst_docstrings.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_python.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_tables.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_tabs.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_todos.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/sphinx/test_sphinx_viewcode.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_accessibility.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_ansi.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_blocks.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_carapace.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_cli_wrapper.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_color.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_command_doc.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_commands.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_config.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_config_discovery.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_config_flags.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_config_schema.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_context.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_docs_anchors.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_docs_api.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_docs_directives.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_envvar.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_execution.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_highlight.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_humanize.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_hygiene.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_jobs.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_layout.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_logging.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_logo.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_multicall.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_myst.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_output.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_parameters.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_prebake.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_progress.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_project.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_pygments.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_pyproject.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_pytest.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_readme.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_recording.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_screenshots.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_snippet.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_sphinx_crossrefs.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_spinner.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_styling.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_table.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_telemetry.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_test_suite.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_testing.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_theme.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_trail.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_tree.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_types.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_utils.py'), PosixPath('/home/runner/work/click-extra/click-extra/tests/test_version.py'))¶
Python sources of the package and its test suite.
- tests.test_hygiene.codec_names(tree)[source]¶
Each codec name an
encoding=argument,encode()ordecode()receives.
tests.test_jobs module¶
Tests for the –jobs option and the run_jobs and run_lanes fan-out primitives.
- tests.test_jobs.test_default_value(invoke)[source]¶
Default reserves one core, except on hosts with fewer than three CPUs.
- tests.test_jobs.test_keyword_resolution(invoke, keyword, expected)[source]¶
‘auto’ resolves to the reserved-core default, ‘max’ to all logical CPUs.
On a host with at least two logical CPUs the resolution is silent; on a single-CPU host the keyword collapses to a single (sequential) job with a warning, so the assertion adapts to the host running the suite.
- tests.test_jobs.test_parallel_keyword_collapses_to_sequential_warns(invoke, keyword, cpu_count, default_jobs, cpu_phrase)[source]¶
‘auto’/’max’ warn when too few logical CPUs force a single (sequential) job.
- tests.test_jobs.test_explicit_single_job_is_silent(invoke)[source]¶
An explicit ‘–jobs 1’ is a deliberate sequential choice: no warning.
- tests.test_jobs.test_default_collapse_to_sequential_is_quiet(invoke)[source]¶
The bare default (‘auto’) collapsing to a single job does not warn.
The user never asked for parallelism: warning on the option’s own default would fire on every bare invocation on a 1-CPU host, polluting captured runner streams and the CLI output rendered in Sphinx docs.
- tests.test_jobs.test_default_collapse_to_sequential_logged_at_info(invoke)[source]¶
The default’s collapse to a single job stays discoverable at info level.
This is the silent trap on a single-CPU host: no flag is passed, yet execution runs sequentially. The trace lives at info level, next to the resolved-jobs line, instead of a default-verbosity warning.
- tests.test_jobs.test_resolved_job_count_logged_at_info(invoke)[source]¶
The resolved job count and os.cpu_count() are logged at info level.
- tests.test_jobs.test_run_jobs_preserves_order(jobs)[source]¶
Results come back in submission order, sequential or parallel.
- tests.test_jobs.test_run_jobs_sequential_is_lazy()[source]¶
With one worker, items run lazily so a caller can stop early.
- tests.test_jobs.test_run_jobs_preserves_order_past_its_window(jobs)[source]¶
Order holds when the stream is longer than the in-flight window.
- tests.test_jobs.test_run_jobs_handles_empty_and_single_streams(jobs)[source]¶
The peek that sizes the run does not lose the items it looked at.
- tests.test_jobs.test_run_jobs_parallel_reads_no_further_than_its_window()[source]¶
The parallel path pulls a bounded window instead of draining the stream.
- tests.test_jobs.test_run_jobs_parallel_stops_scheduling_on_early_exit()[source]¶
Breaking out of a parallel run leaves the rest of the stream unread.
- tests.test_jobs.test_run_jobs_reads_jobs_from_context(invoke)[source]¶
Without an explicit count, run_jobs reads the resolved –jobs value.
- tests.test_jobs.test_run_jobs_without_context_runs_sequential()[source]¶
Outside any Click context and with no count, run_jobs falls back to 1.
- tests.test_jobs.test_run_jobs_interrupt_aborts_without_blocking()[source]¶
A KeyboardInterrupt returns at once, without waiting on in-flight tasks.
Results yield in submission order, so the interrupting item (index 0) is pulled first: the abort fires while a second, still-running item is parked on an event that stays unset for the run. A plain
with-block teardown wouldshutdown(wait=True)and hang on that parked task; the run drops queued work and returns at once.
- tests.test_jobs.test_resolve_jobs_without_context_is_sequential()[source]¶
No context means nothing to read a job count from: stay sequential.
- tests.test_jobs.test_resolve_jobs_single_item_is_sequential()[source]¶
A single item has nothing to parallelize.
- tests.test_jobs.test_resolve_jobs_reads_context(jobs, count, expected)[source]¶
The resolved –jobs value drives the count, capped at the item count.
- tests.test_jobs.test_resolve_jobs_serial_at_debug()[source]¶
serial_at_debug collapses to sequential only at DEBUG verbosity.
- tests.test_jobs.test_run_lanes_preserves_order(jobs)[source]¶
Results come back in lane-submission order, items within a lane in order.
- tests.test_jobs.test_run_lanes_preserves_order_past_its_window(jobs)[source]¶
Lane order holds when there are more lanes than the in-flight window.
- tests.test_jobs.test_run_lanes_materializes_lanes_lazily()[source]¶
A lane becomes a list only when it is about to be scheduled.
- tests.test_jobs.test_run_lanes_is_run_jobs_with_singleton_lanes()[source]¶
run_jobs is the degenerate case of run_lanes: one item per lane.
- tests.test_jobs.test_run_lanes_serializes_within_a_lane()[source]¶
Within a lane, items run one at a time even when lanes run in parallel.
- tests.test_jobs.test_run_lanes_runs_lanes_concurrently()[source]¶
Distinct lanes overlap: a barrier only releases if all lanes run at once.
- tests.test_jobs.test_run_lanes_sequential_is_lazy()[source]¶
With one worker, items run lazily so a caller can stop early.
- tests.test_jobs.test_run_lanes_reads_jobs_from_context(invoke)[source]¶
Without an explicit count, run_lanes reads the resolved –jobs value.
- tests.test_jobs.test_run_lanes_without_context_runs_sequential()[source]¶
Outside any Click context and with no count, run_lanes falls back to 1.
- tests.test_jobs.test_run_lanes_empty_yields_nothing()[source]¶
No lanes, or only empty lanes, yields nothing and raises nothing.
- tests.test_jobs.test_lone_item_or_lane_runs_in_the_calling_thread(run)[source]¶
With nothing to overlap, the work runs in the calling thread, whatever the worker count.
- tests.test_jobs.test_run_lanes_interrupt_aborts_without_blocking()[source]¶
A KeyboardInterrupt returns at once, without waiting on in-flight lanes.
Mirror of
test_run_jobs_interrupt_aborts_without_blocking(): see it for the rationale.
- tests.test_jobs.test_run_lanes_interrupt_stops_each_lane_after_its_current_item()[source]¶
An interrupt lets each running lane finish its current item, then stop.
- tests.test_jobs.test_interrupt_tripped_as_the_wait_began_is_handled_promptly(run)[source]¶
A Ctrl+C the interpreter noted as the main thread blocked on a task is handled within the poll period, not when the task ends.
interrupt_main()leaves the interrupt pending without waking the main thread: the state aSIGINTleaves when it lands after the interpreter last checked for one and before the thread blocked in the C library. An unbounded wait on the task would run the handler only when the task returns.
- tests.test_jobs.test_wait_result_raises_the_interrupt_a_lock_error_replaced(monkeypatch, context, expected)[source]¶
A
RuntimeErrorthe wait raises in place of a Ctrl+C becomes the Ctrl+C again.A free-threaded CPython can raise the interrupt between the wait and the lock it takes back, then fail to release it, as python/cpython#118433 reports. Any other
RuntimeErrorof the wait stays as it is.
- tests.test_jobs.test_run_jobs_reports_a_task_timeout_error()[source]¶
A task raising
TimeoutErrorends the run on it, within the poll period.concurrent.futures.TimeoutErroris the builtinTimeoutErrorfrom Python 3.11 on, which a task raises itself when a socket times out: a wait reading the result with a timeout of its own could not tell the two apart.
- tests.test_jobs.test_invalid_value(invoke)[source]¶
Values that are neither an integer nor a known keyword are rejected.
- tests.test_jobs.test_jobs_shell_complete(incomplete, expected)[source]¶
–jobs completion suggests the auto/max keywords and never an integer.
- tests.test_jobs.test_clamp_to_one(invoke, value, warning)[source]¶
0 disables parallelism and negatives clamp: both run 1 job with a warning.
- tests.test_jobs.test_exceeds_cpu_count(invoke)[source]¶
A count above the core count is honored, with an I/O-bound caveat.
- tests.test_jobs.test_no_warning_within_bounds(invoke)[source]¶
No warning when the value is within the valid range.
- tests.test_jobs.test_default_jobs(cpu_count, expected)[source]¶
The default reserves one logical CPU, from three CPUs up.
- tests.test_jobs.test_default_jobs_of_this_host()[source]¶
The published default follows the rule for the CPUs of this host.
tests.test_layout module¶
Tests for click_extra.layout: measuring a line and composing one.
- tests.test_layout.test_center_in_rule_draws_an_unbroken_line_for_no_label(label)[source]¶
Naming nothing draws a divider, not a frame around an empty middle.
- tests.test_layout.test_center_in_rule_measures_a_styled_label_unstyled()[source]¶
A label’s escapes occupy no cell, so they must not shorten the rule.
- tests.test_layout.test_center_in_rule_paints_the_rule_and_leaves_the_label_alone()[source]¶
The rule recedes by default, and a caller’s own label styling survives.
- tests.test_layout.test_fit_columns_floors_at_nothing_by_default()[source]¶
The floor belongs to whoever lays the text out, not to the measure.
- tests.test_layout.test_cell_width_discounts_what_a_terminal_draws_nowhere(text, cells)[source]¶
Escapes and hyperlinks measure nothing, and a tab measures to its stop.
wcwidth.wcswidthrefuses any string carrying a control character and answers-1: a measure built on it falls back to the character count of a styled string, 16 for a redapricotthe terminal draws in 7.
- tests.test_layout.test_cell_width_is_never_negative()[source]¶
A control character measures what a terminal advances by, never
-1.
- tests.test_layout.test_pad_to_measures_the_text_a_terminal_draws()[source]¶
str.ljustcounts escapes it cannot see, so a styled line pads short.
- tests.test_layout.test_pad_to_closes_a_style_before_the_blanks()[source]¶
A background left open would otherwise bleed across the gap.
- tests.test_layout.test_pad_to_leaves_text_already_wide_enough_alone()[source]¶
Nothing to pad, nothing added, reset included.
tests.test_logging module¶
- tests.test_logging.CUSTOM_FORMAT = '{levelname} | {name} | {message}'¶
A format to tell a logger set up on purpose from the default of Click Extra.
- tests.test_logging.run_handlers()[source]¶
The handlers of Click Extra attached to the root logger right now.
- tests.test_logging.test_root_logger_defaults()[source]¶
Check our internal default is aligned to Python’s root logger.
- tests.test_logging.test_integrated_verbosity_options(invoke, cmd_decorator, cmd_type, args, expected_level, assert_output_regex)[source]¶
- tests.test_logging.test_counter_trace_agrees_with_its_count(invoke, args, trace)[source]¶
The
-v/-qtrace says one level in the singular, and more in the plural.
- tests.test_logging.test_standalone_option_default_logger(invoke, cmd_decorator, option_decorator, args, expected_level, assert_output_regex)[source]¶
Checks: - option affect log level - the default logger is
root- the default logger message format - level names are colored - log level is propagated to all other loggers
- tests.test_logging.test_default_logger_param(invoke, logger_param, params)[source]¶
Passing a logger instance or name to the
default_loggerparameter works.
- tests.test_logging.test_new_logger_name_passing(invoke)[source]¶
Test extra logger with custom format, passed to the option by its name.
- tests.test_logging.test_new_logger_object_passing(invoke)[source]¶
Test extra logger with custom format, passed as an object to the option.
- tests.test_logging.restored_root_logger()[source]¶
Put the root logger back the way the test found it.
new_logger()naming no logger reconfigures the process-wide root one throughbasicConfig(force=True), which drops whatever handlers were already attached. Nothing inloggingundoes that, so a test doing it once decides how every later test in the same worker renders: leaving the handler behind is what madetest_logger_propagationfail whenever this module’s root-logger test ran before it.A run of a plain Click command that failed while parsing keeps its hold on the handler of Click Extra until its context is collected. Collecting first keeps that handler out of the saved list, which would otherwise attach it again once the test is over.
- class tests.test_logging.RecordingHandler[source]¶
Bases:
HandlerStand-in for the handler of an application: keeps the messages it receives.
Initializes the instance - basically setting the formatter to None and the filter list to empty.
- tests.test_logging.app_handler(restored_root_logger)[source]¶
A handler attached to the root logger before any command is defined.
This is the state an application leaves the root logger in before it imports a CLI, and the one pytest leaves it in with its own capture handlers.
- tests.test_logging.test_new_logger_root_config(invoke, restored_root_logger)[source]¶
Modify the root logger via
new_logger()
- tests.test_logging.test_decorating_a_command_leaves_the_root_logger_alone(cmd_decorator, app_handler)[source]¶
Defining a command changes neither the handlers nor the level of
root.A command is defined when its module is imported: the handlers found there belong to whoever imports it.
- tests.test_logging.test_unregistered_logger_name_is_set_up_without_the_root_logger(app_handler)[source]¶
A name unknown to
logginggets a logger of its own when the option is built. The root logger stays out of it.
- tests.test_logging.extra_command(body)[source]¶
A Click Extra command, which carries the verbosity options by default.
- tests.test_logging.extra_group(body)[source]¶
A Click Extra group: its subcommand carries the verbosity options too.
- tests.test_logging.click_command(body)[source]¶
A plain Click command borrowing the verbosity option.
- tests.test_logging.click_command_with_its_own_logger(body)[source]¶
A plain Click command whose verbosity option sets a logger of its own.
- tests.test_logging.click_command_without_verbosity(body)[source]¶
A plain Click command borrowing an option other than the verbosity ones.
- tests.test_logging.test_run_adds_one_handler_beside_the_others(invoke, app_handler, build)[source]¶
A run prints through one handler of Click Extra, added beside the handlers already on the root logger, and gone once the run is over.
The handler of the application stays attached all along, and receives the record too: a console handler there prints it a second time.
- tests.test_logging.test_each_run_gets_the_handler(invoke, app_handler)[source]¶
The handler leaves with a run, and the next run gets one again.
- tests.test_logging.test_record_of_an_earlier_option_is_printed(invoke, app_handler)[source]¶
The handler is attached before Click processes any option, so a record emitted by an option processed ahead of the verbosity ones is printed.
- tests.test_logging.test_record_after_a_run_is_not_printed(invoke, app_handler, capsys)[source]¶
Once a run is over, Click Extra prints nothing of what the process logs.
- tests.test_logging.test_caplog_captures_the_records_of_a_command_defined_in_a_test(invoke, caplog)[source]¶
pytest attaches its capture handlers to the root logger before the test body runs: a command defined in that body must leave them in place.
- tests.test_logging.test_file_handler_of_the_root_logger_keeps_writing(invoke, restored_root_logger, tmp_path)[source]¶
No handler of the root logger is closed, at any point.
A file handler opened in
wmode never opens its file again once closed, which is the mode of the one behind pytest’s--log-file.
- tests.test_logging.test_handler_leaves_however_the_run_ends(invoke, app_handler, args, exit_code)[source]¶
A Click Extra command gives the handler back the moment its run ends, whatever the run ends on.
- tests.test_logging.test_failed_run_of_a_click_command_gives_the_handler_back_when_collected(invoke, app_handler)[source]¶
A plain Click command has no hook on a run that fails while parsing: the handler leaves once Python collects the context of that run.
- tests.test_logging.test_run_keeps_its_handler_when_a_failed_run_is_collected(invoke, app_handler)[source]¶
The context of a failed run may be collected in the middle of a later run, which then still needs the handler they share.
- tests.test_logging.test_failed_run_collected_while_a_record_is_handled(invoke, restored_root_logger)[source]¶
The context of a failed run may be collected while the
rootlogger hands a record to its handlers: the handlers after the one of that run still get it.
A run started from within another one prints through the same handler, and leaves it to the run around it when it ends.
- tests.test_logging.test_resilient_parsing_adds_no_handler(app_handler)[source]¶
Click parses without side effects to complete a command line: the root logger gets no handler then.
- tests.test_logging.test_root_logger_set_up_on_purpose_is_left_as_it_is(invoke, restored_root_logger, set_up, build)[source]¶
A command left on the root logger keeps the format
new_logger()orbasicConfig()gave it, and adds no handler of its own.
- tests.test_logging.test_handlers_given_to_new_logger_replace_the_run_handler(invoke, restored_root_logger)[source]¶
new_logger()given the handlers of the caller is how an application keeps the handler of Click Extra off the root logger.
- tests.test_logging.test_new_logger_replaces_the_handlers_of_the_root_logger(restored_root_logger, force, replaced)[source]¶
Left on its default name,
new_logger()removes and closes every handler of the root logger, unless told not to force.That is its documented contract, and the reason not to call it at import time: the handlers of an application go with the others.
- tests.test_logging.test_new_logger_with_a_name_leaves_the_root_logger_alone(app_handler)[source]¶
Given a name,
new_logger()sets that logger up and no other.
- tests.test_logging.application_levels(restored_root_logger)[source]¶
Give the loggers the levels of an application for the time of a test.
The
rootlogger works atINFO, and theclick_extraone follows it, as it does from the start. None of the two is at the level the verbosity options default to: a test can so tell a level given back from one reset.
- tests.test_logging.levels()[source]¶
The levels a verbosity option sets: of
rootand ofclick_extra.
- tests.test_logging.test_levels_are_given_back_however_the_run_ends(invoke, application_levels, args, exit_code, traced)[source]¶
A Click Extra command gives its loggers the levels they had before the run back the moment it ends, whatever it ends on.
- tests.test_logging.test_logger_of_the_option_gets_its_own_level_back(invoke, application_levels)[source]¶
An option given a logger of its own leaves the level of
rootalone, and gives that logger the level it had.
- tests.test_logging.test_reset_loggers_falls_back_on_the_default_level(application_levels)[source]¶
Called with no level to give back,
reset_loggers()sets the default one.
- tests.test_logging.test_levels_are_given_back_when_a_subcommand_fails_while_parsing(invoke, application_levels)[source]¶
The context of the group closes, the one of its subcommand never does.
The group carries no verbosity option of its own here: one that does queues the reset on its own context, which closes.
- tests.test_logging.test_failed_run_of_a_click_command_gives_its_levels_back_when_collected(invoke, application_levels)[source]¶
A plain Click command has no hook on a run that fails while parsing: the levels come back once Python collects the context of that run.
- tests.test_logging.failing_click_command(contexts, body=None)[source]¶
A plain Click command that keeps the context of each of its runs alive, and fails while parsing when given an invalid
--baskets.
- tests.test_logging.test_run_keeps_its_levels_when_a_failed_run_is_collected(invoke, application_levels)[source]¶
The context of a failed run may be collected in the middle of a later run: the levels are by then the ones of that later run, and stay.
- tests.test_logging.test_levels_found_before_a_failed_run_are_the_ones_given_back(invoke, application_levels)[source]¶
A run that starts on the levels a failed run left behind gives back the ones found before that failed run, not the ones it left.
- tests.test_logging.test_released_handler_put_back_on_the_root_logger_prints_nothing(invoke, app_handler, capsys)[source]¶
Code that saves the handlers of the root logger and puts them back later may attach again a handler its run let go of in between. That handler prints nothing, and the next run takes it off.
A fixture isolating a test does that around a plain Click command that failed while parsing: Python collects the context of that run, and so releases its handler, whenever it sees fit.
- tests.test_logging.test_importing_a_cli_leaves_the_root_logger_alone()[source]¶
In a new interpreter, importing Click Extra and defining a command leave the root logger with the one handler the application gave it.
- tests.test_logging.test_root_logger_prints_in_a_new_interpreter_beside_a_named_logger()[source]¶
A command whose verbosity option sets a named logger still prints the records of the root logger in the style of Click Extra.
Run in a new interpreter: no other command of the process may have given the root logger its handler.
- tests.test_logging.test_stream_handler_honors_no_color_from_background_threads(capsys, monkeypatch)[source]¶
A record emitted with no reachable Click context (as from run_cli’s stream reader threads) still honors –no-color, through the process-wide mirror published by the color options.
- tests.test_logging.test_formatter_renders_label_glued_to_level_name()[source]¶
A record tagged with a
labelattribute (as run_cli’s streamed output lines are) renders it inside the level prefix:debug:mas: message.
tests.test_logo module¶
Test the terminal rendition of the Click Extra brand mark.
The layout it is drawn into belongs to VersionScreen
and is tested in test_version.py. What is checked here is the mark itself: that its
geometry lands on the grid, that its palette follows the system it claims to, and
that both survive the terminal.
- tests.test_logo.SILHOUETTE_FLOOR = 2.0¶
Contrast a face must keep against either background to hold its silhouette.
Below the 3:1 WCAG asks of non-text graphics, deliberately: that threshold assumes a shape whose only cue is its edge against the page, and this one also has two neighbouring planes to be read against. What it rules out is a face disappearing.
- tests.test_logo.ARTWORK = PosixPath('/home/runner/work/click-extra/click-extra/docs/assets/logo-square.svg')¶
The SVG the terminal rendition shares its palette with.
Present in a source checkout and in the sdist, absent from the wheel, which is the whole reason
click_extra.logorepeats the colors instead of reading them.
- tests.test_logo.PLANE_NAMES = {'left': 'l', 'right': 'r', 'top': 't'}¶
How the artwork’s CSS spells the planes
click_extra.logokeys by initial.
- tests.test_logo.test_palette_matches_the_artwork()[source]¶
The terminal mark and the artwork paint the same eighteen colors.
Both are flat-shaded, so nothing calls for two palettes, and nothing but this notices when one is recolored alone.
- tests.test_logo.test_geometry_lands_on_whole_sub_pixels()[source]¶
Every vertex is an integer, so the fill has nothing to round.
A polygon landing between sub-pixels is what turns a straight edge into an irregular stair, and no amount of resolution fixes it.
- tests.test_logo.test_edges_advance_two_sub_pixels_per_row()[source]¶
The lid edges are 2:1, which is the whole reason for the projection.
The artwork is a true 30-degree isometric, whose edges advance 1.732 sub-pixels per row. No grid holds that, so it comes out as a stair of alternating two- and four-wide treads that reads as fraying. Two across per one down tiles exactly.
- tests.test_logo.test_rows_are_seated_not_floating()[source]¶
A cube rests on the two below it, rather than hovering over them.
The artwork’s own row pitch is wider than a stack allows, which shows the background between the rows. A cube meets the pair beneath it where their lids meet, exactly half a lid below their rims.
- tests.test_logo.test_mark_fits_its_declared_box()[source]¶
Every line is exactly as wide as declared, trailing blanks included.
- tests.test_logo.test_palette_follows_its_derivation()[source]¶
Every face sits at its cube’s hue and its plane’s luminance.
The palette is written out rather than computed, so it stays greppable and a designer can tune one value. Nothing but this notices a tuned value drifting off the system it claims to belong to.
- tests.test_logo.test_every_cube_shows_exactly_its_three_faces()[source]¶
The grid carries the eighteen declared colors and nothing between them.
Rasterizing the mark and reading pixels back would let the antialiaser mint colors belonging to no face, over a hundred of them at this size. Hit-testing the polygons is what keeps the count to what the palette declares.
- tests.test_logo.test_mark_keeps_its_distance_from_both_backgrounds(background)[source]¶
One palette has to serve a light terminal and a dark one.
Flat faces are what make that possible: the shape lives in the difference between a cube’s three planes, so only the silhouette has to clear the background, and that fits in the middle of the range.
- tests.test_logo.test_neighbouring_faces_survive_a_256_color_terminal()[source]¶
No two faces that touch collapse onto the same palette index.
The mark is emitted in 24-bit color, so a terminal that downsamples is the degraded case rather than the target. It still has to show six cubes.
- tests.test_logo.SCREEN_DEPENDENCIES_ARE_RELEASES = True¶
Whether the dependencies the version screen names are installed releases.
The screen’s
Built onrow prints the installed Click and Cloup versions, so a branch build widens it past the docs URL that is otherwise the longest row. The width below is a design constraint on the configuration this project ships, and the screen already falls back to the plain message when it does not fit, so asserting it against a dev string measures upstream’s version scheme rather than this package.
tests.test_multicall module¶
Tests multicall / argv[0] dispatch and the invocation-name hook.
- tests.test_multicall.make_kitchen(personalities=None, **group_kwargs)[source]¶
A fresh multicall CLI per test, to keep process-global state from leaking.
The domain is deliberately a kitchen: the test suite asserts dispatch mechanics, not any real tool’s behavior.
- tests.test_multicall.test_argv0_dispatch(runner, monkeypatch)[source]¶
With no explicit
prog_name, the basename ofargv[0]dispatches.
- tests.test_multicall.test_env_prefix_follows_personality(runner)[source]¶
A personality reads its own
<NAME>_*envvars, like a standalone binary.
- tests.test_multicall.test_explicit_mapping_replaces_identity(runner)[source]¶
An explicit
personalitiesmapping is exhaustive, not additive.
- tests.test_multicall.test_explicit_prog_name_wins(runner, monkeypatch)[source]¶
An explicit
prog_nametakes precedence overargv[0].
- tests.test_multicall.test_extra_tokens_prepended(runner)[source]¶
Personality tokens are prepended to the user’s arguments.
- tests.test_multicall.test_flat_parse_any_order(runner)[source]¶
Group and subcommand options mix freely in a personality.
- tests.test_multicall.test_group_callback_skipped_in_personality_mode(runner)[source]¶
A personality is standalone: the group’s callback does not run.
- tests.test_multicall.test_group_mode_unchanged(runner)[source]¶
A non-matching invocation name leaves the regular group behavior intact.
- tests.test_multicall.test_help_conformance(runner)[source]¶
A personality’s help lists every option the group’s help lists.
This is what catches the Cloup layout trap: patching
paramson a copy updates the parser but not the help formatter, which is computed fromparamsat__init__time.
The default identity mapping skips hidden and synthetic subcommands.
- tests.test_multicall.test_invocation_name_meta_key(runner)[source]¶
ctx.metaexposes the invocation name on the root context.
- tests.test_multicall.test_no_envvar_leak_into_group_mode(runner)[source]¶
Building a personality must not rewrite the original params’ envvars.
- tests.test_multicall.test_resolve_invocation_name(runner, monkeypatch, argv0, expected)[source]¶
The detector uses the unresolved basename and strips
.exe.
- tests.test_multicall.test_unknown_subcommand_in_mapping(runner)[source]¶
A personality mapping an absent subcommand fails loudly at dispatch.
- tests.test_multicall.test_version_names_each_personality(runner)[source]¶
Each personality’s
--versionnames itself, in sequence.Regression test:
VersionOptionused to cacheprog_nameon the instance, pinning the first name seen in a process forever. The warm-up invocation with debug logging matters: it resolves every version field without anyone calling--versionat all.
- tests.test_multicall.test_version_prog_name_varies_without_multicall(runner)[source]¶
--versionfollows an explicitprog_nameeven in plain group mode.
- tests.test_multicall.test_personalities_subcommand(runner)[source]¶
The auto-injected
personalitiessubcommand lists the names.
- tests.test_multicall.test_personalities_command_can_be_disabled(runner)[source]¶
personalities_command=Falsesuppresses the listing subcommand.
- tests.test_multicall.test_personalities_command_can_be_overridden(runner)[source]¶
A
personalitiessubcommand of the author’s replaces the injected one.
- tests.test_multicall.test_injected_subcommands_listed_last_in_declaration_order()[source]¶
helpandpersonalitiestrail the author’s own subcommands.
- tests.test_multicall.test_plain_click_subcommand_is_a_personality(runner)[source]¶
A subcommand built by Click alone dispatches like any other.
- tests.test_multicall.test_normalize_personality()[source]¶
Personality values normalize to non-empty token tuples.
- tests.test_multicall.test_deepcopy_keeps_every_sentinel_identical()[source]¶
A copied parameter carries its enum members over, it does not rebuild them.
An enum member is a singleton, so a copy handing back an equal-but-distinct one is already wrong, and the
object()behind a PEP 661 sentinel never survives a rebuild. Walking the whole default option set catches the next parameter to grow one.
- tests.test_multicall.test_version_option_deepcopy_drops_cache()[source]¶
Deepcopying a warmed-up
VersionOptionyields fresh field resolution.
- tests.test_multicall.test_lazy_subcommand_is_a_personality(runner, tmp_path, monkeypatch)[source]¶
A lazy multicall group answers to a subcommand it has not imported yet.
Personality dispatch runs before any context exists, so the name is resolved through the lazy registry rather than the subcommands already loaded. Invoked as
bake, the group imports the subcommand and runs it, instead of falling through to its own usage screen.
tests.test_myst module¶
Tests for the reST-to-MyST converters and the convert-to-myst command.
- tests.test_myst.test_convert_directives_keeps_fenced_body()[source]¶
A directive body already holding a fence cannot nest: it stays reST.
- tests.test_myst.test_convert_directives_skips_fence_interiors()[source]¶
A reST directive inside a fence is prior nested-conversion output: keep it.
- tests.test_myst.test_convert_source_nested_directives_idempotent()[source]¶
Nested directives convert the outer level only, and re-runs are no-ops.
- tests.test_myst.test_convert_source_fenced_samples_untouched()[source]¶
Fence bodies are sample code: reST markup inside them is data.
tests.test_output module¶
- tests.test_output.UNENCODABLE_ON_CP1252 = ('✓', '✘', '╭', '─')¶
Glyphs Click Extra prints that a legacy Windows code page cannot encode: the two status marks, and two characters of a rounded table border.
- class tests.test_output.FakeStream(encoding, *, refuse=False)[source]¶
Bases:
objectA stand-in for a standard stream, recording what it was reconfigured to.
The real streams of a test run already speak UTF-8, so the legacy path is never taken through them.
- tests.test_output.test_prep_path_reopens_the_process_stdout_as_utf8(monkeypatch, tmp_path)[source]¶
The stdout of the process itself is opened again on its descriptor, as UTF-8.
Its own
cp1252encoding cannot carry the glyphs. Closing the stream returned leaves that stdout open.- Return type:
- tests.test_output.test_prep_path_returns_a_process_stdout_without_descriptor(monkeypatch)[source]¶
A stdout of the process with no file descriptor is returned as-is.
- Return type:
- tests.test_output.test_prep_path_stdout_reaches_the_runner_capture(capture)[source]¶
A command writing to
-prints into the capture of Click’s test runner.Under
capture="fd",sys.stdout.fileno()returns a copy of the original stdout descriptor, so a stream opened on it writes past the capture.- Return type:
- tests.test_output.test_legacy_stream_is_switched_to_utf8(monkeypatch, encoding)[source]¶
A stream unable to carry the glyphs is reconfigured to UTF-8.
So is a stream reporting an encoding Python does not know.
- Return type:
- tests.test_output.test_utf8_stream_is_left_alone(monkeypatch, encoding)[source]¶
A stream already speaking UTF-8 is never reconfigured, whatever its spelling.
- Return type:
- tests.test_output.test_a_refusing_stream_never_raises(monkeypatch)[source]¶
A stream refusing reconfiguration is left as the platform made it.
- Return type:
tests.test_parameters module¶
- tests.test_parameters.CLICK_HELP_PARAM_NAME = '_click_default_help'¶
Click’s internal
.namefor its built-in--helpoption.Detected at runtime instead of hard-coded:
_click_default_helpis an internal identifier of Click, which Click Extra does not control.
- class tests.test_parameters.Custom[source]¶
Bases:
ParamTypeA dummy custom type.
- convert(value, param, ctx)[source]¶
Convert the value to the correct type. This is not called if the value is
None(the missing value).This must accept string values from the command line, as well as values that are already the correct type. It may also convert other compatible types.
The
paramandctxarguments may beNonein certain situations, such as when converting prompt input.If the value cannot be converted, call
fail()with a descriptive message.- Parameters:
value – The value to convert.
param – The parameter that is using this type to convert its value. May be
None.ctx – The current context that arrived at this value. May be
None.
- tests.test_parameters.test_canonical_param_name_matches_click(decl)[source]¶
The fold answers what Click names a parameter declared that way.
Click derives the name in
Option._parse_decls, which splits the prefix then applies this same fold. Pinning the two together is what keeps the helper honest when Click moves.
- tests.test_parameters.test_canonical_param_name_is_many_to_one()[source]¶
The fold identifies a name, and cannot reconstruct a spelling.
- tests.test_parameters.test_canonical_param_name_never_answers_an_identifier_decl()[source]¶
Click takes an identifier declaration verbatim, so no fold produces it.
This is why a resolved spelling has to come back from the names a CLI declares, rather than from what the fold returns.
- class tests.test_parameters.FruitOption(param_decls=None, show_default=None, prompt=False, confirmation_prompt=False, prompt_required=True, hide_input=False, is_flag=None, flag_value=Sentinel.UNSET, multiple=False, count=False, allow_from_autoenv=True, type=None, help=None, hidden=False, show_choices=True, show_envvar=False, deprecated=False, **attrs)[source]¶
Bases:
OptionAn option class to search for.
- class tests.test_parameters.AppleOption(param_decls=None, show_default=None, prompt=False, confirmation_prompt=False, prompt_required=True, hide_input=False, is_flag=None, flag_value=Sentinel.UNSET, multiple=False, count=False, allow_from_autoenv=True, type=None, help=None, hidden=False, show_choices=True, show_envvar=False, deprecated=False, **attrs)[source]¶
Bases:
FruitOptionA subclass of it, to tell subclass matching apart.
- tests.test_parameters.test_search_params_returns_the_one_match()[source]¶
A single match comes back as itself, a subclass matching by default.
- tests.test_parameters.test_last_param_keeps_the_last_exact_match()[source]¶
Duplicates resolve to the last one, and a subclass never matches.
- tests.test_parameters.test_factory_decorators_expose_option_signature()[source]¶
Factory-built option decorators expose their option class’s real signature.
Guards the signature propagation in
click_extra.decorators.decorator_factory(), which lets editors,help()and Sphinx see the actual parameters instead of an opaque(*args, **kwargs).
- tests.test_parameters.test_params_auto_types(invoke, option_decorator)[source]¶
Check parameters types and structure are properly derived from CLI.
- tests.test_parameters.assert_table_content(output, expected_table, table_format=None)[source]¶
Helper to assert the content of a rendered table in the output.
- Return type:
- tests.test_parameters.test_standalone_show_params_option(invoke, cmd_decorator, option_decorator, assert_output_regex)[source]¶
- tests.test_parameters.test_show_params_table_format_ordering(invoke, args_order)[source]¶
--paramsrespects--table-formatregardless of CLI order.
- tests.test_parameters.test_show_params_native_types(invoke, table_format)[source]¶
Serialization formats emit native types instead of styled glyphs.
- tests.test_parameters.test_show_params_no_default_renders_none(invoke)[source]¶
A parameter with no default renders as None, not the UNSET sentinel.
Regression guard for Click’s
UNSETsentinel leaking into the value and default columns through the--paramsre-parse path. See the RAW_ARGS notes inclick_extra.context.
- tests.test_parameters.test_show_params_config_file_cascade(invoke, tmp_path, monkeypatch)[source]¶
The config_file column names the cascade layer a value resolved from.
The local file wins the root-level parameter; the parent file owns the
[sub]section the local file does not define. A parameter overridden on the command line keeps an empty column: the file did not supply the effective value.
The config_file column names the file of each key of a shared section.
Both files define the
[sub]section and its[sub.leaf]sub-section: at each depth, the local file is named for the key both set, and the parent file for the key only it sets.
- tests.test_parameters.test_show_params_config_file_single_file(invoke, create_config)[source]¶
A single loaded file is attributed to every config-sourced parameter.
- tests.test_parameters.test_show_params_config_file_url(invoke, httpserver, table_format, load_rows)[source]¶
A remote configuration is named by its exact URL, in text and JSON tables.
A text table shrinks a local path to
~, but a URL is not a path: normalized as one, it loses a slash after its scheme. The JSON table, which never shrinks, is the control.
- tests.test_parameters.test_show_params_config_file_column_is_opt_in(invoke)[source]¶
config_file is addressable by ID but stays out of the default table.
- tests.test_parameters.test_show_params_config_file_column_drawn_when_cascading(invoke)[source]¶
A cascading
--configdraws theconfig_filecolumn without being asked.Several files can then take part, so
Sourcealone no longer tells which one a value came from.
- tests.test_parameters.test_show_params_projection_overrides_the_cascade_column(invoke)[source]¶
An explicit
--columnsprojection is honored as given, cascade or not.
- tests.test_parameters.test_show_params_subclass_widens_the_default_columns(invoke)[source]¶
A subclass widening
default_columns()draws the column it added.That hook is the only way to reach an opt-in column on a CLI exposing no
--columnsoption of its own, or one whose--columnsbelongs to its subcommands and carries a different vocabulary.
- tests.test_parameters.test_column_registry_is_consistent()[source]¶
TABLE_HEADERS exposes parallel
column_labels()/column_ids().
- tests.test_parameters.test_render_doc_table_emits_markdown()[source]¶
render_doc_tablereturns a 2-column Markdown table covering every column.
- tests.test_parameters.test_columns_option_projects_and_orders(invoke)[source]¶
--columnskeeps only selected columns and preserves the user order.
- tests.test_parameters.test_columns_option_rejects_unknown_id(invoke)[source]¶
An unknown column ID raises a UsageError with available IDs listed.
- tests.test_parameters.test_params_rows_keep_path_order_under_projection(invoke, columns)[source]¶
Rows follow their parameter path, whichever column comes first.
--zuluis declared under thealphaname: ordered by its spec, its row would follow the one of--beta.
- tests.test_parameters.test_params_never_prompts(invoke)[source]¶
Introspecting an option that declares a prompt does not ask for its value.
The value of a parameter is replayed from the command line, which leaves the one a user would type at the prompt missing.
- tests.test_parameters.test_params_confirmation_prompt_string_reads_as_set(invoke)[source]¶
A custom confirmation prompt asks for confirmation, like
Truedoes.
- tests.test_parameters.test_subcommand_conflicts_with_parent_param(invoke)[source]¶
A subcommand whose name matches its direct parent’s param is skipped in the parameter tree (the config key would be ambiguous), but does not crash the CLI.
[root.alpha] # "foo" is ambiguous: is it the --foo param or the [root.alpha.foo] subcommand? foo = "apple"
- tests.test_parameters.test_nested_subcommand_no_false_conflict_with_root_param(invoke)[source]¶
A nested subcommand can share a name with a root-level param without conflict.
The config paths are distinct (
root.verbosevsroot.alpha.verbose), so there is no ambiguity.
- tests.test_parameters.test_standalone_table_rendering(invoke, opt1, opt2, table_format)[source]¶
Check all rendering styles of the table with standalone
--paramsand--table-formatoption.
- tests.test_parameters.test_standalone_no_color_rendering(invoke, opt1, opt2, opt3, table_format)[source]¶
Check that all rendering styles are responding to the
--coloroption.
- tests.test_parameters.test_iter_params_for_display_follows_the_help_screen()[source]¶
Presentation order, not the processing order carried by
params.
- tests.test_parameters.test_iter_params_for_display_keeps_option_groups_together()[source]¶
Options declared in a Cloup group are yielded under that group.
- tests.test_parameters.test_iter_params_for_display_falls_back_to_get_params()[source]¶
A plain Click command holds a single order, and it is the one to yield.
- tests.test_parameters.test_iter_params_for_display_yields_late_additions_last()[source]¶
A parameter attached after construction is missing from the cached groups.
- tests.test_parameters.test_help_column_is_opt_in(invoke)[source]¶
The Help column exists, stays out of the way, and answers to its ID.
- tests.test_parameters.test_help_column_paints_the_deprecation_marker(invoke)[source]¶
The marker takes the slot the help screen paints it with.
Every other column of the rendered table is themed, and the marker is the one part of this one Click Extra writes rather than the CLI author.
- tests.test_parameters.PARAMETER_SPEC_CLICK_VERSION = (8, 6)¶
Click release naming a parameter by
Parameter.specin a deprecation notice.From that release on, Click prints the flag with the longest prefix:
--apt-cygfor--apt-cyg/--no-apt-cyg. Below it Click prints theapt_cygidentifier, which Click Extra replaces with every spelling.
- tests.test_parameters.DEPRECATED_FLAG_NAME = '--apt-cyg / --no-apt-cyg'¶
How a notice names
--apt-cyg/--no-apt-cygon the installed Click.
- tests.test_parameters.test_option_deprecation_notice_names_its_flags(invoke)[source]¶
The notice names a flag a user types, not the identifier Click derived.
Below
PARAMETER_SPEC_CLICK_VERSION, Click reportshuman_readable_name, which for an option isself.name: the one spelling that appears nowhere on the command line.
- tests.test_parameters.test_option_error_hint_names_each_environment_variable(invoke, envvar, hint)[source]¶
The hint of a failing option quotes each variable that feeds it.
Click Extra stores every
envvaras a tuple, which the hint must not print as itsrepr.
- tests.test_parameters.test_deprecation_notice_reaches_a_configuration_file(invoke, create_config)[source]¶
A file switching a deprecated parameter on gets the notice too.
Click stops one rank short of the configuration, so the one place a selection outlives the project it names said nothing.
- tests.test_parameters.test_deprecation_notice_stays_silent_on_the_default(invoke)[source]¶
Nobody asked for the parameter, so nothing announces it.
- tests.test_parameters.test_deprecation_notice_is_emitted_once(invoke, create_config)[source]¶
A parameter named on both the command line and in a file warns once.
- tests.test_parameters.test_help_column_is_documented()[source]¶
The auto-generated column reference covers the opt-in column too.
- tests.test_parameters.POSITIONAL_DEFINITIONS = frozenset({'SortByOption'})¶
Option classes taking positional definitions ahead of
param_decls.SortByOptionreads its column definitions positionally, which is why itsparam_declsis keyword-only (seeclick_extra.decorators.sort_by_option).
param_declsfirst, then annotated keyword-only arguments, then**kwargs.One shape across every option class: a caller never has to look up the positional order of a passthrough, and an unannotated one cannot slip in.
- tests.test_parameters.test_option_constructors_leave_the_root_logger_alone(option_class)[source]¶
Building an option changes neither the handlers nor the level of
root.An option is built when its command is decorated, so when the module of the command is imported. The root logger belongs to the whole process by then: an application importing the CLI, or pytest collecting it, has its handlers there.
- tests.test_parameters.test_options_lend_the_root_logger_a_handler_for_the_run(option_class)[source]¶
Each option gives
rootthe handler of Click Extra when Click prepares to parse a command line, and takes it back when the context closes.Any of these options may report through log records: none may count on another option of the command to bring the handler that prints them.
- tests.test_parameters.test_params_lists_the_envvars_a_subcommand_reads(invoke)[source]¶
A subcommand setting its own
auto_envvar_prefixis listed under it.The walk behind
--paramsbuilds each subcommand’s context the way Click does for a real invocation, context settings included. A prefix derived from the parent’s would listGARDEN_PLANT_CROP, a variable the CLI never reads.
tests.test_prebake module¶
Tests for the one-pass dunder baking.
The single-field prebake_dunder and prebake_version helpers are covered
in test_version.py, beside the version option that reads what they write.
The project metadata readers are covered in test_project.py.
- tests.test_prebake.PYPROJECT_SCRIPTS = '[project]\nname = "orchard"\n\n[project.scripts]\norchard = "orchard.cli:main"\n'¶
A
pyproject.tomldeclaring one console script.
- tests.test_prebake.src_project(tmp_path)[source]¶
A src-layout project declaring one console script.
Its package carries two empty placeholders: a build field, which always resolves, and a git field, which cannot resolve outside a repository.
- tests.test_prebake.test_prebake_dunders_fills_every_empty_placeholder_in_one_pass(tmp_path)[source]¶
Every empty placeholder is filled, and only the resolvers it needs run.
A placeholder already holding a value and a field with no placeholder at all never trigger their resolver, so a build does not shell out for nothing.
- tests.test_prebake.test_prebake_dunders_leaves_a_placeholder_its_resolver_cannot_fill(tmp_path)[source]¶
A resolver answering nothing leaves its placeholder, and the file, alone.
- tests.test_prebake.test_prebake_dunders_fills_two_placeholders_sharing_a_line(tmp_path)[source]¶
Two literals on one line both land, whatever the first one’s new length.
- tests.test_prebake.test_prebake_dunders_writes_a_literal_holding_the_value(tmp_path, placeholder, value, expected)[source]¶
The baked literal parses back to the value, in the quotes it opened with.
A value can carry the quote character the placeholder is written in, like a branch named after a fruit’s owner: escaped, it cannot close the literal early, and the file still compiles.
- tests.test_prebake.test_prebake_dunders_counts_columns_past_non_ascii_text(tmp_path)[source]¶
A placeholder after a non-ASCII literal on the same line lands in place.
The parser counts columns in UTF-8 bytes: read as characters, every multi-byte character ahead of the placeholder would shift the splice right.
- tests.test_prebake.test_prebake_all_bakes_a_src_layout_project(invoke, src_project, monkeypatch)[source]¶
prebake allfinds the package of a src-layout project on its own.A build field always resolves, so its placeholder proves the file was found and written. The git field stays empty outside a repository.
tests.test_progress module¶
- tests.test_progress.test_progressbar_follows_progress_flag(invoke, args, expected_hidden)[source]¶
click_extra.progressbar gates
hiddenon the resolved –progress flag.
An explicit
hidden=wins over –no-progress, like echo(color=…).
- tests.test_progress.test_progressbar_shown_without_active_context()[source]¶
Outside a Click command the bar defaults to shown, like click.progressbar.
- tests.test_progress.test_progressbar_label_emission_off_tty(invoke, args, label_shown)[source]¶
Off a TTY a shown bar still emits its label once; a hidden one emits nothing.
- tests.test_progress.test_progressbar_shows_final_position_with_update_min_steps()[source]¶
With an
update_min_stepsthat does not divide the length, the bar still lands on total/total instead of the last multiple (14/20 for a length of 20 and anupdate_min_stepsof 7).
- tests.test_progress.test_progressbar_prints_a_log_record_above_itself(monkeypatch)[source]¶
A record logged while a bar draws lands on a line of its own, above it.
Click draws a bar with a carriage return and no line feed until it finishes, so a record written in between continued the bar’s row, after its position.
- tests.test_progress.LOGGING_UNDER_A_BAR_SCRIPT = '\nimport logging\nimport time\n\nfrom click_extra import command, progressbar\n\n\n@command\ndef sort_plums():\n with progressbar(range(3), label="Sorting plums") as bar:\n for _ in bar:\n logging.warning("Opening the fruit basket")\n time.sleep(0.05)\n\n\nsort_plums()\n'¶
A CLI logging from inside an open progress bar.
- tests.test_progress.test_progressbar_logging_on_a_pseudo_terminal()[source]¶
On a real terminal, each record a CLI logs under a bar gets its own row.
- tests.test_progress.test_progressbar_draws_on_stderr_by_default()[source]¶
The bar draws where log records and the other live lines go.
- tests.test_progress.test_progressbar_draws_fast_items_once_per_interval()[source]¶
Items faster than the interval draw nothing between the first and the last position, however many there are.
- tests.test_progress.test_progressbar_draws_every_slow_item()[source]¶
Items slower than the interval each draw their position, so a bar over slow items never looks frozen.
tests.test_project module¶
Tests for the project and entry-point discovery of click_extra.project.
- tests.test_project.PYPROJECT_SCRIPTS = '[project]\nname = "orchard"\n\n[project.scripts]\norchard = "orchard.cli:main"\n'¶
A
pyproject.tomldeclaring one console script.
- tests.test_project.SETUP_CFG_SCRIPTS = '[options.entry_points]\nconsole_scripts =\n vineyard = vineyard.cli:main\n'¶
A
setup.cfgdeclaring one console script.
- tests.test_project.src_project(tmp_path)[source]¶
A src-layout project declaring one console script.
Its package carries two empty placeholders: a build field, which always resolves, and a git field, which cannot resolve outside a repository.
- tests.test_project.test_read_project_scripts_prefers_pyproject(tmp_path)[source]¶
[project.scripts]wins over asetup.cfgsitting beside it.
- tests.test_project.test_read_project_scripts_falls_back_to_setup_cfg(tmp_path)[source]¶
console_scriptsinsetup.cfgis read whenpyproject.tomlhas none.
- tests.test_project.test_locate_package_root(tmp_path, layout, expected)[source]¶
The directory to import from follows the package through both layouts.
- tests.test_project.test_discover_package_init_files_finds_a_src_layout(src_project)[source]¶
A package under
src/is discovered like one at the project root.
- tests.test_project.test_discover_package_init_files_defaults_to_the_current_directory(src_project, monkeypatch)[source]¶
Without a directory, the paths are relative to the current one.
- tests.test_project.test_discover_finds_init(tmp_path, monkeypatch)[source]¶
Discovers
__init__.pyfrom[project.scripts].
- tests.test_project.test_discover_no_pyproject(tmp_path, monkeypatch)[source]¶
Returns empty list when
pyproject.tomlis missing.
- tests.test_project.test_discover_no_scripts(tmp_path, monkeypatch)[source]¶
Returns empty list when
[project.scripts]is absent.
- tests.test_project.test_discover_deduplicates(tmp_path, monkeypatch)[source]¶
Multiple scripts from the same package yield one path.
- tests.test_project.test_detect_source_package_setup_cfg_only(tmp_path)[source]¶
A project declaring its scripts in
setup.cfgalone is detected too.
- tests.test_project.test_detect_source_package_no_project(tmp_path)[source]¶
A directory holding no packaging metadata names the place it read.
- tests.test_project.test_resolve_entry_point_ambiguity_rule(tmp_path, scripts, target)[source]¶
The scripts must name one target, however many names point to it.
Two targets are ambiguous even inside one package: nothing tells which function the project means.
- tests.test_project.test_resolve_entry_point_src_layout(src_project)[source]¶
The entry point knows its module, function, package and import root.
- tests.test_project.test_resolve_entry_point_package_not_on_disk(tmp_path)[source]¶
A package absent from disk leaves the root to each caller to settle.
wrapandconvert-to-mystrefuse the same project, each its own way.
tests.test_pygments module¶
- tests.test_pygments.lex(text, **options)[source]¶
Shorthand: lex
textand return(token_type, value)pairs.optionsgo to theAnsiColorLexer, liketrue_color=False.
- tests.test_pygments.collect_classes(klass, prefix='Ansi')[source]¶
Returns all classes defined in
click_extra.pygmentsthat are a subclass ofklass, and whose name starts with the providedprefix.
- tests.test_pygments.get_pyproject_section(*section_path)[source]¶
Descends into the TOML tree of
pyproject.tomlto reach the value specified bysection_path.
- tests.test_pygments.test_ansi_lexers_candidates(tmp_path)[source]¶
Look into Pygments test suite to find all ANSI lexers candidates.
Good candidates for ANSI colorization are lexers that are producing
Generic.Outputtokens, which are often used by REPL-like and scripting terminal to render text in a console.The list is manually maintained in Click Extra code, and this test is here to detect new candidates from new releases of Pygments.
Attention
The Pygments source code is downloaded from GitHub in the form of an archive, and extracted in a temporary folder.
The version of Pygments used for this test is the one installed in the current environment.
Danger
Security check While extracting the archive, we double check we are not fed an archive exploiting relative
..or.path attacks.
- tests.test_pygments.test_session_lexers_cover_every_shell_session_lexer()[source]¶
Every Pygments lexer built on
ShellSessionBaseLexerhas an ANSI variant.collect_session_lexersnames its lexers by hand rather than walking all of Pygments at import time, so this walk is what notices a new one.
- tests.test_pygments.test_plain_text(text, expected)[source]¶
Plain text without escape sequences passes through unchanged.
- tests.test_pygments.test_sgr_fg_standard(code, color)[source]¶
SGR 30-37 set standard foreground colors.
- tests.test_pygments.test_sgr_bg_standard(code, color)[source]¶
SGR 40-47 set standard background colors.
- tests.test_pygments.test_sgr_fg_bright(code, color)[source]¶
SGR 90-97 set bright foreground colors.
- tests.test_pygments.test_sgr_bg_bright(code, color)[source]¶
SGR 100-107 set bright background colors.
- tests.test_pygments.test_sgr_text_attribute(code, attr)[source]¶
SGR attribute codes set the corresponding text styling.
- tests.test_pygments.test_sgr_attribute_reset(set_code, reset_code, attr)[source]¶
Each attribute can be individually reset by its specific SGR code.
- tests.test_pygments.test_sgr22_resets_bold_and_faint()[source]¶
SGR 22 (normal intensity) resets both bold and faint simultaneously.
- tests.test_pygments.test_empty_sgr_is_reset()[source]¶
An empty SGR sequence (ESC [ m) is equivalent to SGR 0.
- tests.test_pygments.test_sgr39_resets_foreground()[source]¶
SGR 39 resets foreground color to default.
- tests.test_pygments.test_sgr49_resets_background()[source]¶
SGR 49 resets background color to default.
- tests.test_pygments.test_sgr39_keeps_other_attributes()[source]¶
SGR 39 only resets foreground; other attributes persist.
- tests.test_pygments.test_sgr_combined(params, expected_token)[source]¶
Multiple SGR codes in a single escape sequence are applied together.
- tests.test_pygments.test_24bit_rgb_fg_quantized(r, g, b, expected_idx)[source]¶
SGR 38;2;r;g;b quantizes to nearest 256-color index when true_color=False.
- tests.test_pygments.test_24bit_rgb_bg_quantized(r, g, b, expected_idx)[source]¶
SGR 48;2;r;g;b quantizes background to nearest 256-color index when true_color=False.
- tests.test_pygments.test_truecolor_fg_preserves_hex(r, g, b, expected_hex)[source]¶
With
true_color=True, SGR 38;2;r;g;b emitsToken.Ansi.FG_{hex}.
- tests.test_pygments.test_truecolor_bg_preserves_hex(r, g, b, expected_hex)[source]¶
With
true_color=True, SGR 48;2;r;g;b emitsToken.Ansi.BG_{hex}.
- tests.test_pygments.test_truecolor_combined_fg_and_bg()[source]¶
Foreground and background 24-bit RGB combine into a single compound token.
- tests.test_pygments.test_truecolor_with_attribute()[source]¶
Bold + 24-bit RGB combines into a single compound token.
- tests.test_pygments.test_truecolor_default_enabled()[source]¶
Default
AnsiColorLexer()preserves 24-bit RGB asFG_{rrggbb}tokens.
- tests.test_pygments.test_truecolor_explicit_disable_quantizes()[source]¶
AnsiColorLexer(true_color=False)quantizes RGB to the 256-color palette.
- tests.test_pygments.test_truecolor_invalid_range_ignored()[source]¶
Out-of-range RGB values are skipped in true-color mode too.
- tests.test_pygments.test_truecolor_truncated_params_ignored()[source]¶
Truncated RGB params set no color in true-color mode either.
The stray
2reads as SGR 2 (faint), as in the quantized mode: seetest_24bit_truncated_params.
- tests.test_pygments.test_truecolor_filter_forwards_flag()[source]¶
AnsiFilter(true_color=True)forwards the flag to its inner lexer.
- tests.test_pygments.test_truecolor_session_lexer_forwards_flag()[source]¶
Lexer kwarg
true_color=Trueflows through_AnsiFilterMixin.
- tests.test_pygments.test_true_color_lexer_option_reads_a_string(alias, value, expected)[source]¶
A lexer reads a
true_colorstring as the boolean it spells.
- tests.test_pygments.test_true_color_filter_option_reads_a_string(value, expected)[source]¶
The filter reads a
true_colorstring as the boolean it spells.
- tests.test_pygments.test_formatter_renders_truecolor_inline_style()[source]¶
AnsiHtmlFormatteremits inline style forFG_/BG_tokens.
- tests.test_pygments.test_formatter_renders_truecolor_background()[source]¶
Background 24-bit RGB renders as
background-colorinline style.
- tests.test_pygments.test_formatter_truecolor_combined_with_class_styling()[source]¶
Bold + RGB renders both a CSS class for Bold and an inline style for the color.
- tests.test_pygments.test_formatter_truecolor_fg_and_bg_nested_spans()[source]¶
Both fg and bg RGB on the same token produce two nested inline-style spans.
- tests.test_pygments.test_formatter_quantize_path_no_inline_styles()[source]¶
When
true_color=Falseis opted into, no inline styles are emitted.
- tests.test_pygments.test_formatter_osc8_with_truecolor_coexist()[source]¶
OSC 8 hyperlink and 24-bit RGB tokens cooperate in the same span.
Both mechanisms inject Private Use Area markers into the token stream (
_LINK_*for hyperlinks,_RGB_*for RGB colors) and rely on independent post-processing passes informat_unencoded. This test pins the contract that both rewrites happen and don’t interfere.
- tests.test_pygments.test_extra_css_matches_sgr_attributes()[source]¶
EXTRA_ANSI_CSS keys match the attribute component names.
- tests.test_pygments.test_extra_css_follows_the_style_table()[source]¶
Every attribute but blink renders with the declaration
Style.to_csswrites.Faintin particular dims to the sameopacityas adimstyle. Blink has no CSS declaration of its own and takes the keyframe animation instead.
- tests.test_pygments.test_non_sgr_csi_stripped()[source]¶
Non-SGR CSI sequences (cursor movement, etc.) are stripped.
- tests.test_pygments.test_csi_ends_on_its_final_byte(text, expected)[source]¶
A CSI sequence ends on its ECMA-48 final byte, a letter or not.
A final byte like the
~of the bracketed-paste markers ends the sequence: the text after it is kept whole.
- tests.test_pygments.test_escape_ends_on_its_final_byte(text, expected)[source]¶
An escape carrying intermediate bytes is stripped up to its final byte.
ESC # 8is the screen alignment test: the#is an intermediate byte and the8the final one. A grammar consuming one byte after the escape left that8in the text, wherestrip_ansi()removed it.
- tests.test_pygments.test_vt100_charset_stripped()[source]¶
VT100 charset selection escapes are stripped.
- tests.test_pygments.test_vt100_charset_g1_stripped()[source]¶
ESC ) designator for G1 charset is stripped.
- tests.test_pygments.test_unknown_escape_stripped()[source]¶
Unknown single-byte escape sequences are stripped.
- tests.test_pygments.test_bare_escape_at_end()[source]¶
A lone ESC at the end of input is consumed without error.
- tests.test_pygments.test_osc_sequence_stripped()[source]¶
Non-hyperlink OSC sequences are fully consumed and stripped.
- tests.test_pygments.test_osc_st_terminated_stripped()[source]¶
OSC sequences terminated by ST (ESC ) are fully stripped.
- tests.test_pygments.test_osc8_hyperlink(text, expected)[source]¶
OSC 8 hyperlinks emit link start/end tokens around the visible text.
- tests.test_pygments.test_osc8_with_sgr()[source]¶
OSC 8 hyperlink combined with SGR color preserves both.
- tests.test_pygments.test_osc8_unsafe_scheme_stripped(url)[source]¶
OSC 8 with unsafe or missing URL scheme is stripped silently.
- tests.test_pygments.test_osc8_implicit_close()[source]¶
A new OSC 8 link implicitly closes the previous one.
- tests.test_pygments.test_osc8_unclosed()[source]¶
An unclosed OSC 8 link is automatically closed at end of input.
- tests.test_pygments.test_osc8_close_without_open()[source]¶
An OSC 8 close without a preceding open is silently ignored.
- tests.test_pygments.test_osc8_survives_a_full_reset()[source]¶
SGR 0 inside a link resets the styling alone: the link closes at its OSC 8.
- tests.test_pygments.test_color_persists_until_changed()[source]¶
A foreground color set in one sequence persists until explicitly changed.
- tests.test_pygments.test_multiple_color_changes()[source]¶
Color can be changed multiple times without resetting.
- tests.test_pygments.test_attribute_stacking()[source]¶
Attributes accumulate: bold then italic produces bold+italic.
- tests.test_pygments.test_independent_fg_bg()[source]¶
Foreground and background colors are independent of each other.
- tests.test_pygments.test_sgr_with_trailing_semicolons()[source]¶
An empty trailing parameter is a
0, which resets the color set before it.
- tests.test_pygments.test_sgr_leading_semicolons()[source]¶
An empty leading parameter is a
0: a reset, then the color after it.The lexer shares the decoder of
split_ansi, which reads\x1b[;31mas red, so a page renders what a terminal shows.
- tests.test_pygments.test_sgr_double_semicolons()[source]¶
An empty parameter between two codes is a reset between them.
- tests.test_pygments.test_sgr_unknown_codes_ignored()[source]¶
Unknown SGR codes are silently ignored; known codes still apply.
- tests.test_pygments.test_256color_truncated_params()[source]¶
Truncated 256-color sequence (missing color index) leaves leftover codes.
38;5: code 38 triggers extended color handling but needs at least 2 more values (mode + index). Only 1 remains (5), so 38 skips. Then 5 is processed as SGR 5 (blink).
- tests.test_pygments.test_24bit_truncated_params()[source]¶
A truncated 24-bit RGB sequence sets no color.
38;2;255;128misses the blue channel, so the decoder skips38without consuming anything after it:2is then read as SGR 2 (faint), and the two channel values are unknown codes. The same readingsplit_ansigives the sequence.
- tests.test_pygments.test_extended_color_unknown_mode()[source]¶
Extended color with unknown mode (not 5 or 2) sets no color.
38;3;100: code 38 is skipped on its own, since neither of the two color modes follows it.3is then read as SGR 3 (italic) and100as SGR 100 (bright black background), the readingsplit_ansigives the sequence.
- tests.test_pygments.test_non_numeric_sgr_params()[source]¶
Non-numeric characters in CSI params cause partial consumption.
ESC[abc;31mis not a valid SGR sequence. The regex consumesESC[aas a CSI withaas the final byte, leavingbc;31mtextas plain text.
- tests.test_pygments.test_empty_text_between_sequences()[source]¶
Sequences with no text between them produce no empty tokens.
- tests.test_pygments.test_newline_in_colored_text()[source]¶
Newlines within colored text are preserved in the token value.
- tests.test_pygments.test_interleaved_text_and_escapes()[source]¶
Complex interleaving of plain text and escape sequences.
- tests.test_pygments.test_lexer_resets_between_calls()[source]¶
Each call to get_tokens starts from a clean state.
- tests.test_pygments.test_ansi_styles_has_all_named_colors()[source]¶
Style dict contains entries for all 16 named foreground and background colors.
- tests.test_pygments.test_ansi_styles_has_256_palette()[source]¶
Style dict contains entries for all 256 foreground and background indices.
- tests.test_pygments.test_ansi_styles_excludes_all_attributes()[source]¶
All text attribute tokens are absent from the style dict.
Furo’s dark-mode CSS generator adds
color: #D0D0D0to every token in the style dict. For attribute tokens, this overrides actual foreground colors on compound tokens when the attribute rule appears later in the CSS cascade. All attribute styling is handled byEXTRA_ANSI_CSS/custom.cssinstead.
- tests.test_pygments.test_ansi_styles_count()[source]¶
Style dict has only color entries: 32 named + 512 indexed.
- tests.test_pygments.test_formatter_no_color_on_attribute_css()[source]¶
CSS rules for attribute-only tokens must not set a color property.
When an attribute token (like -Ansi-Strikethrough) has a
colorproperty in its CSS rule, it can override the foreground color of a sibling color token (like -Ansi-Red) if the attribute rule appears later in the CSS cascade. This regression test catches the issue that Furo’s dark-mode generator exposed.
- tests.test_pygments.test_palette_256_completeness()[source]¶
256-color palette has exactly 256 entries.
- tests.test_pygments.test_palette_256_hex_format()[source]¶
All palette values are 7-character hex strings.
- tests.test_pygments.test_lexer_map_completeness()[source]¶
LEXER_MAP has one entry per session lexer.
- tests.test_pygments.test_ansi_variant_belongs_to_this_module(original, variant)[source]¶
Each variant lives in
click_extra.pygments, under the name its entry point resolves, and derives from the lexer it varies.
- tests.test_pygments.test_ansi_variant_subclass_keeps_its_names()[source]¶
A subclass of a variant inherits its names and aliases, prefixed once.
- tests.test_pygments.test_ansi_filter_transforms_output_tokens()[source]¶
AnsiFilter converts Generic.Output tokens containing ANSI codes.
- tests.test_pygments.test_ansi_filter_passes_through_other_tokens()[source]¶
AnsiFilter does not modify tokens that are not Generic.Output.
- tests.test_pygments.test_formatter_css_classes_single_color()[source]¶
Single-color token gets the expected CSS classes.
- tests.test_pygments.test_formatter_css_classes_compound()[source]¶
Compound token gets decomposed CSS classes.
- tests.test_pygments.test_formatter_css_classes_256color()[source]¶
256-color tokens get the correct CSS class.
- tests.test_pygments.test_formatter_style_defs_contain_ansi_colors()[source]¶
get_style_defs() includes CSS rules for ANSI color tokens.
Two formatters on the same base style run on one augmented style class.
- tests.test_pygments.test_formatter_css_classes_cache_keyed_on_prefix()[source]¶
The shared class cache tells formatters with different prefixes apart.
- tests.test_pygments.test_formatter_osc8_hyperlink()[source]¶
OSC 8 hyperlink is rendered as an HTML <a> tag.
- tests.test_pygments.test_formatter_osc8_with_color()[source]¶
OSC 8 hyperlink combined with SGR color renders both link and color.
- tests.test_pygments.test_formatter_osc8_url_escaping()[source]¶
URLs with special HTML characters are properly escaped in href.
- tests.test_pygments.test_formatter_css_contains_link_rules()[source]¶
get_style_defs() includes CSS for hyperlink styling.
- tests.test_pygments.test_formatter_osc8_unsafe_scheme_no_link()[source]¶
Unsafe URL schemes produce no <a> tag in formatted output.
- tests.test_pygments.test_formatter_osc8_quotes_escaped_in_href()[source]¶
Quotes in a hyperlink URL cannot break out of the href attribute.
Pygments escapes quotes in token text only before its
2.21.0release, so the formatter escapes them itself, with the entities older Pygments writes. Ampersands are left to Pygments, and are escaped once.
- tests.test_pygments.test_real_world_ansi(text, expected_tokens)[source]¶
Real-world ANSI patterns from terminal tools and documentation references.
- tests.test_pygments.test_ansi_session_decodes_a_styled_input_line()[source]¶
An input line arriving styled keeps its colors instead of its escape bytes.
A session lexer reads any line opening on a prompt as a typed command, and a
--helpepilog writing$ my-cli pick --ripematches. A shell lexer reading its escapes would tokenize\x1b[36mas an operator between two runs of text, and leave the raw bytes in the page.
- tests.test_pygments.test_ansi_session_leaves_a_plain_input_line_to_the_shell_lexer()[source]¶
Input carrying no escape still gets its shell highlighting.
- tests.test_pygments.test_ansi_variant_keeps_the_text_of_its_original(original, variant)[source]¶
A variant renders a session free of escapes to the text of its original.
The blank lines opening and closing an output block included: each lexer reads its own prompts in
_PLAIN_SESSIONS, so it splits the text into input and output blocks at different places.
- tests.test_pygments.test_no_click_extra_help_screen_renders_raw_escapes()[source]¶
No help screen this package ships leaks an escape byte into its HTML.
The whole CLI tree is rendered the way
click:rundoes it, then lexed and formatted. Whatever the lexer fails to decode surfaces as a literal\x1bin the page, so the assertion reads the formatted output rather than the token stream.
tests.test_pyproject module¶
Consistency checks tying the test matrix to the declared dependencies.
The click version axis of the test matrix is the one dependency whose whole
supported range is exercised release-by-release (a patch can change behavior
mid-stream). These tests keep that axis in sync with the click dependency
specifier, so a drift, like a new Click release or a raised floor, fails CI
instead of silently rotting.
- tests.test_pyproject.PYPROJECT = PosixPath('/home/runner/work/click-extra/click-extra/pyproject.toml')¶
Path to the project’s
pyproject.toml, relative to this test file.
- tests.test_pyproject.SENTINELS = ('released', 'stable', 'main')¶
Moving-reference values of the
click-versionaxis: the lockfile-resolved release, and Click’sstableandmaindevelopment branches. Everything else in the axis is a pinned release number.
- tests.test_pyproject.UV_LOCK = PosixPath('/home/runner/work/click-extra/click-extra/uv.lock')¶
Path to the project’s
uv.lock, which holds the Clickreleasedcells run.
- tests.test_pyproject.load_click_matrix()[source]¶
Read the Click setup from
pyproject.toml.The
click-versionaxis is spread across the forward-lookingtest-matrix.full-includerows (the pinned releases and thestable/mainsentinels) and thereleaseddefault declared intest-matrix.include.
- tests.test_pyproject.stable_pypi_versions(package)[source]¶
Return all non-yanked, non-prerelease versions of
packageon PyPI.- Return type:
set[Version]
- tests.test_pyproject.locked_versions(package)[source]¶
Return the versions of
packagethatuv.lockpins.A
releasedcell installs from the lock withuv run --frozen, so it runs this version, not the newest one on PyPI: the cooldown keeps a new release out of the lock for a week.
- tests.test_pyproject.test_click_floor_is_pinned_and_sentinels_present()[source]¶
The matrix floor stays in sync with the dependency floor (hermetic).
The lowest pinned
click-versionmust equal the lower bound of theclickspecifier, so raising or lowering the dependency floor without updating the matrix (or vice versa) fails here. Runs offline, unlike the PyPI cross-check below.
- tests.test_pyproject.test_click_matrix_matches_authorized_releases()[source]¶
The pinned releases match exactly the Click releases the specifier allows.
Every release allowed by the
clickspecifier must be referenced in the matrix: the locked one through thereleasedsentinel, every other one by an explicit pin. This asserts the set equality in both directions:a missing pin means a Click release that no cell runs, like a new release that the cooldown still keeps out of
uv.lock;a stale pin means a pinned version is no longer an authorized release (the floor was raised past it, or the release was yanked).
Either way, the matrix needs an edit, and this is the signal. There is no exemption list: a release the suite cannot run on gets its own answer at the moment it appears, whether that is a floor bump past it or a hole opened here on purpose.
- tests.test_pyproject.test_nuitka_includes_every_lazy_subcommand_module()[source]¶
A Nuitka build bundles the module of each lazily mounted subcommand.
Nuitka follows
importstatements only, while a lazy subcommand is imported from a string: a module missing here ships a binary whose subcommand fails.
tests.test_pytest module¶
Test the Pytest helpers.
- tests.test_pytest.test_isolated_app_dir(invoke, isolated_app_dir)[source]¶
Config discovery is repointed at the isolated directory.
A CLI invoked in-process must not see the host’s real configuration folder, and a configuration file planted in the isolated directory must be picked up by the default
--configsearch pattern.
- tests.test_pytest.ambient_color_var(request, monkeypatch)[source]¶
One color variable set in the caller’s shell, before
runnerstarts.Requested ahead of
runnerin a signature, so the variable is in place by the time the fixture isolates the environment.
- tests.test_pytest.test_runner_ignores_ambient_color_vars(ambient_color_var, runner)[source]¶
A color variable of the caller’s shell does not reach the invoked CLI.
Each one can switch the colors of the output a test matches as plain text.
- tests.test_pytest.ambient_cache_home(monkeypatch, tmp_path)[source]¶
A cache folder set in the caller’s shell, before
runnerstarts.Requested ahead of
runnerin a signature, so the variable is in place by the time the fixture isolates the environment.
tests.test_readme module¶
Tests for readme.md, which ships to PyPI as the long description.
- tests.test_readme.URL_RE = re.compile('\\]\\((?P<markdown>[^)\\s]+)|\\s(?:href|src)=["\'](?P<html>[^"\']+)["\']|^\\[[^\\]]+\\]:\\s*(?P<definition>\\S+)', re.MULTILINE)¶
Target of every link and image in a Markdown file.
Matches the inline Markdown forms,
`text <url>`_and, thehrefandsrcattributes of an HTML tag, and a reference definition,[label]: url.
- tests.test_readme.test_readme_links_and_images_are_absolute()[source]¶
Every link and image of
readme.mdpoints at an absolute URL.pyproject.tomlsetsreadme = "readme.md", so this file ships verbatim as the PyPI long description. GitHub resolves a relative target against the repository. PyPI has no repository to resolve it against, so the image breaks or the link goes nowhere, on the published project page alone.- Return type:
tests.test_recording module¶
Tests for click_extra.capture.recording, which rebuilds a terminal’s screen.
- class tests.test_recording.TTYStringIO(initial_value='', newline='\n')[source]¶
Bases:
StringIOAn in-memory text buffer that claims to be an interactive terminal.
- tests.test_recording.snapshots(stream)[source]¶
Replay a stream, keeping the screen each redraw leaves behind.
Splitting on the erase-in-line is what a real sampler does with timestamps instead: every animation ends a frame with one.
- tests.test_recording.test_screen_replays_a_stream(stream, expected)[source]¶
The screen shows what a terminal would, for the vocabulary it follows.
- tests.test_recording.test_screen_keeps_a_color_set_between_a_return_and_its_text()[source]¶
A color opened after the cursor comes back still wraps what follows.
An animation writes the return, then its color, then the text. The redraw is deferred until something lands on the row, and the color is the first thing to land: it must trigger the clear and survive it, or the frame draws in the terminal’s default ink.
- tests.test_recording.test_screen_feeds_the_same_whether_split_or_whole()[source]¶
A stream arriving in pieces lands where the whole of it would.
A recorder reads whatever the terminal hands it, which is not aligned on anything: a frame routinely arrives across two reads.
- tests.test_recording.test_screen_counts_a_wide_glyph_by_its_cells()[source]¶
A wide glyph covers the two cells it is drawn with.
The count is what says whether the cursor sits at the start of a row, which is what an erase-in-line acts on.
- tests.test_recording.test_screen_recovers_every_frame_a_spinner_draws(monkeypatch)[source]¶
Replaying a real spinner’s stream gives back its frames, and only those.
The end of the loop this module exists to close:
frame_lines()says what the spinner draws, the spinner writes it at a terminal, and the screen rebuilds those same lines out of the stream.
- class tests.test_recording.StepClock(step=1.0)[source]¶
Bases:
objectA clock advancing a fixed step per reading, so a test can time frames.
- tests.test_recording.test_recorder_claims_to_be_a_terminal()[source]¶
A spinner animates for a recorder without being told to.
The whole question a spinner asks before drawing is whether its stream is a terminal. Answering it is what makes the in-process path need no pseudo-terminal, and therefore work on every platform.
- tests.test_recording.test_recorder_frames_carry_what_the_screen_held()[source]¶
Each frame lasts until the next screen replaced it.
- tests.test_recording.test_recorder_drops_a_write_that_changed_nothing()[source]¶
A write leaving the screen as it was is not a frame of its own.
- tests.test_recording.test_recorder_drops_a_cursor_shape_change()[source]¶
A sequence the screen does not act on adds no frame, whatever its bytes.
ESC [ 2 SP qsets the cursor’s shape and carries an intermediate byte. A grammar stopping at the first letter missed it, so the sequence landed on the screen as text and split one picture into two identical frames.
- tests.test_recording.test_recorder_stores_one_sample_per_distinct_screen()[source]¶
A write changing nothing stores nothing, so memory follows the screens.
A spinner redraws the same frame for as long as its label holds, and a long recording would otherwise keep a copy of the whole screen per redraw.
- tests.test_recording.test_recorder_drops_a_screen_holding_only_blanks()[source]¶
An erasure, and the newline ending it, picture nothing.
- tests.test_recording.test_recorder_records_a_spinner_without_a_terminal(monkeypatch)[source]¶
A spinner handed a recorder animates into it, on any platform.
No pseudo-terminal is involved, which is what a documentation build on Windows depends on.
- tests.test_recording.BAR_SCRIPT = "import sys, time\nfor filled in range(4):\n sys.stderr.write('\\r[' + '#' * filled + ']\\x1b[K')\n sys.stderr.flush()\n time.sleep(0.05)\n"¶
A progress bar drawn to
stderr, redrawn in place, as a real one is.
- tests.test_recording.test_record_command_recovers_a_foreign_animation()[source]¶
A command this process does not host still animates, under a terminal.
The case a pipe cannot reach: the program asks whether it is talking to a terminal, and only a pseudo-terminal makes it draw. Its frames go to
stderr, where a spinner and a progress bar both write.
- tests.test_recording.STUBBORN_SCRIPT = '\nimport signal, sys, time\nsignal.signal(signal.SIGTERM, signal.SIG_IGN)\nsys.stdout.write("ripe")\nsys.stdout.flush()\ntime.sleep(6)\n'¶
A command that ignores
SIGTERMand outlives the recording.
- tests.test_recording.test_record_command_kills_a_command_ignoring_sigterm(monkeypatch)[source]¶
A command ignoring
SIGTERMis killed once the grace runs out, so the recording returns instead of waiting for the command to exit on its own.
- tests.test_recording.SLEEPER_SCRIPT = '\nimport pathlib, sys, time\nsys.stdout.write("ripe")\nsys.stdout.flush()\npathlib.Path(sys.argv[1]).touch()\ntime.sleep(30)\n'¶
A command that draws once, touches the file it is given, then waits for a signal to end it.
- tests.test_recording.test_record_command_registers_its_command_for_ctrl_c(tmp_path)[source]¶
A recording registers its command where the Ctrl+C handlers find it.
A worker thread records while the main thread calls
terminate_live_processes, which is what a Ctrl+C runs: the command ends, and so does the recording, long before itsduration. The interrupt waits for the command to draw, which a slow runner can delay past registration.
- tests.test_recording.STUBBORN_PARENT_SCRIPT = '\nimport signal, subprocess, sys, time\nsignal.signal(signal.SIGTERM, signal.SIG_IGN)\nseed = subprocess.Popen((sys.executable, "-c", "import time; time.sleep(30)"))\nsys.stdout.write(f"seed={seed.pid}")\nsys.stdout.flush()\ntime.sleep(30)\n'¶
A command ignoring
SIGTERMthat starts a child of its own, which does too.
- tests.test_recording.test_record_command_kills_what_its_command_started(monkeypatch)[source]¶
A command killed at the end of its recording takes its descendants along.
They would otherwise outlive the recording, holding the terminal it drew on.
- tests.test_recording.SPLIT_GLYPH_SCRIPT = '\nimport sys, time\nraw = sys.stdout.buffer\nglyph = "╰".encode("UTF-8")\nraw.write(glyph[:1])\nraw.flush()\ntime.sleep(0.2)\nraw.write(glyph[1:] + b" ripe")\nraw.flush()\n'¶
A box-drawing corner flushed one byte at a time, as a busy pty read splits it.
- tests.test_recording.test_record_command_reassembles_a_glyph_split_across_reads()[source]¶
A multi-byte glyph straddling two pty reads decodes whole.
A pseudo-terminal hands the stream back in kernel-buffer-sized chunks, so a heavy redraw regularly splits a box-drawing glyph or an emoji across two reads. Decoding each chunk on its own mangles both halves into
U+FFFD; the recorder must hold the partial sequence until its tail arrives.
- tests.test_recording.test_record_command_closes_its_terminal_when_the_command_cannot_start(tmp_path)[source]¶
A command that never started leaves no end of its terminal open.
The pseudo-terminal is opened before the command is spawned, so a spawn refused by the system is the one exit that has both ends to close.
- tests.test_recording.test_record_and_render_draws_the_prompt_on_every_frame()[source]¶
The stated invocation leads each frame, styled as a shell prompt.
- tests.test_recording.test_record_and_render_hides_an_empty_prompt()[source]¶
An empty –prompt draws no invocation at all.
- tests.test_recording.test_record_and_render_reports_the_exit_code()[source]¶
The command’s own verdict travels beside the picture of it.
- tests.test_recording.test_record_and_render_refuses_an_empty_recording()[source]¶
A command that drew nothing leaves nothing to animate.
- tests.test_recording.text_columns(svg, text)[source]¶
Every column a capture starts a run reading
texton, in any frame.
- tests.test_recording.test_record_and_render_numbers_every_frame_in_one_gutter(monkeypatch)[source]¶
A screen reaching ten rows does not push its text a cell to the right.
Each frame counts its own rows, from one. Numbered on its own, a nine-row screen aligns its numbers on one digit and the ten-row screen after it on two, so every row moves between the two frames. The recording is stated rather than timed, so both screens are sure to be in it.
- tests.test_recording.test_quantize_lands_jitter_on_one_grid(recorded, expected)[source]¶
Two runs of one unchanged command time their frames the same.
- tests.test_recording.test_quantize_keeps_the_frames_it_times()[source]¶
Rounding a duration never rewrites the screen it belongs to.
- tests.test_recording.test_quantized_recordings_of_one_command_render_the_same_bytes()[source]¶
The point of the exercise: a rerun rewrites the asset byte for byte.
Two recordings of one unchanged command, timed a few milliseconds apart the way two runs on a differently loaded machine are.
- tests.test_recording.test_a_dropped_frame_does_not_move_the_recording()[source]¶
A frame the scheduler lost leaves the animation’s identity alone.
Quantizing cannot recover it, so the fingerprint is written not to care: it covers the frames a cycle holds and the beat, not how many were caught.
- tests.test_recording.test_a_changed_command_moves_the_recording()[source]¶
What the fingerprint is for: real drift is still reported.
- tests.test_recording.TYPED_PROMPT = '\x1b[90m$\x1b[0m \x1b[97mbasket\x1b[0m ripen --all'¶
A styled prompt line, as
format_cli_promptcomposes one.
- tests.test_recording.test_ansi_prefix_keeps_the_styling_in_force(count, expected)[source]¶
An escape is drawn nowhere, so every one before the cut still applies.
- tests.test_recording.test_ansi_prefix_never_cuts_an_escape(count)[source]¶
A half-written escape lands on the screen as the digits it is made of.
- tests.test_recording.test_type_line_makes_one_frame_per_character()[source]¶
A keystroke is a screen, so a character is a frame.
- tests.test_recording.test_type_line_holds_the_finished_line_for_the_submit_beat()[source]¶
The pause before the return key, and only on the frame that waits it.
- tests.test_recording.test_type_line_types_nothing_for_an_empty_line()[source]¶
A recording carrying no prompt has no opening to play.
- tests.test_recording.test_type_line_rejects_a_beat_going_nowhere(kwargs, message)[source]¶
Seconds run forwards, and a frame lasting none is never shown.
- tests.test_recording.test_a_typed_opening_walks_its_cursor_one_cell_at_a_time()[source]¶
The caret is the cursor, and it follows the text with no code of its own.
What the design rests on: a cursor is drawn as part of the row it stands on, read off that row’s text, so an animation typing a line gets its caret for nothing.
- tests.test_recording.test_a_recording_types_its_prompt_when_asked()[source]¶
The typed opening leads the recorded frames, under the prompt it types.
- tests.test_recording.test_a_recording_types_nothing_unless_asked()[source]¶
By default, a recording opens on the finished prompt.
- tests.test_recording.RIPENED_SCRIPT = "import sys, time\nfor filled in range(4):\n sys.stderr.write('\\r[' + '#' * filled + ']\\x1b[K')\n sys.stderr.flush()\n time.sleep(0.05)\nsys.stderr.write('\\r\\x1b[Kripe\\n')\n"¶
The same bar, ending on a newline the way a finished command does.
- tests.test_recording.last_drawn_row(svg)[source]¶
Index of the lowest row a capture draws a glyph on, counted from zero.
Matched on the styled runs alone: the caption and the credit line are
<text>too, and both sit outside the grid this measures.- Return type:
- tests.test_recording.recorded(script, **kwargs)[source]¶
Record one script, however the capture is asked to close.
- Return type:
- tests.test_recording.test_a_closing_prompt_fills_the_row_the_cursor_waited_on()[source]¶
A command closing on a newline already left the row the shell wants.
Which is what makes the pair free: the window is the same height either way, and the row holding nothing but a cursor holds a prompt instead.
- tests.test_recording.test_a_closing_prompt_opens_a_row_under_a_screen_left_mid_line()[source]¶
A command that never ended its line is given one, as a shell gives it.
The free case above is the common one and not the only one: a progress bar redrawing in place leaves the cursor mid-line, so the shell coming back costs the row it prints its own newline onto.
tests.test_screenshots module¶
Tests for click_extra.capture and the captures committed under docs/.
A screenshot is CLI output that stopped re-running, so it rots the moment the
command it pictures changes. A rendered capture is machine-readable, which is
what makes both halves of this module possible: every run of same-styled
characters is one <text> element carrying its column as an x offset and its
width as a textLength, so the terminal text can be rebuilt from the glyph
coordinates and compared to what a CLI prints today.
That comparison needs neither a network round-trip nor a capture tool, which
keeps this an ordinary unit test. When the drift check fails, re-run the command
documented alongside the image in docs/screenshots.md.
- tests.test_screenshots.ASSETS = PosixPath('/home/runner/work/click-extra/click-extra/docs/assets')¶
Directory the committed captures live in.
- tests.test_screenshots.COMMITTED_PROMPT = '$ '¶
Prompt sigil every committed capture was shot under.
PROMPTis the running platform’s, and Windows draws>there, so a reshoot has to be normalized back to this one before the two are compared. What the check is about is the command’s output, not which shell drew the line above it.
- class tests.test_screenshots.Capture(filename, args, columns=80, head=None, background=TerminalBackground.DARK)[source]¶
Bases:
NamedTupleA terminal capture committed under
docs/assetsand embedded in the docs.Carries what it takes to reshoot it, which is what makes the drift check below able to state the command each image is supposed to picture.
Create new instance of Capture(filename, args, columns, head, background)
- columns: int¶
Terminal width it was shot at.
A CLI wraps its output to the ambient terminal size, so a check running under a different width would compare a differently wrapped screen and fail on the wrapping alone.
- background: TerminalBackground¶
Chrome it was drawn on, which is also the terminal it was told it ran in.
- property prompt: str¶
The
COMMITTED_PROMPTline the capture draws above its output.
- property command: tuple[str, ...]¶
The command line reproducing this capture through this interpreter.
docs/screenshots.mddocuments the same commands reached throughuv run, which is what a human types from a checkout. Going straight atsys.executablekeeps the check independent of uv, and of whether the environment happens to be synced.
- tests.test_screenshots.COMMITTED_CAPTURES = (('auto-theme-dark-screen.svg', ('--theme', 'auto', 'themes', '--help'), 80, None, TerminalBackground.DARK), ('auto-theme-light-screen.svg', ('--theme', 'auto', 'themes', '--help'), 80, None, TerminalBackground.LIGHT), ('color-gradient-screen.svg', ('gradient',), 80, None, TerminalBackground.DARK), ('text-styles-screen.svg', ('styles',), 160, 14, TerminalBackground.DARK), ('theme-gallery-screen.svg', ('themes',), 80, 34, TerminalBackground.DARK))¶
Every capture shot by the
screenshotcommand, in file-name order.The
hello-click*pair opening the readme is not here: those are written by the tutorial’s ownclick:runblocks at documentation-build time, and guarded bytests/test_sphinx_crossrefs.pyinstead.
- tests.test_screenshots.SAMPLE_CAPTURE = '\x1b[1mFruit\x1b[0m Colour\nbanana yellow\nkiwi green\n\n2 fruits\n'¶
A small styled capture, with padded columns and a blank line to preserve.
The second column is deliberately left unstyled: sharing a style with the padding that precedes it is what folds the two into a single run, which is the case that would otherwise place a column by the width of the spaces before it. Style the column and the padding becomes a run of its own.
- tests.test_screenshots.svg_to_lines(svg)[source]¶
Rebuild the captured terminal lines from a rendered capture.
Groups the
<text>runs into lines by their sharedybaseline, then lays each one back on its column: the capture is monospaced, so dividing a run’sxoffset byCELL_WIDTHgives the character column it starts at.
- tests.test_screenshots.svg_window(svg)[source]¶
The width and height of the terminal window drawn in a capture.
- tests.test_screenshots.html_to_text(markup)[source]¶
Strip a capture’s markup back off, leaving the terminal text.
- Return type:
- tests.test_screenshots.test_trim_lines(head, tail, expected)[source]¶
headandtailkeep their ends and account for what they dropped.
- tests.test_screenshots.test_trim_lines_rules_the_default_marker_across_the_picture()[source]¶
The default marker is a rule, spanning the width the picture is laid out at: the widest kept line when the width is left to the command.
- tests.test_screenshots.test_trim_lines_caps_the_rule_at_the_terminal_width()[source]¶
A kept line wider than the capture draws a rule as wide as the capture.
The rule is measured on the kept lines, so a 150-cell log line among them asked for a 150-cell rule in an 80-column image. The renderer folds any line reaching past the width, so that rule came back as a row of dots plus its tail alone on a second row, started outside the text column.
- tests.test_screenshots.test_rule_marker_tracks_the_drawn_width(columns, width_floor, expected)[source]¶
The
autorule spans the text column the picture is drawn at.
- tests.test_screenshots.test_resolve_wrap_marker(wrap_marker, expected)[source]¶
Only
autois replaced: any other marker, empty included, is taken as is.
- tests.test_screenshots.test_trim_lines_leaves_room_for_the_wrap_marker()[source]¶
A rule capped to the width gives up the cell a wrap marker takes.
Capped to
columnsalone, the rule filled the row the marker shortens, so it folded on its last dot and closed its row on the marker, reading as a rule that continues below.
- tests.test_screenshots.test_capture_leaves_room_for_line_numbers_on_the_rule(prompt, head, tail)[source]¶
A numbered capture rules its cut inside the text column the gutter leaves.
The gutter takes cells in front of every row, the rule’s included, so a rule as wide as
columnswould fold on its last dots and put them alone on a row of their own.
- tests.test_screenshots.test_render_draws_the_wrap_marker_at_each_fold()[source]¶
An SVG capture closes each folded row on the marker, and only those rows.
- tests.test_screenshots.test_trim_lines_closes_a_style_the_cut_left_open(truncation)[source]¶
A cut between a style’s opening and its reset does not style the marker.
A heading printed as
style("\n● Step")opens its style on the line above the text and resets it on the text’s own line. Cut between the two, the open underline ran into the marker, and into the linestailkept after it.
- tests.test_screenshots.test_trim_lines_adds_no_reset_after_a_closed_style()[source]¶
A head that closes every style it opens gets its marker untouched.
- tests.test_screenshots.test_trim_lines_keeps_a_marker_too_wide_for_its_rule_bare()[source]¶
A capture narrower than the frame gets the label alone, not a broken rule.
- tests.test_screenshots.test_trim_lines_leaves_an_explicit_marker_alone()[source]¶
Naming a marker takes it verbatim: only
autoasks for a rule.
- tests.test_screenshots.test_tile_runs_never_opens_a_piece_on_padding()[source]¶
A tiled column cut at a blank carries it into the piece before it.
A piece is placed by its first glyph, so one opening on a blank draws every tile behind it a cell late. The cut is made on character count, which lands on a blank whenever a label sits inside a rule.
- tests.test_screenshots.test_tile_runs_keeps_every_piece_on_its_own_column()[source]¶
Cutting a tiled column apart never moves a glyph off its cell.
The cut counts characters and the offset counts cells, which part ways on a wide character.
- tests.test_screenshots.test_tile_runs_cuts_every_tile_it_was_released_with(tile)[source]¶
tile_runscuts Block Elements as well as Box Drawing, as10.1.1did.A capture draws both as shapes, which leaves the deprecated helper no caller in the renderer: its answer stays the released one until it goes. The cut is spelled out, so
TILE_RUNkeeping its released 8 cells is checked too.
- tests.test_screenshots.test_tile_helpers_warn_until_their_removal(name, private)[source]¶
The tile helpers a capture no longer uses still resolve, with a warning.
- tests.test_screenshots.test_svg_module_serves_no_other_missing_name()[source]¶
A name the module never had stays missing, and sets off no warning.
- tests.test_screenshots.named_eighths(char)[source]¶
The eighths of its cell a Block Elements character fills, read off its name.
A Unicode name states the shape outright, like
LOWER THREE EIGHTHS BLOCKorQUADRANT UPPER LEFT AND LOWER RIGHT, which makes it a second source to holdBLOCK_ELEMENTSagainst.
- tests.test_screenshots.test_block_elements_cover_their_whole_unicode_block()[source]¶
Every Block Elements character has a shape, and is one cell wide.
- tests.test_screenshots.test_block_element_fills_what_its_name_states(char)[source]¶
Each Block Elements character fills the part of its cell its name states.
- tests.test_screenshots.test_shape_runs_cut_shapes_from_the_text_between(text, column, expected)[source]¶
Shapes are cut out on their own columns, and nothing else.
Box Drawing, Block Elements and Braille patterns are drawn as shapes. One following a zero width joiner belongs to the cluster before it, and stays in its text.
- tests.test_screenshots.test_shape_runs_place_each_piece_where_cell_width_counts_it(text)[source]¶
Each piece starts on the column
cell_widthcounts up to it.cell_widthcounts the character after a zero width joiner in the cluster before it. A shape cut out of that cluster would push the text after it a cell past its column, onto the run the grid places next.
- tests.test_screenshots.test_braille_dots_match_their_unicode_names(code)[source]¶
Each Braille pattern raises the dots its Unicode name lists.
- tests.test_screenshots.test_braille_dots_keep_one_pitch_across_cells()[source]¶
Two stacked rows of full Braille cells plot one even grid of dots.
The dot positions are rounded to a tenth of a pixel, the precision every coordinate of a capture is written at.
- tests.test_screenshots.named_arms(char)[source]¶
The arms of a Box Drawing line, read off its Unicode name.
A name states one weight for all its arms, as in
LIGHT DOWN AND RIGHT, or one per group of arms, as inDOWN LIGHT AND RIGHT HEAVY.
- tests.test_screenshots.test_box_drawing_covers_its_whole_unicode_block()[source]¶
Every Box Drawing character is a line, a dash or a curve, one cell wide.
- tests.test_screenshots.test_box_arms_match_their_unicode_names(char)[source]¶
Each Box Drawing line has the arms and weights its Unicode name lists.
- tests.test_screenshots.test_box_dashes_match_their_unicode_names(char)[source]¶
Each dashed line runs the way, weighs and counts what its name says.
- tests.test_screenshots.test_box_strokes_join_as_menlo_draws_them(char, expected)[source]¶
A line’s strokes meet where Menlo
22.0d2e1joins them.
- tests.test_screenshots.test_double_cross_leaves_its_gaps_open()[source]¶
No stroke of
╬crosses into the gaps between its double lines.
- tests.test_screenshots.test_box_rule_is_one_shape_and_its_lines_meet_across_rows()[source]¶
A rule merges across its cells, and a vertical line runs on into the next row.
- tests.test_screenshots.test_blank_braille_draws_nothing_and_keeps_its_text()[source]¶
The blank pattern raises no dot, and still reads back as itself.
- tests.test_screenshots.test_stacked_blocks_leave_no_gap()[source]¶
Two rows of full blocks paint one unbroken column, whatever the font.
A font draws
█shorter than a capture’s row, so the same picture set as glyphs leaves a band of background between the two rows.
- tests.test_screenshots.test_half_block_splits_its_cell_in_the_middle()[source]¶
▀inks the top half of its cell and leaves the bottom to the background.
- tests.test_screenshots.test_block_run_is_one_shape()[source]¶
A bar of
█is one rectangle, not one per cell.
- tests.test_screenshots.test_shade_mixes_its_ink_with_the_paper()[source]¶
▒is half ink, mixed into the color behind it.
- tests.test_screenshots.test_shapes_stay_in_the_text()[source]¶
A run drawn as shapes keeps its characters, as text that paints nothing.
- tests.test_screenshots.test_no_committed_capture_draws_shapes_as_glyphs()[source]¶
No committed capture draws Box Drawing, Block Elements or Braille as glyphs.
A capture written before the renderer did still shows a seam at the joins of its box drawing, a band between its stacked rows of block art, or Braille wider than its column. Rewrite it the way its page documents: a recording, by deleting it and building again.
- tests.test_screenshots.test_style_rules_writes_one_declaration_per_property(sgr, expected)[source]¶
Every decoration reaches the CSS, and the three share one declaration.
CSS keeps the last of a repeated property, so a run written as two
text-decorationdeclarations loses all but one of them.overlinehad no branch at all, which left the column ofclick-extra stylesnaming it identical to the plain one in every viewer.
- tests.test_screenshots.test_render_folds_an_unusable_unique_id()[source]¶
A file name that is not a CSS identifier cannot leak into a class name.
- tests.test_screenshots.test_render_places_every_run_on_its_own_column()[source]¶
Rebuilding the terminal text from a capture gives the text back.
Each run is placed by its
xoffset alone, so laying the runs back on the columns that offset names has to reproduce what the terminal printed.
- tests.test_screenshots.test_no_run_starts_on_padding()[source]¶
No visible run in a capture is positioned by the spaces preceding it.
The invariant the column arithmetic exists for: a renderer that ignores
textLengthor cannot resolve the font still lands every run where it belongs. Checked against every committed capture as well as a fresh render, so a regression cannot hide in the assets or in the renderer alone.
- tests.test_screenshots.test_capture_clip_holds_every_descender()[source]¶
The terminal’s clip is never shorter than the text it contains.
A clip cropped to the text block’s height minus a pixel cuts the descenders off the last line, turning an underscore into a blank. Checked against every committed capture, which is where such a crop would go unnoticed.
- tests.test_screenshots.test_no_capture_references_a_missing_clip()[source]¶
Every
clip-patha capture uses resolves to aclipPathit defines.A dangling reference is rendered inconsistently: a browser drops the clip, a strict SVG 1.1 renderer drops the whole element with it.
- tests.test_screenshots.ANIMATION_PRESETS = ('aesthetic', 'arc', 'arrow', 'arrow2', 'arrow3', 'balloon', 'balloon2', 'beta-wave', 'binary', 'blue-pulse', 'bounce', 'bouncing-ball', 'bouncing-bar', 'box-bounce', 'box-bounce2', 'christmas', 'circle', 'circle-halves', 'circle-quarters', 'clock', 'dots', 'dots-8bit', 'dots-circle', 'dots10', 'dots11', 'dots12', 'dots13', 'dots14', 'dots2', 'dots3', 'dots4', 'dots5', 'dots6', 'dots7', 'dots8', 'dots9', 'dqpb', 'dwarf-fortress', 'earth', 'finger-dance', 'fish', 'fist-bump', 'flip', 'grenade', 'grow-horizontal', 'grow-vertical', 'hamburger', 'hearts', 'layer', 'line', 'line2', 'material', 'mindblown', 'monkey', 'moon', 'noise', 'orange-blue-pulse', 'orange-pulse', 'pipe', 'point', 'pong', 'rolling-line', 'runner', 'sand', 'shark', 'simple-dots', 'simple-dots-scrolling', 'smiley', 'soccer-header', 'speaker', 'square-corners', 'squish', 'star', 'star2', 'toggle', 'toggle10', 'toggle11', 'toggle12', 'toggle13', 'toggle2', 'toggle3', 'toggle4', 'toggle5', 'toggle6', 'toggle7', 'toggle8', 'toggle9', 'triangle', 'weather')¶
Every bundled spinner: the population an animated capture is checked over.
- tests.test_screenshots.animated_capture(name)[source]¶
Draw one bundled spinner as an animated capture.
- Return type:
- tests.test_screenshots.stylesheet_of(svg)[source]¶
The
<style>block of a rendered capture.- Return type:
- tests.test_screenshots.claimed_names(svg)[source]¶
Every name a capture’s stylesheet claims in the scope of a whole page.
Class selectors and keyframe names both: inlined into one HTML document, the stylesheets of two captures are global and share that scope.
- tests.test_screenshots.test_animated_capture_defines_every_class_it_uses(name)[source]¶
No frame names a class its capture’s stylesheet leaves undefined.
An undefined class does not blank its frame: the text falls back to the presentation attributes and draws in a default face in the window’s own ink. The animation then appears to reset its styling once a cycle, only the frames carrying a rule looking right.
- tests.test_screenshots.test_animated_capture_draws_its_chrome_once(name)[source]¶
The window, its caption and its clip path are drawn once, not per frame.
- tests.test_screenshots.test_animation_windows_tile_the_cycle(name)[source]¶
Each frame’s window abuts the next, covering the cycle exactly once.
A gap leaves the picture blank for part of every turn; an overlap draws two frames on top of each other.
- tests.test_screenshots.test_animated_capture_draws_a_row_that_never_moves_once()[source]¶
A row identical in every frame is drawn outside them, not once per frame.
On a recording where one line moves under a screen of lines that do not, this is the difference between one copy of that screen and one per frame.
- tests.test_screenshots.test_animated_capture_keeps_a_row_that_moves_in_every_frame()[source]¶
Nothing is shared when every row differs, which is the spinner’s case.
- tests.test_screenshots.BAND_GROUP_RE = re.compile('<g clip-path="url\\(#[\\w-]+-window\\)">(.*?)</g>')¶
The group holding every band, clipped to the window rather than to the text.
- tests.test_screenshots.bands_of(svg)[source]¶
The paint and baseline of each band an emphasized capture draws.
- tests.test_screenshots.test_emphasized_lines_are_banded_where_they_sit()[source]¶
A band lands on the row it names, counted from one, and spans the window.
Edge to edge rather than stopping where the padding does: the row is what is emphasized, not the column of text sitting in it.
- tests.test_screenshots.test_emphasis_is_mixed_from_the_chrome_it_is_drawn_on()[source]¶
One ratio answers for both chromes: lighter on dark, darker on light.
- tests.test_screenshots.test_emphasis_rejects_a_line_the_capture_does_not_have()[source]¶
Naming a line past the end is an authoring error, not a silent no-op.
- tests.test_screenshots.test_screenshot_reports_a_line_the_capture_does_not_have(invoke, tmp_path)[source]¶
The command reports the authoring error as one, not as a traceback.
The height of a capture is only known once its command has run, so the renderer is the one to refuse a band past its end, and the command turns that refusal into its own error message.
- tests.test_screenshots.FRAME_BAND_RE = re.compile('<g class="([\\w-]+)-f(\\d+)"[^>]*clip-path="url\\(#[\\w-]+-window\\)">(.*?)</g>')¶
A band group riding one frame of an animation.
- tests.test_screenshots.test_emphasis_arrives_with_the_row_it_marks()[source]¶
A band shows in the frame that first draws its row, and not before.
Waiting in empty space for the animation to reach it reads as a stray rectangle. Arriving with the row is also the moment a gutter would first number that row, so the two land together.
- tests.test_screenshots.test_emphasis_leaves_a_closing_blank_alone()[source]¶
The empty beat closing a cycle draws no band, having drawn no row.
Two animated captures inlined in one page keep their own timing.
Both stylesheets are global there. A selector they share hands the shorter animation the longer one’s clock, and the frames it does not have go blank for that part of every cycle.
- tests.test_screenshots.test_frame_animation_css_only_ever_animates()[source]¶
Every rule the timing emits sits behind the reduced-motion guard.
Nothing here hides a frame: that is the frames’ own
visibilityattribute, which is what a renderer reading no stylesheet still honors. A reader asking their system for less motion therefore lands on the same still as one whose viewer speaks no CSS animation at all.
- tests.test_screenshots.test_animated_capture_hides_its_frames_outside_the_stylesheet(name)[source]¶
Every frame but the poster is hidden by an attribute, not by a rule.
A renderer free to ignore
<style>would otherwise draw all of them at once, stacked on top of each other. The poster is the last frame, which on an animation that accumulates is the one that says the most.
- tests.test_screenshots.test_animated_capture_renders_the_same_bytes_twice(name)[source]¶
An unchanged animation rewrites byte-identical bytes.
What lets a committed asset be regenerated on every build without dirtying the working tree.
- tests.test_screenshots.test_cursor_cell_reads_the_screen_it_is_given(picture, expected)[source]¶
A frame’s text already says where the terminal left its cursor.
- tests.test_screenshots.test_cursor_cell_matches_the_screen_that_wrote_it()[source]¶
The derived column is the one the screen itself counted, not an estimate.
Locks the claim
cursor_cell()rests on: a recorded frame carries its cursor position implicitly, so nothing has to travel beside the text to say where the cursor was.
- tests.test_screenshots.cursor_rect(svg)[source]¶
The cursor a capture drew, as its x, y, width and height.
Found by its
crispEdgesrendering, which the text of these tests carries on nothing else: they draw unstyled words, so no run brings a background rectangle of its own. The count is asserted rather than assumed, so a test picture that grows a styled run fails here instead of measuring the wrong rectangle.
- tests.test_screenshots.test_a_capture_draws_no_cursor_unless_asked()[source]¶
The default is no cursor, which is what every committed asset shows.
- tests.test_screenshots.test_cursor_is_drawn_in_the_shape_it_names(shape, width, height)[source]¶
Each shape covers the part of its cell that shape is.
- tests.test_screenshots.test_a_shapeless_cursor_takes_the_terminal_its_preset_names(name)[source]¶
--preset windowsdraws Windows Terminal’s bar without stating it.
- tests.test_screenshots.test_a_cursor_under_the_output_grows_the_window_by_that_line()[source]¶
Output closing on a newline leaves the cursor on the row underneath.
- tests.test_screenshots.test_a_cursor_does_not_move_what_emphasize_may_mark()[source]¶
A band picks out a line of output, and a cursor draws no line.
- tests.test_screenshots.test_an_animated_cursor_blinks_on_one_clock()[source]¶
One rule and one keyframe set, however many frames carry a cursor.
A terminal has one cursor. Namespacing the blink per frame would instead let each copy light on its own beat, which reads as several cursors.
- tests.test_screenshots.test_the_blank_closing_a_cycle_shows_no_cursor()[source]¶
An empty terminal has nothing to put a cursor on.
- tests.test_screenshots.test_a_steady_cursor_names_no_class()[source]¶
A class the stylesheet never defines is what breaks a renderer.
- tests.test_screenshots.test_a_blinking_cursor_sits_behind_the_reduced_motion_guard()[source]¶
Blinking is motion, and a reader may have asked for less of it.
- tests.test_screenshots.test_a_blink_never_touches_visibility()[source]¶
Opacity multiplies into a hidden frame’s group; visibility would override it.
A frame is hidden by
visibilityandopacitytogether, andvisibilityis inherited: a cursor restoring it would show through every frame at once, which is the whole reason the blink dims opacity alone.
- tests.test_screenshots.test_blink_css_rejects_a_blink_going_nowhere()[source]¶
A cursor blinking in no time is a cursor that never lights.
- tests.test_screenshots.test_a_capture_rejects_a_cursor_blinking_backwards()[source]¶
Seconds run forwards, here as everywhere else.
- tests.test_screenshots.test_screenshot_cursor_options_need_their_partner(invoke, tmp_path, arguments, message)[source]¶
Each knob names the one it decorates, rather than passing unnoticed.
- tests.test_screenshots.test_screenshot_draws_a_cursor_when_asked(invoke, tmp_path)[source]¶
--cursorputs one on a still capture, blinking on one clock.
- tests.test_screenshots.test_screenshot_draws_no_cursor_unless_asked(invoke, tmp_path)[source]¶
The default is what every capture taken before the option showed.
- tests.test_screenshots.test_screenshot_cursor_takes_the_shape_it_names(invoke, tmp_path, shape, width)[source]¶
A named shape overrides whatever the terminal preset would have drawn.
- tests.test_screenshots.test_screenshot_bare_cursor_takes_the_preset_shape(invoke, tmp_path)[source]¶
--cursoralone draws Windows Terminal’s bar under--preset windows.
- tests.test_screenshots.test_screenshot_record_types_its_prompt(invoke, tmp_path)[source]¶
--typingopens the recording on the command line being typed.
- tests.test_screenshots.test_auto_columns_leaves_room_for_a_cursor()[source]¶
An auto width derives from the text, which always ends on its last cell.
Without the extra cell the cursor wraps onto a row of its own, which is a row holding nothing but a cursor.
- tests.test_screenshots.test_auto_columns_with_a_cursor_on_a_blank_screen(picture)[source]¶
A blank screen shows no cursor, and leaves the width to its floor.
No frame placing a cursor left
max()with the width alone to compare, which raisedTypeError: 'int' object is not iterablefor a command printing nothing under--prompt "".
- tests.test_screenshots.test_a_closing_prompt_belongs_to_the_last_frame_alone()[source]¶
Closing every frame would say the command exited before it had.
Counted rather than located, the sigil landing on a different row in each frame of an animation that grows. A row drawn the same in every frame is drawn once, so an unclosed animation shows its opening sigil a single time and each closed frame adds one of its own: measured at 1, 2 and 5 here.
- tests.test_screenshots.test_append_prompt_closes_a_finished_screen(text, expected)[source]¶
The shell comes back on the row under the output, and on no other.
- tests.test_screenshots.test_append_prompt_takes_the_sigil_its_preset_draws()[source]¶
A capture of Windows Terminal closes on the shell that terminal runs.
- tests.test_screenshots.test_a_closing_prompt_costs_no_height_beside_a_cursor()[source]¶
The cursor already claimed that row: the prompt only fills it.
What makes the pair worth having. A cursor alone leaves a row holding nothing, and the sigil turns it into the shell waiting.
- tests.test_screenshots.test_screenshot_closes_a_still_with_a_prompt(invoke, tmp_path)[source]¶
--closing-promptdraws the shell coming back under the output.
- tests.test_screenshots.test_auto_hold_scales_with_the_final_screen(text, expected)[source]¶
The pause follows the populated lines of the closing screen.
- tests.test_screenshots.test_animated_capture_resolves_an_auto_hold()[source]¶
hold="auto"renders exactly as the resolved pause would.
- tests.test_screenshots.test_animated_capture_rejects_an_unknown_hold_word()[source]¶
A word the annotation forbids still has to fail loudly at runtime.
- tests.test_screenshots.test_animated_capture_rejects_a_negative_pause(pause)[source]¶
A pause runs forward or not at all, whichever end of the cycle it sits on.
- tests.test_screenshots.test_parse_hold(value, expected)[source]¶
A hold is seconds, or the one word asking the final screen to decide.
- tests.test_screenshots.test_parse_hold_rejects(value, message)[source]¶
A hold that is neither seconds nor
autosays what it would take.
- tests.test_screenshots.test_parse_pause(value, expected)[source]¶
A pause is seconds, zero included.
- tests.test_screenshots.test_html_capture_does_not_animate()[source]¶
An HTML capture is selectable text, which has no frame to hide.
The refusal names the format the way a reader writes it, not as the
CaptureFormat.HTMLmember it is held in.
- tests.test_screenshots.test_column_segments(text, expected)[source]¶
A run is cut at its gutters, and each column carries the one it starts on.
- tests.test_screenshots.test_no_capture_places_a_column_by_its_padding()[source]¶
No column in a capture is positioned by the blanks in front of it.
The invariant that lets a capture survive a renderer ignoring
textLength:librsvg(and through itrsvg-convertand ImageMagick) lays every run out at the font’s natural width, so a gutter paid for in glyphs collapses and the columns slide onto each other. Each column carrying its ownxasks the renderer for nothing but coordinates.
- tests.test_screenshots.test_no_capture_clips_through_a_transform()[source]¶
The element a capture’s clip is hung on carries no transform of its own.
A
clipPathinuserSpaceOnUseresolves against “the user coordinate system in place when it is referenced”, and renderers disagree over whether the referencing element’s owntransformcounts. Hung on a translated group, the two readings differ by the translation, and the one that ignores it crops the text partway down the window: what macOS Finder’s thumbnailer does.
- tests.test_screenshots.test_no_capture_hangs_a_filter_on_its_window()[source]¶
The window’s own rectangle carries no filter.
An element whose filter a renderer cannot resolve is an element in error, which the spec answers by not rendering it at all. Hung on the window, a filter costs the background, the frame and the shadow together and leaves the text floating on the page: what macOS Finder’s thumbnailer and ImageMagick both do with a capture built that way.
- tests.test_screenshots.test_capture_states_its_font_outside_the_stylesheet()[source]¶
The terminal text names its face, size and color as attributes too.
A renderer is free to ignore a
<style>block, and several do. The text then falls back to a proportional face at a default size in default black, which is a terminal capture with neither its grid nor its colors.
- tests.test_screenshots.test_every_capture_declares_its_encoding()[source]¶
A capture states UTF-8 outright rather than leaving it to be guessed.
A standalone SVG carries no HTTP header to say so. XML defaults to UTF-8 with no declaration, but WebKit applies its HTML fallback to the document encoding instead, which renders every multi-byte character as mojibake: a full block becomes
â, and a capture of colored output becomes a wall of accented letters in macOS Quick Look.
- tests.test_screenshots.test_no_capture_fetches_a_font()[source]¶
A capture resolves its fonts locally, or falls back down its own stack.
A committed image is read offline, on a page forbidding third-party requests, and by renderers that speak no
@font-face.
- tests.test_screenshots.test_wide_glyphs_are_sized_by_cell(text, characters, cells)[source]¶
A run is drawn as wide as the cells it occupies, not its character count.
A CJK ideograph takes two terminal cells, so sizing a run by
len()squeezes it to half its width and stacks the glyphs on each other.
- tests.test_screenshots.test_capture_output_forces_color_and_pins_width(monkeypatch)[source]¶
A capture overrides the environment’s color opt-out and terminal width.
- tests.test_screenshots.test_capture_output_states_the_terminal_it_simulates(background, monkeypatch)[source]¶
The chrome reaches the command as the variables a terminal would set.
Which is what lets a CLI asking for
--theme autorender for the window its capture lands in, instead of falling back to dark inside a light one.
- tests.test_screenshots.test_capture_output_hides_the_terminal_it_runs_from(variable, monkeypatch)[source]¶
The terminal taking the capture never reaches the command captured.
Left through, a command tailoring its output to that terminal draws one picture on the machine that took the capture and another everywhere else, which no committed asset survives.
- tests.test_screenshots.test_capture_output_keeps_stderr_out_unless_asked()[source]¶
Only
stdoutis captured by default, so a wrapper’s chatter stays out.
- tests.test_screenshots.CAPTURE_CLICK_VERSION = (8, 5)¶
Click release whose help layout the committed captures picture.
Click
8.5.0gave positional arguments a section of their own, reshaping every help screen it renders. A capture is a picture of one Click’s output, so the matrix cells pinning an older Click inside the supported range compare it against a screen that never carried that section. TheClick releasedcells still check every committed capture, which is where the check belongs.
- tests.test_screenshots.test_committed_capture_matches_cli(committed)[source]¶
Every committed capture still pictures what its command prints today.
The capture is reshot through
capture(), the very pipeline that produced the committed file, and the two are compared as terminal text. Going through the whole pipeline rather than against the command’s raw output is what keeps the check honest on both counts the two differ:a command wrapping to an 80-column terminal is handed
min(columns, max_width) - 2, while Click’s ownCliRunnerpinsFORCED_WIDTHto 80, so an in-process render sits two columns wider;the renderer folds a line longer than the terminal, exactly as a terminal does, so a help screen carrying an unwrappable option list (
--table-formatand its 463-character choice list) occupies more rows in the image than the command printed.
Only the text is compared, so restyling a theme does not redden this.
- tests.test_screenshots.test_capture_format_from_path(filename, expected)[source]¶
A capture’s format is read off the file name it is written under.
- tests.test_screenshots.test_capture_format_from_path_rejects_an_unknown_extension(filename)[source]¶
An extension naming no format says which ones do.
- tests.test_screenshots.test_capture_format_from_path_reads_a_file_named_dash_as_a_file(tmp_path)[source]¶
Only a bare
-is the terminal: a file called-has an extension to lack.--output ./-reaches here resolved to an absolute path, and the file it names is as nameable as any other.
- tests.test_screenshots.test_deliver_capture_writes_a_file_named_dash(tmp_path, capsys)[source]¶
A resolved
-is a file to write, not the terminal to print on.
- tests.test_screenshots.test_screenshot_does_not_print_a_resolved_dash(invoke)[source]¶
--output ./-is a file in the working directory, which names no format.Click resolves it to an absolute path whose last component is
-, which is not the bare-naming the terminal.
- tests.test_screenshots.test_render_draws_on_the_chrome_it_is_given(format, background, expected, unwanted)[source]¶
Both renderers answer to the chrome, so a light capture stays readable.
The colors are the contract: an SVG paints its window rect with them, and an HTML capture inlines them on its
<pre>.
- tests.test_screenshots.test_render_frames_with_a_border_its_chrome_can_show(background)[source]¶
Each chrome frames its window in something its own background shows.
The regression this guards is a light capture keeping the translucent white a dark renderer draws by default: a white window on a white page, whose shape is left for the reader to infer from the text inside it.
- tests.test_screenshots.test_chrome_resolves_what_the_capture_draws(background)[source]¶
Nonefields take the background’s and the preset’s own; stated ones stay.
- tests.test_screenshots.test_capture_functions_take_one_chrome(function)[source]¶
The window is one
chromeargument everywhere, with one default.
- tests.test_screenshots.test_renderers_take_one_resolved_chrome(renderer)[source]¶
The two renderers take the window as one chrome, bare unless stated.
A renderer draws the values it is given, so it refuses a chrome still carrying a
Nonethat onlyresolve()can fill.
- tests.test_screenshots.test_bare_chrome_is_resolved(background)[source]¶
The renderers’ default draws as it stands: resolving it changes nothing.
- tests.test_screenshots.test_render_rejects_an_unknown_argument()[source]¶
A typo is still refused, as a signature naming every argument would.
- tests.test_screenshots.test_render_paints_what_it_is_given()[source]¶
A caller’s own frame and shadow reach the window.
- tests.test_screenshots.test_render_states_the_whole_window()[source]¶
Corner radius, frame thickness, backdrop and title all reach the source.
- tests.test_screenshots.test_gradient_svg(backdrop, expected)[source]¶
A CSS gradient becomes a paint server; anything else stays a color.
- tests.test_screenshots.test_gradient_svg_places_the_line_in_user_space(value, expected)[source]¶
The gradient line runs through the center, at the angle asked for.
- tests.test_screenshots.test_render_paints_a_gradient_backdrop()[source]¶
A gradient backdrop reaches the capture as a declared paint server.
- tests.test_screenshots.test_number_lines(text, expected)[source]¶
Each line gets its number, right-aligned on the widest one.
- tests.test_screenshots.test_preset_catalog(name)[source]¶
Every preset carries a full palette on both of its dresses.
- tests.test_screenshots.test_palette_color(color, expected)[source]¶
A style’s color resolves against the palette only where it names a slot.
- tests.test_screenshots.test_palette_color_rejects_a_value_naming_no_color(color)[source]¶
A value that is not a color cannot silently paint something.
A
boolis anint, and names no palette slot for all that.
- tests.test_screenshots.test_blend(ratio, expected)[source]¶
Dim text is mixed toward the background rather than made transparent.
- tests.test_screenshots.test_grid_wraps_at_the_terminal_edge()[source]¶
A line reaching past the last column continues on the next row.
Cropping it would drop text the command printed, and a capture is evidence of what ran.
- tests.test_screenshots.test_grid_marks_each_fold_with_its_wrap_marker()[source]¶
A marked fold closes its row on the marker, painted as a mark of the layout.
The marker takes the last cell of the row it closes, so each folded row holds one cell less of text, and the last row of the line carries none: nothing continues past it.
- tests.test_screenshots.test_grid_without_a_wrap_marker_folds_a_full_row_silently()[source]¶
No marker means the terminal’s own fold: a line exactly as wide fits.
- tests.test_screenshots.test_grid_never_splits_a_wide_glyph()[source]¶
A wide glyph straddling the last column moves down whole.
- tests.test_screenshots.test_grid_keeps_a_glyph_wider_than_the_grid()[source]¶
A glyph too wide for the whole grid is drawn rather than wrapped forever.
- tests.test_screenshots.test_window_buttons(name, expected, unwanted)[source]¶
Each preset draws the decorations its desktop does, or none at all.
- tests.test_screenshots.test_render_draws_the_preset_it_is_given()[source]¶
A preset reaches the capture: its palette, its font, its decorations.
- tests.test_screenshots.test_preset_paints_its_own_titlebar(name)[source]¶
A window drawn as a desktop’s carries that desktop’s chrome on top.
- tests.test_screenshots.test_titlebar_collapses_on_a_window_wearing_nothing()[source]¶
An empty strip is dropped, and comes back as soon as it holds something.
- tests.test_screenshots.test_render_thins_the_window_out(format, expected)[source]¶
Opacity below one lets whatever the capture sits on through its body.
- tests.test_screenshots.DRAWN_FORMATS = (CaptureFormat.HTML, CaptureFormat.SVG)¶
The formats that draw a document around the captured text.
ANSIis the one that does not: it hands a terminal the stream it was already carrying, so there is no margin to credit in and nothing a window option could describe. Everything below asking about a window therefore asks it of these two.
- tests.test_screenshots.test_render_credits_what_drew_it(format)[source]¶
Every capture carries the mark, in the space around the terminal.
- tests.test_screenshots.test_watermark_links_the_package_name(format)[source]¶
The credit points a reader holding only the image back at the docs.
- tests.test_screenshots.test_watermark_leaves_a_borrowed_credit_unlinked(format)[source]¶
A project crediting itself has no click-extra to point anywhere.
- tests.test_screenshots.test_watermark_is_not_terminal_text()[source]¶
The mark is chrome, so reading a capture back does not collect it.
- tests.test_screenshots.test_watermark_sits_where_the_margin_is()[source]¶
It is drawn against the image’s own corner, wherever the margin puts it.
- tests.test_screenshots.test_render_draws_neither_when_asked_for_neither()[source]¶
noneis the value that leaves the window bare.
- tests.test_screenshots.test_render_geometry(margin, padding)[source]¶
Margin grows the image alone; padding grows the window inside it too.
Neither moves a glyph relative to the others: the capture is repositioned as a whole, so its text still reads back line by line.
- tests.test_screenshots.test_render_html_escapes_the_captured_text()[source]¶
Markup a CLI prints is escaped, not handed to the browser as markup.
The load-bearing test of the HTML renderer:
ansi_to_html()translates ANSI and copies everything else verbatim, so a caller skipping the escape turns click-extra’s own--export-confighelp, which says it writesto <stdout>, into an unclosed tag.
- tests.test_screenshots.test_render_html_round_trips_the_terminal_text()[source]¶
Stripping the markup back off returns exactly what the CLI printed.
The HTML counterpart of
svg_to_lines(): it catches a dropped character, a mangled escape and a stray tag in one assertion.
- tests.test_screenshots.test_render_html_fragment_is_self_contained()[source]¶
A fragment carries its own styling, and no document scaffolding.
- tests.test_screenshots.test_render_html_document_wraps_the_fragment()[source]¶
A full document is the fragment plus scaffolding, nothing else.
- tests.test_screenshots.test_render_html_quotes_survive_the_style_attribute()[source]¶
The font stack cannot terminate the
styleattribute it sits in.A double-quoted family name would close the attribute early and spill CSS into the markup, so the stack is single-quoted.
- tests.test_screenshots.css_rects(declarations)[source]¶
The rectangles a
block_css()box paints.Read back off its background layers, in eighths of a cell, the unit of
BLOCK_ELEMENTS.
- tests.test_screenshots.test_block_css_paints_the_shape_of_its_character(char)[source]¶
The CSS of a block element paints the rectangles its shape names.
- tests.test_screenshots.test_render_html_draws_each_box_run_as_one_svg()[source]¶
A run of Box Drawing is one box holding the run’s lines as an SVG.
One SVG per run rather than a background per cell, so a rule merges into one line, and the characters stay in the text.
- tests.test_screenshots.test_braille_css_draws_one_disc_per_dot(code)[source]¶
The CSS of a Braille pattern paints a disc for each dot it raises.
- tests.test_screenshots.test_render_html_draws_shapes()[source]¶
Every shape sits in a box drawing it, whatever its run’s style.
Its glyphs stay in the text, so the capture still reads back as the terminal text, and the run keeps the colors it came with.
- tests.test_screenshots.test_render_html_keeps_a_joined_shape_in_its_cluster()[source]¶
A shape after a zero width joiner stays in the text, as in an SVG capture.
- tests.test_screenshots.test_fit_columns(text, expected)[source]¶
The width
autoresolves to is the longest line, floored by the caller.
- tests.test_screenshots.test_render_auto_columns_folds_nothing()[source]¶
autolays the image out at what the text asks for.A line the command does not wrap on its own (a prompt, a wide table) is folded by a pinned width and kept whole by this one.
- tests.test_screenshots.test_screenshot_rejects_an_unusable_width(invoke, tmp_path, value, message)[source]¶
--columnstakes a width or the one word standing for none of them.
- tests.test_screenshots.test_parse_columns(value, expected)[source]¶
A width is an integer, or the one word leaving it to the text.
- tests.test_screenshots.test_parse_columns_rejects(value, message)[source]¶
A width that is neither says what it would take.
- tests.test_screenshots.test_screenshot_rejects_an_unusable_hold(invoke, tmp_path, value, message)[source]¶
A bad
--holdis reported against the option, like a bad--columns.The prefix is what tells a reader which of a dozen options to fix.
- tests.test_screenshots.test_capture_environment(columns, pinned)[source]¶
The command is told the chrome, denied the terminal, and given its width.
- tests.test_screenshots.test_screenshot_pacing_needs_record(invoke, tmp_path, arguments, message)[source]¶
The pacing knobs describe a recording, so a still refuses them.
- tests.test_screenshots.test_screenshot_record_rejects_still_only_arrangements(invoke, tmp_path, arguments, output_name, message)[source]¶
A recording is an animated SVG of whole screens at a pinned width.
- tests.test_screenshots.test_screenshot_record_writes_an_animation(invoke, tmp_path)[source]¶
--recordlands an animated SVG named after its output stem.
- tests.test_screenshots.test_screenshot_reports_a_command_that_cannot_start(invoke, monkeypatch, tmp_path, mode, program)[source]¶
A command the system refuses to start is an error line, not a traceback.
Only the start of the line is pinned: the reason closing it is the system’s own, and it differs between platforms.
- tests.test_screenshots.test_screenshot_reports_a_command_outliving_its_timeout(invoke, tmp_path)[source]¶
A command still running when
--timeoutends is an error line.The command is killed, and nothing is written.
- tests.test_screenshots.test_screenshot_wrap_needs_the_console_script(invoke, monkeypatch, tmp_path)[source]¶
--wraprefuses to re-enter through anything but the installed command.python -m click_extraresolves a target differently, so falling back to it would capture a different CLI than the composition it stands for. Better to say so than to quietly picture the wrong thing.
- tests.test_screenshots.test_screenshot_wrap_matches_the_composition(invoke, tmp_path)[source]¶
--wrapis the nested composition, spelled as a flag.Captured both ways, the terminal text has to match: the flag is a shortcut, not a second way of rendering.
- tests.test_screenshots.test_ansi_capture_is_the_text_it_was_given()[source]¶
The one format that renders nothing: a terminal reads the stream itself.
- tests.test_screenshots.test_ansi_capture_draws_no_window()[source]¶
None of the window options reach a format that has no window.
- tests.test_screenshots.test_ansi_capture_bands_an_emphasized_row()[source]¶
A terminal has no behind, so the band is the row’s own background.
- tests.test_screenshots.test_ansi_band_survives_a_reset_inside_the_row()[source]¶
Restated after every escape, or the band stops at the first keyword.
Pygments closes a colored run with a full reset, which clears the background along with the ink it was closing.
- tests.test_screenshots.test_ansi_capture_does_not_animate()[source]¶
There is no frame to hide in a stream a terminal paints as it arrives.
- tests.test_screenshots.test_render_dresses_an_html_capture_in_the_preset_font_alone()[source]¶
A preset’s font reaches an HTML capture, and its title bar stays the SVG’s.
The buttons and the strip they sit in are drawn by the SVG alone: an HTML capture reflows with the page embedding it, and has no window to seat them.
tests.test_snippet module¶
Tests for click_extra.capture.snippet.
A snippet rides the screenshot renderer, so this module tests the step that is
new: the colors a Pygments style hands over, and the shape of the text it hands
them on. What the renderer then does with that text is covered by
test_screenshots.py, and is deliberately not re-tested here.
- tests.test_snippet.SAMPLE = 'def ripen(fruit, days=3):\n """Wait for the mango."""\n return fruit\n'¶
A few lines of Python exercising keywords, strings and punctuation.
- tests.test_snippet.WINDOW_RE = re.compile('<rect fill="(?P<paint>#[0-9a-f]{6})" stroke=')¶
The window’s own rectangle, whose fill is the terminal’s background.
- tests.test_snippet.window_paint(svg)[source]¶
The background color a rendered snippet’s window is filled with.
- Return type:
- tests.test_snippet.test_snippet_wears_the_style_background(background)[source]¶
The window is painted the background the syntax style was designed for.
A style states the surface its colors were chosen against, and drawn on any other one it is a picture of that style in a window it was never meant for.
- tests.test_snippet.test_snippet_style_beats_the_preset_chrome()[source]¶
A preset dresses the window; the style still paints its body.
The two answer different questions, so a snippet wearing a terminal’s decorations keeps the colors its code was highlighted with.
- tests.test_snippet.test_every_style_paints_a_readable_window(style)[source]¶
Every style Pygments ships renders, and states a background and an ink.
A conformance sweep rather than a spot check: the palette is built from two attributes a style is free to leave unset, and the fallback for each has to hold across the whole catalog.
- tests.test_snippet.test_style_without_text_color_keeps_the_chrome_ink()[source]¶
A style naming no color for plain text falls back to the chrome’s own.
Twenty-five of the styles Pygments ships are in this case, all of them light ones whose code is meant to be black.
- tests.test_snippet.test_snippet_carries_the_style_colors_into_the_picture()[source]¶
The colors a style names reach the rendered document.
The whole premise of the module: Pygments writes true-color ANSI, which is the interchange format the renderer already reads.
- tests.test_snippet.test_highlight_expands_tabs()[source]¶
A tab becomes spaces before the snippet is laid out.
It has no width on a character grid, so one reaching the renderer takes the whole line’s measurement down with it.
- tests.test_snippet.test_snippet_draws_the_lines_it_was_given()[source]¶
The code comes back out of the picture, character for character.
- tests.test_snippet.test_snippet_numbers_its_lines()[source]¶
A numbered snippet counts from one, in a gutter of its own.
- tests.test_snippet.test_snippet_leaves_room_for_line_numbers_on_the_rule()[source]¶
A numbered snippet cut short rules the cut inside its text column.
The gutter takes cells in front of every row, the rule’s included, so a rule as wide as
columnswould fold on its last dots and put them alone on a row of their own.
- tests.test_snippet.test_snippet_renders_the_same_bytes_twice()[source]¶
Nothing in a snippet is timed, sampled or ordered by chance.
Which is what lets a documentation build rewrite a committed asset and leave the working tree clean.
- tests.test_snippet.test_resolve_lexer(language, filename, expected)[source]¶
A stated language wins over a file name, which wins over a guess.
- tests.test_snippet.test_unknown_language_is_an_error()[source]¶
A misspelled language fails loudly rather than drawing plain text.
A typo swallowed here would surface as an uncolored picture nobody re-reads.
- tests.test_snippet.test_unknown_style_is_an_error()[source]¶
A misspelled style names the ones that exist.
- tests.test_snippet.test_style_defaults_to_the_chrome(background)[source]¶
Naming no style takes the one the chrome is drawn for.
- tests.test_snippet.test_default_style_sits_close_to_the_capture_chrome(background)[source]¶
The default style’s background is a near match for a capture’s own.
The reason these two were picked: a snippet and a terminal capture sharing a page must not show a step between their windows.
- tests.test_snippet.test_snippet_renders_html()[source]¶
The HTML format carries the style’s colors too.
Every option name
capture_options()attaches.Read off a bare function the decorator is applied to, rather than restated here: a list written out by hand is one more place for the two commands to drift apart, which is the thing this is here to catch.
Both capture commands spell every shared option the same way.
The drift this guards against is silent and lands on the reader rather than the maintainer: an option learned on one command that means something else, or nothing at all, on the other.
--columnsis the deliberate exception. Its whole purpose is to differ: a command wraps its own output to it, where a file was never wrapped.
- tests.test_snippet.test_columns_defaults_differ_by_command()[source]¶
A snippet sizes itself to its longest line; a capture pins a width.
- tests.test_snippet.test_snippet_command_writes_a_capture(invoke, tmp_path)[source]¶
The command colors a file and writes the picture where it was told.
- tests.test_snippet.test_snippet_command_reads_stdin(invoke, tmp_path)[source]¶
A dash reads the source from stdin, where no file name identifies it.
- tests.test_snippet.test_snippet_command_rejects_a_fragment_image(invoke, tmp_path)[source]¶
--fragmentis an HTML notion: an SVG has no page to be pasted into.
- tests.test_snippet.test_snippet_command_reports_an_unknown_language(invoke, tmp_path)[source]¶
A misspelled language is a clean CLI error, not a traceback.
- tests.test_snippet.LATIN1_SOURCE = b"p\xe2te = 'bris\xe9e'\n"¶
A line of Python saved in Latin-1, which UTF-8 cannot decode.
- tests.test_snippet.test_snippet_command_refuses_a_source_that_is_not_utf8(invoke, tmp_path, from_stdin)[source]¶
A source in another encoding is a usage error on SOURCE, not a traceback.
- tests.test_snippet.test_snippet_command_leaves_the_output_alone_on_undecodable_stdin(tmp_path)[source]¶
Undecodable stdin is refused before the output file is opened.
In UTF-8 mode,
sys.stdinreads undecodable bytes as surrogates, and those only fail once the capture is written, over the previous capture. The test runner’s stdin decodes strictly and never takes that path, so the command runs as its own process, its stdin set tosurrogateescapeon every Python version.
- tests.test_snippet.test_snippet_prints_ansi_to_stdout(invoke, tmp_path)[source]¶
A dash target prints the escape sequences a terminal paints.
The one target that needs no rendering: what a terminal reads is the stream the capture was carried in all along.
- tests.test_snippet.test_snippet_stdout_strips_color_when_piped(invoke, tmp_path)[source]¶
Piped, the escapes go and the code stays.
The reason this routes through
echorather than a bare write: a redirect that captured raw escape sequences would produce a file nobody can read.
- tests.test_snippet.test_snippet_stdout_closes_on_one_newline(invoke, tmp_path)[source]¶
Exactly one, so the next prompt does not land on the last row.
- tests.test_snippet.test_snippet_writes_an_ansi_file(invoke, tmp_path)[source]¶
The
.ansiextension names the same format, written out.
- tests.test_snippet.EPILOG_EXAMPLES = (['--output', 'basket.svg', 'basket.py'], ['--output', 'basket.html', '--syntax-style', 'dracula', 'basket.py'], ['--output', 'basket.svg', '--line-numbers', '--emphasize-lines', '12', 'basket.py'])¶
The argument lists the
snippethelp screen closes on, one per example.
- tests.test_snippet.test_snippet_epilog_examples_do_what_they_say(invoke, tmp_path, arguments)[source]¶
Every example of the help screen runs, and a style it names paints the window.
The
--themeof every Click Extra command is the help screen’s own theme, so an example spelling the syntax style that way ran without complaint and drew the default style instead of the one it named.
tests.test_sphinx_crossrefs module¶
Render tests for Sphinx cross-references in the built documentation.
Build the docs once and assert against the real HTML that in-page and
cross-page anchors resolve to the sections they name, and that intersphinx
references to Click resolve to the upstream site. This catches drift the
moment a heading rename shifts a make_id slug, a page link goes stale, or
the intersphinx_mapping URL breaks, none of which a mock-based test would
notice.
The same build shows what the click_extra.sphinx extension changes in its
output: each changelog release anchored on its version, no viewcode page that
nothing links to, and no pickled page holding a node of another document.
The docs are built in a subprocess through uv run --group docs, and so are
the pickled pages read back, so this module needs no Sphinx dependency of its
own and runs in the plain test environment.
- tests.test_sphinx_crossrefs.UV_RUN_DOCS = ('uv', 'run', '--group', 'docs')¶
Prefix of a command run in the docs environment.
- tests.test_sphinx_crossrefs.CHANGELOG_RELEASE = re.compile('^## \\[`(?P<version>\\d+\\.\\d+\\.\\d+)(?:\\.\\w+)?` \\(', re.MULTILINE)¶
Heading of a release in
changelog.md, the.devNsuffix left out.
- tests.test_sphinx_crossrefs.FOREIGN_NODES_SCRIPT = 'import json\nimport pickle\nimport sys\nfrom pathlib import Path\n\ndoctrees = Path(sys.argv[1])\nforeign = {}\nfor path in sorted(doctrees.rglob("*.doctree")):\n doctree = pickle.loads(path.read_bytes())\n count = sum(1 for node in doctree.findall() if node.document is not doctree)\n if count:\n foreign[path.relative_to(doctrees).as_posix()] = count\nprint(json.dumps(foreign))\n'¶
Count, for each pickled page, the nodes that belong to another document.
Runs in the docs environment, which holds every class the pages pickle.
- tests.test_sphinx_crossrefs.built_docs(tmp_path_factory, captures_before_build)[source]¶
Build the HTML documentation once and return its output directory.
Builds into a throwaway directory (rather than
docs/_build) so the run is hermetic and never clobbers a developer’s local build. Reads in parallel, asdocs.yamldoes: a serial read hides most of the viewcode pages a parallel one publishes.Requests
captures_before_build()so the snapshot is taken before the build whichever test triggers it: every test of this module shares the one build, andpytest-randomlydecides which of them runs first.- Return type:
- tests.test_sphinx_crossrefs.captures_before_build()[source]¶
Snapshot every committed capture before a build gets a chance to rewrite one.
built_docs()requests it, so the bytes are read while the tree is still as committed, whichever test of the module builds first.
- tests.test_sphinx_crossrefs.test_committed_captures_survive_a_build(captures_before_build, built_docs)[source]¶
A build leaves every committed capture byte-identical.
A
click:runblock carrying:screenshot:rewrites its image from the CLI’s live output on every build. So an image that no longer matches the code surfaces here, instead of sitting stale in a readme nobody re-checked.The bytes compare raw: the
@generatedstamp and the default watermark name no release, so a version bump moves neither. Regenerating is still the fix for a real difference: build and commit it.
- tests.test_sphinx_crossrefs.read_html(built_docs, filename)[source]¶
Read a built HTML page.
- Return type:
- tests.test_sphinx_crossrefs.test_install_page_internal_anchor(built_docs)[source]¶
The install page’s in-page binaries link resolves to its section.
- tests.test_sphinx_crossrefs.test_changelog_releases_are_anchored_on_their_version(built_docs)[source]¶
Each release of the changelog has an anchor made from its version.
docutils reduces a release heading to nothing and numbers its section from a per-page counter, so
#id40names another release after each new one.click_extra_release_anchorsindocs/conf.pygives each section an anchor like#v1-4-2instead. Seeclick_extra.sphinx.changelog.
- tests.test_sphinx_crossrefs.test_viewcode_pages_link_back_to_their_objects(built_docs)[source]¶
Each published module page links back to an object it documents.
sphinx.ext.viewcodekeeps a module it read even when no documented object links to it, and a parallel read publishes a page for each such module, reachable from the_modulesindex alone. Seeclick_extra.sphinx.viewcode.
- tests.test_sphinx_crossrefs.test_pickled_pages_hold_no_node_of_another_document(built_docs)[source]¶
Every node of each pickled page belongs to that page.
A node that keeps a throwaway document pickles that document’s settings with the page, build environment included. Counting such nodes catches a leak of any size, where a ceiling on file sizes lets through a leak that only some of the pages carry. See
click_extra.sphinx.doctrees.
- tests.test_sphinx_crossrefs.test_cli_reference_anchors(built_docs, anchor)[source]¶
The CLI page’s tree and config summary tables deep-link their sections.
One anchor per directive:
click-extra-wrapcomes from{click:tree},test-suite-filefrom{click:config}. Their presence is also the canary for the directives themselves staying on the page.
- tests.test_sphinx_crossrefs.test_cross_page_links_resolve(built_docs, source_page, href, target_page, anchor)[source]¶
Cross-page links point at anchors that exist on the target page.
Every case names a page, so splitting one moves the anchor and leaves the case behind. Repoint them in the same commit that splits the page: this module is skipped off Linux, so the break surfaces only in CI, and a local
pytest -m oncereports a clean pass while skipping all of it.
tests.test_spinner module¶
- tests.test_spinner.test_noop_on_non_tty_stream()[source]¶
A non-interactive stream produces no output and spawns no thread.
- tests.test_spinner.test_delay_suppresses_quick_calls()[source]¶
A call shorter than the delay never draws anything.
- tests.test_spinner.test_shown_false_when_not_drawn()[source]¶
shownstays False whenever no frame reaches the terminal.
- tests.test_spinner.test_shown_true_after_drawing()[source]¶
shownflips to True once a frame is drawn, and stays True after stop.
- tests.test_spinner.test_suspend_and_resume()[source]¶
A spinner restarts cleanly after a stop, without re-using a dead thread.
- tests.test_spinner.test_rotation_direction(reverse)[source]¶
Frames cycle forwards by default and backwards when
reverse=True.
- tests.test_spinner.test_beep_silent_when_disabled()[source]¶
A disabled spinner never beeps, even with
beep=True.
- tests.test_spinner.test_echo_degrades_to_plain_write_when_disabled()[source]¶
Off a TTY the message is still emitted, just without control codes.
- tests.test_spinner.test_progress_option_is_a_default_option()[source]¶
ProgressOption ships in the default option set of every extra command.
- tests.test_spinner.test_progress_option_resolution(invoke, args, expected)[source]¶
ctx.meta[PROGRESS]follows –progress and –accessible, never color.
- tests.test_spinner.test_standalone_progress_option(invoke, cmd_decorator, option_decorator)[source]¶
- tests.test_spinner.test_auto_spinner_follows_progress_flag(invoke, args, draws)[source]¶
A
live="auto"spinner stays still under--no-progressand--accessible, even on a terminal, with no wiring in the command.
- tests.test_spinner.test_spinner_stop_leaves_a_stalled_drawing_thread_behind(monkeypatch)[source]¶
stop()gives up on a drawing thread stuck in a write after the grace, instead of hanging the Ctrl+C that asked for the stop.
- tests.test_spinner.SPINNING_SCRIPT = '\nimport sys\nimport time\n\nfrom click_extra import Spinner\n\nSpinner("Brewing tea", stream=sys.stdout, live="always", interval=0.02).start()\ntime.sleep(30)\n'¶
A process spinning until something kills it.
- tests.test_spinner.test_sigterm_shows_the_cursor_again(tmp_path)[source]¶
A spinner killed by
SIGTERMleaves its terminal with the cursor shown, and the process still dies bySIGTERM.
- tests.test_spinner.test_dumb_terminal_disables_spinner(monkeypatch, term)[source]¶
A cursor-less terminal self-disables the spinner even on a TTY.
- tests.test_spinner.test_live_always_overrides_dumb_terminal(monkeypatch)[source]¶
An explicit
live="always"wins over theTERM=dumbauto-detection.
- tests.test_spinner.test_decorator_runs_function_inside_spinner()[source]¶
@spinneranimates while the wrapped function runs and returns its result.
- tests.test_spinner.test_bare_decorator_without_parentheses()[source]¶
@Spinnerwith no parentheses wraps the function with default settings.
- tests.test_spinner.test_resolve_color_enabled(monkeypatch, env, stream_factory, expected)[source]¶
Color follows FORCE_COLOR / dumb TERM / NO_COLOR then TTY, with no context.
- tests.test_spinner.test_color_stripped_but_spinner_still_spins_when_disabled(monkeypatch)[source]¶
NO_COLOR strips the spinner’s color but never stops it spinning.
- tests.test_spinner.test_style_alone_keeps_painting_the_line_as_one_run()[source]¶
Without a part style,
stylestill wraps the whole line, byte for byte.
- tests.test_spinner.test_part_styles_paint_their_own_part(options, glyph, label, timer)[source]¶
Each part carries its leading space and takes its own style over
style.The timer’s parentheses belong to its part, which is what a
timercallable formatting only the duration cannot reach.
- tests.test_spinner.test_color_off_strips_escapes_embedded_in_the_label(options)[source]¶
A label styled by hand renders plain when color is off, like any other.
- tests.test_spinner.test_kept_line_paints_label_and_timer_with_part_styles(monkeypatch)[source]¶
ok()paints its label and timer with the part styles, when color is on.
- tests.test_spinner.test_kept_line_strips_embedded_escapes_with_color_off()[source]¶
Off a TTY,
ok()writes the label plain, embedded escapes included.
- tests.test_spinner.test_frame_lines_are_what_the_animation_draws(monkeypatch)[source]¶
A picture of a spinner shows the lines the spinner really writes.
Spins for real, recovers every frame written to the stream, and asserts each one is a line
frame_lines()offers. Composing the picture separately from the animation is what lets the two drift the first time the glyph, the label or the timer change places; comparing them is what stops that.
- tests.test_spinner.test_frame_lines_honors_reverse()[source]¶
A spinner cycling backwards is pictured spinning the way it animates.
- tests.test_spinner.test_frame_lines_colors_on_request(color)[source]¶
A capture asks for color whatever stream the spinner would have drawn on.
A spinner that never started resolved no color for itself, so the picture has to state its own answer rather than inherit that one.
- tests.test_spinner.test_ok_degrades_to_plain_line_when_disabled(monkeypatch)[source]¶
Off a TTY the outcome is still recorded, without symbol color.
- tests.test_spinner.test_timer_accepts_custom_formatter()[source]¶
A callable
timerformats the elapsed seconds itself (yaspin #236).
- tests.test_spinner.test_enable_windows_ansi_is_a_safe_noop()[source]¶
The Windows VT-enable never raises: off Windows, or on a stream with no usable console handle (the path the spinner exercises on every platform).
- tests.test_spinner.test_catalog_is_complete()[source]¶
The cli-spinners / ora catalog is present and well-formed.
Every frame of a preset occupies the same number of terminal cells.
A ragged preset moves its label a cell in and out as the animation turns, and a capture laid out on the frames reserves a column for the widest one.
- tests.test_spinner.test_catalog_frames_carry_no_upstream_padding(name)[source]¶
No preset pads every one of its frames with a trailing space.
Upstream writes its emoji frames that way. Kept, the space lands next to the one the spinner writes before its label and reads as a double gap.
- tests.test_spinner.test_multichar_preset_renders()[source]¶
A multi-character animation (which upstream
\brenderers drop) draws.
- tests.test_spinner.test_demo_spinner_table_lists_selection(invoke)[source]¶
--tableprints the catalog table; the tour stays TTY-only.
- tests.test_spinner.test_demo_spinner_without_table_flag_shows_no_table(invoke)[source]¶
Off a TTY and without –table, the command renders no table.
- tests.test_spinner.test_demo_spinner_tour_column_shows_three_cycle_time(invoke)[source]¶
The Tour column reports 3 × frames × interval (dots = 2.4s).
- tests.test_spinner.test_tour_duration_bounds_dwell()[source]¶
The tour dwell aims for three cycles, clamped to [_TOUR_MIN, _TOUR_CAP], and never trims a huge spinner below one full cycle.
- tests.test_spinner.test_active_spinner_registry_lifecycle()[source]¶
A started spinner advertises itself, keyed by stream, until stopped.
- tests.test_spinner.test_active_spinner_ignores_disabled_spinner()[source]¶
A disabled spinner never animates, so it never registers either.
- tests.test_spinner.test_trail_glyph(args, kwargs, expected)[source]¶
The glyph takes a symbol and a style in place of the theme’s.
- tests.test_spinner.test_color_resolution_reaches_worker_threads(monkeypatch)[source]¶
A spinner started on a worker thread sees the invocation’s
--no-color.The thread-local command context is absent there, so the published invocation color is what carries the decision, and it outranks the environment it was reconciled from.
- tests.test_spinner.test_resolve_timer_follows_time_flag()[source]¶
timer=Nonefollows the –time flag; explicit settings pass through.
- tests.test_spinner.test_spinner_timer_follows_time_flag()[source]¶
A spinner’s default
timerfollows –time, as a trail’s does.
- tests.test_spinner.CUSTOM_CSS = PosixPath('/home/runner/work/click-extra/click-extra/docs/_static/custom.css')¶
Stylesheet carrying the documentation’s
unicode-rangedeclaration.
- tests.test_spinner.GRIDLESS_BLOCKS = ((9472, 9599), (9600, 9631), (9632, 9727), (10240, 10495))¶
Ranges no mainstream monospace font carries a glyph for.
A browser substitutes one that knows nothing of the character grid, so these come out narrow or wide and a captured table’s columns slide apart along the row.
docs/_static/custom.cssbinds a subset font to exactly these.
- tests.test_spinner.declared_unicode_ranges()[source]¶
Read the codepoint spans the documentation’s stylesheet claims.
- tests.test_spinner.test_catalog_gridless_glyphs_are_covered_by_the_docs_font(invoke)[source]¶
Every glyph a browser cannot size is one the documentation ships a font for.
The catalog table is laid out on a character grid, and a browser only reproduces that if every glyph advances by one cell.
custom.cssbinds a subset font to the ranges no ordinary monospace font carries; this checks the two have not drifted apart, which they would the moment the declaration is narrowed or a spinner starts drawing from a range it omits.Note
Narrower than the problem it guards: it checks the ranges already known to need a font, not that a newly used range has been noticed. A spinner drawn from some other gridless block would pass here and still misalign.
- tests.test_spinner.SPINNER_ASSETS = PosixPath('/home/runner/work/click-extra/click-extra/docs/assets')¶
Directory the inventory gallery writes one animation per preset into.
- tests.test_spinner.test_every_spinner_has_a_committed_animation()[source]¶
The inventory gallery covers the catalog exactly, one asset per preset.
The gallery is generated from
SPINNERSat build time, so a new preset grows an asset on the next build and this only fails on a committed tree that has fallen behind. The stray half matters more: a renamed or dropped preset leaves its old animation behind, where nothing references it and nothing would notice.
tests.test_styling module¶
Tests for click_extra.styling.Style extras.
- tests.test_styling.test_named_color_string_passes_through()[source]¶
Plain named-color strings must not be touched by the hex shorthand.
- tests.test_styling.test_ror_with_cloup_left_operand()[source]¶
cloup_style | my_styleis reached via__ror__.
- tests.test_styling.test_or_with_non_style_returns_notimplemented()[source]¶
Style | int falls through to
int.__ror__and raisesTypeError.
- tests.test_styling.test_to_dict_omits_none_only()[source]¶
Falseboolean attributes are kept; onlyNoneis filtered out.
- tests.test_styling.test_from_dict_accepts_hex_or_rgb()[source]¶
Both hex strings and RGB tuples work as input.
- tests.test_styling.test_str_no_styling_has_no_color_codes()[source]¶
Style with no fields set produces only click’s bare reset suffix.
- tests.test_styling.test_repr_palette_index_zero()[source]¶
Index
0is falsy but set: it must survive theis not Noneguards.
- tests.test_styling.test_to_css_palette_index_zero()[source]¶
Palette index
0is falsy but set, and resolves to ANSI black.
- tests.test_styling.test_to_css_bright_named_color_resolves_to_rgb()[source]¶
Bright ANSI colors aren’t valid CSS keywords: convert to RGB.
- tests.test_styling.test_from_ansi_full_styled_string_round_trip()[source]¶
Parsing a style’s complete output (trailing reset included) recovers it.
- tests.test_styling.test_from_ansi_round_trip_through_call()[source]¶
A Style can be parsed back from its own ANSI output.
- tests.test_styling.test_contrast_ratio_meets_wcag_aa_for_dracula_default()[source]¶
Dracula’s default fg/bg pair clears WCAG AA (4.5).
- tests.test_styling.test_eq_survives_a_call()[source]¶
Calling a style leaves its equality and hash untouched.
- tests.test_styling.test_eq_with_cloup_style(style, twin)[source]¶
A click-extra Style equals the cloup.Style carrying its fields, both ways.
- tests.test_styling.test_eq_with_non_style()[source]¶
Comparing with anything else defers to the other operand.
- tests.test_styling.test_style_call_keeps_24bit_on_truecolor(monkeypatch)[source]¶
An RGB color emits a 24-bit sequence when the terminal supports truecolor.
- tests.test_styling.test_style_call_quantizes_without_truecolor(monkeypatch)[source]¶
An RGB color downsamples to the nearest 256-index without truecolor.
- tests.test_styling.test_style_call_leaves_named_and_indexed_colors(monkeypatch, colorterm)[source]¶
Named and palette-index colors never quantize, regardless of depth.
- tests.test_styling.test_style_call_survives_depth_flip(monkeypatch)[source]¶
The same style renders correctly when truecolor flips between calls.
Quantizing works on a transient copy, so the shared (often singleton) theme style keeps its 24-bit colors for the next call.
- tests.test_styling.test_style_call_palette_index_zero(style, expected)[source]¶
Rendering palette index
0must emit its escape code, not drop it.
- tests.test_styling.test_style_call_empty_string_color_rejected(style)[source]¶
Click validates colors at render time and rejects empty strings.
Click 8.5 raises
TypeError, its development branchValueError.
- tests.test_styling.test_int_enum_color_round_trips_by_value()[source]¶
A palette index held by an
IntEnumserializes as the index it is.
- tests.test_styling.test_split_ansi_preserves_text()[source]¶
Concatenated run texts equal the ANSI-stripped input.
- tests.test_styling.test_render_ansi_splits_runs_at_newlines()[source]¶
No markup wrapper ever crosses a line boundary.
- tests.test_styling.test_wrap_ansi_matches_textwrap_on_plain_text()[source]¶
Unstyled input defers entirely to
textwrap.
- tests.test_styling.test_wrap_ansi_breaks_on_visible_width()[source]¶
A styled string breaks exactly where its plain counterpart does.
textwrapalone counts the escape bytes toward the line length, which would break a styled string several words early.
- tests.test_styling.test_wrap_ansi_closes_styling_on_every_line()[source]¶
No escape sequence crosses a line boundary.
tests.test_table module¶
- tests.test_table.test_table_formats_definition()[source]¶
Check all table formats are accounted for and properly named.
- tests.test_table.test_all_table_formats_have_test_rendering()[source]¶
Check all table formats have a rendering test fixture defined.
- tests.test_table.test_all_table_rendering(invoke, cmd_decorator, option_decorator, format_name, expected)[source]¶
- tests.test_table.test_emoji_presentation_width_follows_the_terminal(monkeypatch, term_program, expected)[source]¶
An emoji-presentation sequence measures as its terminal advances it.
- tests.test_table.test_emoji_presentation_gains_the_column_it_paints_into(monkeypatch)[source]¶
A terminal painting the glyph wider than it advances gets a column for it.
- tests.test_table.test_emoji_presentation_padding_stays_out_of_markup(monkeypatch)[source]¶
A markup rendering outlives this terminal, so its cells keep their text.
- tests.test_table.test_markup_pads_emoji_presentation_by_unicode_width(monkeypatch)[source]¶
A markup table pads an emoji-presentation sequence by the Unicode tables.
Whatever terminal renders it: a Markdown table is read by a formatter and a browser that never see that terminal, and
mdformatpads it withwcswidth.
- tests.test_table.test_print_table_pads_markup_by_unicode_width(monkeypatch, capsys)[source]¶
print_tablemeasures a markup table likerender_tabledoes.A narrow-emoji terminal must not leak into a Markdown table that a subcommand prints with
ctx.print_table.
- tests.test_table.test_table_rows_line_up_on_every_terminal_wcwidth_knows(monkeypatch, term_program)[source]¶
Rows advance the same width on each terminal, not just the default one.
Measured with
wcwidth’s own per-terminal tables rather than with the measure the layout used, so a terminal whose correction Click Extra misses shows up as a ragged table instead of agreeing with itself.
- tests.test_table.test_table_rows_are_uniform_under_a_narrow_emoji_terminal(monkeypatch)[source]¶
Every row of a rendered table advances the same width in that terminal.
Measured with the terminal’s own rule, which is the one deciding whether the table’s vertical rules line up on screen.
- tests.test_table.test_markup_strips_ansi_by_default(invoke, format_id)[source]¶
Markup formats without native styling strip ANSI codes by default.
- tests.test_table.test_markup_preserves_ansi_with_color_flag(invoke, format_id)[source]¶
--coloroverrides ANSI stripping for non-styled markup formats.
- tests.test_table.test_color_flag_from_parent_group_preserves_ansi(invoke)[source]¶
A
--colorforced on a parent group reaches the subcommand’s table.Mirrors the layout of the
click-extrademo CLI: the color and table options live on the group, while the table is printed from a subcommand whose context does not own those parameters.
- tests.test_table.STYLED_MARKUP_SAMPLES = {TableFormat.HTML: '<span style="color: red">hello</span>', TableFormat.JIRA: '{color:red}hello{color}', TableFormat.LATEX: '\\textcolor{red}{hello}', TableFormat.LATEX_BOOKTABS: '\\textcolor{red}{hello}', TableFormat.LATEX_LONGTABLE: '\\textcolor{red}{hello}', TableFormat.LATEX_RAW: '\\textcolor{red}{hello}', TableFormat.MEDIAWIKI: '<span style="color: red">hello</span>', TableFormat.TEXTILE: '%{color: red}hello%', TableFormat.UNSAFEHTML: '<span style="color: red">hello</span>'}¶
Expected native styling of a red
hellocell, per styled format.
- tests.test_table.test_styled_formats_translate_ansi(invoke, format_id, styled_cell, flags)[source]¶
Styled formats translate ANSI codes to native markup, by default and under a forced
--coloralike.
- tests.test_table.test_styled_formats_strip_ansi_with_no_color(invoke, format_id)[source]¶
Disabling colors renders styled formats plain, with no translation.
- tests.test_table.test_serialize_toml_list_wrapping()[source]¶
Top-level lists are wrapped under a
recordkey for TOML.
- tests.test_table.test_serialize_strips_none(table_format)[source]¶
TOML and XML have no null type.
Nonevalues are omitted.
- tests.test_table.test_serialize_xml_list_wrapping()[source]¶
Top-level lists are wrapped under a
recordkey for XML.
- tests.test_table.test_serialize_xml_respells_invalid_names()[source]¶
Keys XML does not accept as element names still give a well-formed document.
- tests.test_table.test_serialize_default_callback()[source]¶
Custom types are converted via the
defaultcallback.
- tests.test_table.test_missing_dependency_clean_error(monkeypatch, func, args, kwargs, message)[source]¶
Missing optional dependency produces a clean error, no traceback.
- tests.test_table.test_render_table_header_edge_cases(headers, data, expected)[source]¶
Edge cases for header handling in structured format rendering.
- tests.test_table.test_render_table_xml_element_names(headers, expected)[source]¶
An XML table parses back with one element per cell, whatever its headers.
An element name cannot start with a digit, so a cell position or a numeric header gets a leading underscore and keeps its digits. A
Nonecell is left out without shifting the position of the cells after it.
- tests.test_table.test_wrappable_formats_definition()[source]¶
Wrapping is claimed by text layouts only, never by a data interchange.
- tests.test_table.test_max_column_widths_never_breaks_a_format(format_id)[source]¶
Every format accepts a width: it either wraps on it or drops it.
Locks the invariant across the whole enum, so a format added later cannot silently raise or corrupt its output when handed a width.
- tests.test_table.test_max_column_widths_keeps_data_formats_intact(format_id)[source]¶
A width never leaks a line break into a serialized cell.
- tests.test_table.test_max_column_widths_scalar_applies_to_every_column()[source]¶
A single value stands in for a per-column list.
- tests.test_table.test_max_column_widths_shorter_than_the_table()[source]¶
A list shorter than the table leaves the trailing columns unlimited.
- tests.test_table.test_column_spec_max_width_is_the_default_source()[source]¶
A width declared on a ColumnSpec applies without any argument.
- tests.test_table.test_max_column_widths_overrides_column_spec()[source]¶
An explicit argument wins over the ColumnSpec declaration.
- tests.test_table.test_column_spec_max_width_follows_a_projection()[source]¶
A ColumnSpec width stays on its column when others are dropped.
- tests.test_table.test_auto_width_fits_the_terminal(monkeypatch)[source]¶
An
autocolumn absorbs the width left by the others.
Several
autocolumns split what is left evenly.
- tests.test_table.test_auto_width_floors_at_min_column_width(monkeypatch)[source]¶
A terminal too narrow to fit the table still renders a usable column.
- tests.test_table.test_auto_width_honors_context_width(invoke)[source]¶
autoreads the width a help screen would use, not the raw terminal.
- tests.test_table.test_vertical_wrapping_keeps_the_label_gutter()[source]¶
Continuation lines of a wrapped cell align under the first one.
- tests.test_table.test_vertical_labels_align_on_their_visible_width(headers)[source]¶
Labels are padded to the columns they occupy, not to their length.
Escape codes take no column and a double-width character takes two, so the separators and the continuation lines of a wrapped cell stay in one column.
- tests.test_table.test_column_sort_key(header_defs, rows, sort_columns, cell_key, expected_first_col)[source]¶
- tests.test_table.test_sort_by_option_choices_and_default(header_defs, expected_choices, expected_default)[source]¶
SortByOption choices and default are derived from column definitions.
- tests.test_table.test_sort_by_option_wires_context(invoke)[source]¶
SortByOption publishes the sort key that ctx.print_table applies.
- tests.test_table.test_print_table_from_subcommand_context(invoke)[source]¶
A group-level –table-format reaches ctx.print_table in subcommands.
ctx.metais shared along the context chain, so theprint_tablecontext method works from a subcommand without reaching for the root context.
- tests.test_table.test_print_table_without_table_option(invoke)[source]¶
ctx.print_table works with no –table-format in the chain.
Falls back to the default rendering format, like the module-level
print_table()invoked without an explicit format.
- tests.test_table.test_render_table_context_method_honors_sort_by(invoke)[source]¶
ctx.render_table applies the –sort-by selection, like ctx.print_table.
- tests.test_table.test_sort_by_option_multi_column(invoke)[source]¶
Multiple –sort-by options define sort priority.
- tests.test_table.test_sort_by_option_decorator(invoke)[source]¶
The sort_by_option decorator wires sorting like a direct SortByOption.
- tests.test_table.test_sort_by_option_decorator_in_option_group(invoke)[source]¶
The sort_by_option decorator composes with @option_group.
- tests.test_table.test_sort_by_option_columns_registry(invoke)[source]¶
A ColumnSpec registry passed via columns= drives choices and ordering.
- tests.test_table.test_sort_by_option_accepts_column_spec_varargs()[source]¶
ColumnSpec instances also work positionally, normalized to (label, id).
- tests.test_table.test_sort_by_option_rejects_positional_and_columns()[source]¶
Column definitions cannot be passed both positionally and via columns=.
- tests.test_table.test_columns_type_matches_ids_exactly()[source]¶
Column IDs are identifiers: a comma list, matched with their case.
The same ColumnSpec registry configures both –columns and –sort-by.
- tests.test_table.test_column_sort_key_field_mapping(header_defs, sort_columns, rows, expected_first_col)[source]¶
The public key builder maps requested fields onto the carried columns.
- tests.test_table.test_column_sort_key_none_when_not_carried(sort_columns)[source]¶
No requested field carried by the table: rows must keep their order.
- tests.test_table.test_render_table_rich_headers_render_labels()[source]¶
ColumnSpec and plain label header entries render their labels.
- tests.test_table.test_unsortable_column_keeps_its_place()[source]¶
sortable=Falsekeeps a column in the layout, out of the sort choices.
- tests.test_table.test_sort_by_option_field_vocabulary()[source]¶
Bare column IDs declare a table-less field vocabulary.
- tests.test_table.test_sort_by_option_labeled_defs_not_vocabulary()[source]¶
Labeled definitions keep the declaration-time baked-sort behavior.
- tests.test_table.test_sort_by_option_rejects_mixed_defs()[source]¶
Bare IDs and labeled definitions cannot be mixed.
tests.test_telemetry module¶
tests.test_test_suite module¶
Tests for the declarative CLI test-suite engine and its test-suite command.
Covers three surfaces:
parse_test_suite()and theCLITestCasenormalization it feeds.run_test_suite(), the parallel orchestrator.The
click-extra test-suitesubcommand wiring it to the CLI.
The host Python interpreter stands in for the command under test, so cases stay fast and platform-neutral.
- tests.test_test_suite.test_parse_returns_cases()[source]¶
A well-formed YAML suite yields one CLITestCase per entry.
- tests.test_test_suite.test_parse_returns_cases_per_format(suite_string, fmt)[source]¶
TOML (cases under [[cases]]) and JSON (bare array) yield the same cases.
- tests.test_test_suite.test_parse_toml_requires_cases_key()[source]¶
A TOML mapping without a top-level ‘cases’ array of tables is rejected.
- tests.test_test_suite.test_parse_rejects_non_suite_format()[source]¶
A format that cannot represent a list of cases is rejected.
- tests.test_test_suite.test_parse_rejects_malformed(suite, exception)[source]¶
Empty, mapping-without-cases, and unknown-directive suites raise.
- tests.test_test_suite.test_load_detects_format_from_extension(tmp_path, filename, content)[source]¶
The file extension selects the parser, so each format yields the case.
- tests.test_test_suite.test_load_rejects_unknown_extension(tmp_path)[source]¶
An extension matching no suite format is rejected.
- tests.test_test_suite.test_cases_from_data_builds_cases()[source]¶
A list of directive mappings becomes CLITestCase instances.
- tests.test_test_suite.test_cases_from_data_rejects_invalid_directive(directive)[source]¶
An unknown directive is rejected, and so is the trace the runner fills.
- tests.test_test_suite.test_case_execution_trace_stays_out_of_comparisons()[source]¶
A case that ran still compares equal to the same case, unrun.
- tests.test_test_suite.test_split_args_honors_quotes(cli, expected)[source]¶
Quoting survives tokenization identically on POSIX and Windows.
shlexandCommandLineToArgvWare two different parsers, so the cases above are the subset of syntax on which they must agree.Two corners stay out on purpose, because the parsers answer differently and each answer is right for its platform: a backslash escapes the next character for
shlexbut stands for itself on Windows (which is what keepsC:\Usersintact), and a doubled quote inside a quoted run closes and reopens it forshlexwhile Windows folds it into one literal quote.
- tests.test_test_suite.test_case_normalizes_scalars()[source]¶
String scalars are coerced: exit_code to int, cli_parameters to a tuple.
- tests.test_test_suite.test_case_normalizes_timeout(value, expected)[source]¶
A timeout given as int, float, or numeric string becomes a float.
Plain integers are what every config format produces for a bare number, so they must be accepted (a
timeout: 5in any suite).
- tests.test_test_suite.test_case_rejects_non_numeric_timeout(value)[source]¶
A boolean or non-scalar timeout is rejected as not-a-float.
- tests.test_test_suite.test_case_accepts_compiled_regexes()[source]¶
A regex directive takes a compiled pattern as well as a string.
The field hints admit both: a compiled pattern is kept as is, a string is compiled with dots matching newlines, and the two mix in one directive.
- tests.test_test_suite.test_case_survives_dataclass_replace()[source]¶
dataclasses.replaceruns the normalization again over normalized values.Every field family has to accept its own output for that: compiled regexes, resolved platform sets and split parameters all round-trip unchanged, and only the replaced field moves.
- tests.test_test_suite.test_output_contains_sees_merged_stream()[source]¶
output_contains matches substrings from both stdout and stderr.
- tests.test_test_suite.test_output_regex_preserves_cross_stream_order()[source]¶
output_regex_matches sees stdout and stderr interleaved in write order.
- tests.test_test_suite.test_output_directive_mismatch_fails()[source]¶
A substring absent from the merged stream fails the case.
- tests.test_test_suite.test_only_platforms_runs_on_member_platform()[source]¶
A case restricted to the current platform’s own id is not skipped.
- tests.test_test_suite.test_only_platforms_skip_names_required_platforms(monkeypatch)[source]¶
The skip message names the case’s required platforms, not the current one.
Naming the current one would read “only runs on platform: macOS” for a case skipped on macOS.
- tests.test_test_suite.test_non_utf8_output_does_not_crash_the_harness()[source]¶
Bytes that are not valid UTF-8 are escaped, not a reader-thread crash.
A binary emitting its platform’s legacy encoding (cp1252 on Windows) would otherwise kill the capture with a
UnicodeDecodeError, failing the case with no hint of the cause. The escaped bytes flow into the captured stream, where assertions can see them.
- tests.test_test_suite.test_child_inherits_utf8_io_encoding()[source]¶
The subprocess emits UTF-8 regardless of the platform’s default.
PYTHONIOENCODING is injected into the child environment so CPython-based binaries write UTF-8 on piped stdout, where Windows would pick cp1252 and desynchronize from the harness’s UTF-8 decoding.
- tests.test_test_suite.test_output_and_stream_directives_are_mutually_exclusive()[source]¶
Combining output_* with stdout_*/stderr_* is rejected at construction.
- tests.test_test_suite.ECHO_ENV = ('-c', "import os; print(os.environ.get('PROBE_VAR', '<absent>'))")¶
Command line printing one variable, or a marker when it is not set.
- tests.test_test_suite.test_env_sets_a_variable_on_the_child()[source]¶
A variable a case declares reaches the command it runs.
- tests.test_test_suite.test_unset_env_hides_an_inherited_variable(monkeypatch)[source]¶
A variable exported around the suite can be taken away for one case.
The half
envcannot cover: assigning the empty string leaves the variable set, which a flag read by bare presence counts as activation.
- tests.test_test_suite.test_env_directives_leave_the_runner_environment_alone(monkeypatch)[source]¶
Cases stay independent under
--jobs: only the child is touched.
- tests.test_test_suite.test_env_overrides_the_injected_io_encoding_default()[source]¶
A case pinning PYTHONIOENCODING wins over the harness’s own default.
The harness injects
utf8so a child’s piped stdout stays decodable (seetest_child_inherits_utf8_io_encoding); a case declaring the variable itself must land after that.cp1252is picked overlatin-1because Python reports the latter back under itsiso8859-1alias.
- tests.test_test_suite.test_env_rejects_what_is_not_a_string_mapping(value, exception, match)[source]¶
An environment holds strings only, so anything else is refused loudly.
- tests.test_test_suite.test_unset_env_normalizes_like_the_envvar_helpers()[source]¶
A single name is wrapped, duplicates collapse, blanks are dropped.
- tests.test_test_suite.LIST_CWD = ('-c', "import os; print(sorted(os.listdir('.')))")¶
Command line printing the names the working directory holds.
- tests.test_test_suite.test_work_directory_moves_the_command(tmp_path)[source]¶
The command runs where asked, not where the runner sits.
- tests.test_test_suite.test_work_directory_defaults_to_the_runner_directory(tmp_path)[source]¶
Left unset, a case sees what it always saw.
- tests.test_test_suite.test_work_directory_leaves_the_command_resolution_alone(tmp_path)[source]¶
The target is resolved before the move, so it is never looked for there.
run_cli_testresolves aPATHname (and.absolute()``s a path) against the runner's own directory first, which is what lets a relative target survive a ``work_directorypointing somewhere that does not hold it.
- tests.test_test_suite.test_run_suite_applies_the_work_directory_to_every_case(tmp_path)[source]¶
The orchestrator hands it down, so a whole suite moves at once.
- tests.test_test_suite.test_env_and_unset_env_are_valid_suite_directives()[source]¶
Both reach a case through a serialized suite, not just the Python API.
- tests.test_test_suite.test_run_counts_pass_and_fail(jobs)[source]¶
Pass/fail tallies match regardless of the worker count.
- tests.test_test_suite.test_run_select_test_skips_others()[source]¶
select_test runs only the chosen 1-based cases; the rest count as skipped.
- tests.test_test_suite.test_run_exit_on_error_bails_sequentially()[source]¶
With one worker, exit_on_error stops before later cases run.
- tests.test_test_suite.test_run_stats_echoes_summary(capsys)[source]¶
stats prints the worker line up front and the result tally at the end.
- tests.test_test_suite.test_run_no_stats_is_quiet(capsys)[source]¶
Without stats, neither the worker line nor the tally is printed.
- tests.test_test_suite.test_cli_runs_default_suite(invoke)[source]¶
With no suite source, the subcommand runs the built-in default suite.
- tests.test_test_suite.test_cli_runs_suite_file(invoke, tmp_path)[source]¶
A –suite-file is parsed and run against the target command.
- tests.test_test_suite.test_cli_runs_toml_suite_file(invoke, tmp_path)[source]¶
A TOML –suite-file runs without the yaml extra, since TOML is built in.
- tests.test_test_suite.test_cli_reports_failure_exit_code(invoke, tmp_path)[source]¶
A failing case makes the subcommand exit non-zero.
- tests.test_test_suite.test_cli_rejects_non_integer_jobs(invoke)[source]¶
–jobs is click-extra’s JobsOption, so a non-numeric value is refused.
- tests.test_test_suite.test_cli_skip_platform_knows_every_platform_id(invoke)[source]¶
–skip-platform takes any extra_platforms ID and refuses anything else.
The IDs are loaded when the option is first parsed, so importing the CLI does not load the platform detection, and the check still happens.
- tests.test_test_suite.test_cli_requires_command(invoke)[source]¶
Without –command/–binary, the subcommand errors with a usage message.
tests.test_testing module¶
Test the testing utilities and the simulation of CLI execution.
- tests.test_testing.test_real_fs()[source]¶
Check a simple test is not caught into the CLI runner fixture which is encapsulating all filesystem access into temporary directory structure.
- tests.test_testing.test_temporary_fs(runner)[source]¶
Check the CLI runner fixture properly encapsulated the filesystem in temporary directory.
- tests.test_testing.test_invoke_force_color(invoke)[source]¶
Test colors are preserved while invoking, and forced to be rendered on Windows.
- tests.test_testing.test_invoke_force_color_contradicting_color_raises()[source]¶
Asking for forced colors and stripped ones at once is refused.
- tests.test_testing.test_invoke_catch_exceptions_follows_the_runner(runner_catches, invoke_catches, propagates)[source]¶
invoke()falls back to the runner’s owncatch_exceptionssetting.
- tests.test_testing.test_command_default_color()[source]¶
With @command and no color env var, Context and ColorOption resolve the GNU auto default (ctx.color=None), yet a forced runner still renders ANSI codes.
- tests.test_testing.test_command_no_color_flag()[source]¶
Invoke with –no-color. Verify ctx.color=False and ANSI stripped from echo output.
- tests.test_testing.test_force_color_attribute()[source]¶
CliRunner.force_color=True applies
force_colorto every run.
- tests.test_testing.test_no_color_envvar()[source]¶
NO_COLOR=1 env var causes ctx.color=False via ColorOption.
- tests.test_testing.test_force_color_envvar()[source]¶
FORCE_COLOR=1 env var keeps ctx.color=True via ColorOption.
tests.test_theme module¶
Tests for click_extra.theme: in-process isolation, built-in themes and
[tool.<cli>.themes.<name>] config integration.
- tests.test_theme.test_theme_does_not_leak_across_invocations()[source]¶
A
--theme lightinvocation must not bleed into a later--helprender.Two back-to-back invocations of the same CLI in the same process:
--theme light --help– selects the light palette for this call only.--help– no--themeargument, must fall back to the dark default.
The dark theme renders headings with
\x1b[94m(bright blue); the light theme uses\x1b[35m(magenta, chosen to stay distinct from its blue options). If the second invocation picks up the first’s choice via process-wide state, it leaks the light palette and the assertion below fails.
- tests.test_theme.test_theme_default_unchanged_after_invocation()[source]¶
An invocation with
--theme lightmust not mutate the process-wide default.
- tests.test_theme.test_demo_themes_renders_each_builtin_palette()[source]¶
click-extra themesrenders the sample help once per built-in theme, each in its own palette.Guards against the whole gallery collapsing onto the default palette: the
themescommand drivesget_current_themethrough the contextTHEMEmeta, so a regression there would render all seven screens identically.
- tests.test_theme.test_demo_themes_renders_only_the_named_palettes()[source]¶
Named theme IDs narrow the gallery down, and keep the order they were given.
- tests.test_theme.test_demo_themes_resolves_auto_to_a_palette_name()[source]¶
autois a directive, so the gallery labels the palette it lands on.Which palette that is depends on the background detected from the environment, so the expectation is read back from the same resolver rather than pinned to
dark.
- tests.test_theme.test_demo_themes_rejects_an_unknown_palette()[source]¶
An unknown ID fails, and the error lists every name that would have worked.
- tests.test_theme.test_theme_meta_key_matches_registry()[source]¶
get_current_theme()reads from the same keyThemeOptionwrites.
- tests.test_theme.test_font_role_slots_are_known_and_disjoint()[source]¶
LITERAL_STYLES/REPLACEABLE_STYLESmust classify real, distinct slots.The two frozensets encode the man-pages(7) bold/italic font roles by slot name and are maintained by hand, so guard against drift: every name must be a real
HelpThemefield, the two roles must not overlap, and the representative slots must keep their expected role.
- tests.test_theme.test_builtin_themes_alphabetical()[source]¶
Built-in themes are declared alphabetically (matters for
--themechoices).
- tests.test_theme.test_builtin_themes_build_on_first_lookup()[source]¶
Importing the package builds no theme, and a
--helpbuildsdarkonly.Reading the theme names, which
--themedoes to list its choices, builds nothing either. Checked in a pristine interpreter, since the other tests have built every theme already.
- tests.test_theme.test_builtin_themes_resolve_to_one_instance()[source]¶
A built-in theme is the same instance in every mapping that holds it.
- tests.test_theme.test_theme_registry_takes_dict_mutations(monkeypatch)[source]¶
Registering, replacing and removing entries work on built-in names too.
- tests.test_theme.test_builtin_themes_are_helptheme_instances()[source]¶
Every
BUILTIN_THEMESentry is aHelpThemeinstance.
- tests.test_theme.test_cloup_constructors_keep_the_subclass(palette)[source]¶
dark()andlight()return a click-extra theme carrying cloup’s palette.Cloup builds both palettes from
cloup.Style, whichto_dictandcascadereject, so each style is widened to click-extra’s.
- tests.test_theme.test_with_rejects_unknown_slots()[source]¶
Every argument naming no slot is listed, in a stable order.
- tests.test_theme.test_with_takes_none_as_a_value()[source]¶
Noneresets a slot, where cloup’s version reads it as no change.
- tests.test_theme.test_builtin_themes_follow_manpage_font_convention(theme_name)[source]¶
Every built-in theme bolds literal slots and italicizes replaceable ones.
Encodes the man-pages(7) typographic convention (
LITERAL_STYLESbold,REPLACEABLE_STYLESitalic) as an invariant across all palettes, so adding a theme or tweaking a slot can’t silently drop the literal/replaceable distinction. Themanpagetheme renders it with no color at all.
- tests.test_theme.test_theme_round_trips_through_dict(theme_name)[source]¶
HelpTheme.to_dict/from_dictround-trips every built-in theme.
- tests.test_theme.test_builtin_theme_definition_matches_to_dict(theme_name)[source]¶
Each built-in definition equals what
to_dictemits for its theme.So a definition sets no slot to its default value, which
to_dictleaves out.
- tests.test_theme.test_to_dict_omits_identity_slots()[source]¶
Slots left at the
identitydefault do not appear into_dictoutput.
- tests.test_theme.test_to_dict_emits_cross_ref_highlight_only_when_overridden()[source]¶
cross_ref_highlightis emitted only when it differs from the default.
- tests.test_theme.test_from_dict_rejects_unknown_keys()[source]¶
Typos like
optoinraiseTypeErrorinstead of being silently dropped.
- tests.test_theme.test_cascade_overrides_only_set_slots()[source]¶
cascadekeeps base’s slots wherever the overlay leaves them at default.
- tests.test_theme.test_cascade_returns_new_instance_when_overlay_changes_anything()[source]¶
Even a single-slot overlay produces a distinct theme instance.
- tests.test_theme.test_cascade_round_trips_through_dict()[source]¶
self.to_dict()wins overbase.to_dict()slot-by-slot.
- tests.test_theme.test_cascade_rejects_non_theme_base()[source]¶
cascaderejects anything that is not aHelpTheme.
- tests.test_theme.test_themechoice_choices_track_global_registry()[source]¶
Outside any context,
choicesreflects the module-level registry.Plus the reserved
autodirective, which every CLI takes and no registry holds.
- tests.test_theme.test_themechoice_choices_pick_up_context_overrides()[source]¶
A theme stashed under
THEME_OVERRIDESshows up inchoices.
- tests.test_theme.test_branded_themes_meet_wcag_aa_large(theme_name, slot)[source]¶
Branded themes’ readable-text slots clear WCAG AA Large (3.0+).
Branded palettes are deliberate 24-bit RGB choices, so we can hold them to a real WCAG threshold. AA Large is the realistic floor: full AA (4.5+) is unattainable for some published themes (like Solarized’s accent blue on its base03 background sits at ~4.08).
A regression that drops one of these slots below 3.0 means the theme is less readable than what currently ships and warrants a deliberate palette tweak rather than a silent slip.
- tests.test_theme.test_themes_meet_legibility_floor(theme_name)[source]¶
Every styled slot in every theme stays above the legibility floor.
Subdued slots (
debug,bracket,default, …) are allowed to fall below WCAG AA Large by design, but no slot should be effectively invisible against the theme’s assumed background. Catches accidental palette tweaks like setting an attribute to nearly the background color.
- tests.test_theme.test_themechoice_inert_when_registry_empty(monkeypatch)[source]¶
With no themes available, ThemeChoice ignores any value instead of failing.
A program can empty the registry: then even the built-in
darkdefault cannot resolve, soconvertreturns None rather than aborting the invocation.
- tests.test_theme.test_cli_runs_with_empty_theme_registry(invoke, monkeypatch)[source]¶
A CLI still runs when no themes are available.
The built-in –theme option defaults to
dark; with an empty registry that default must stay inert instead of crashing the whole command.
- tests.test_theme.test_themes_from_config_overrides_existing_theme()[source]¶
Known theme names cascade on top of the matching built-in palette.
- tests.test_theme.test_themes_from_config_creates_standalone_theme()[source]¶
Unknown theme names build a stand-alone theme with unset slots at default.
- tests.test_theme.test_themes_from_config_does_not_mutate_global_registry()[source]¶
themes_from_configis pure: the module-level registry is untouched.
- tests.test_theme.test_get_theme_registry_falls_back_to_global_without_ctx()[source]¶
get_theme_registry(None)returns a copy of the module-level registry.
- tests.test_theme.test_config_loads_new_theme(invoke, create_config)[source]¶
A
[tool.<cli>.themes.<name>]table registers a new theme for the invocation.
- tests.test_theme.test_config_overrides_existing_theme_palette(invoke, create_config)[source]¶
A
[tool.<cli>.themes.dark]table cascades onto the built-in dark palette.
- tests.test_theme.test_config_theme_appears_in_help_metavar(invoke, create_config)[source]¶
--helplists config-defined themes alongside the built-ins.
- tests.test_theme.test_config_theme_validation_error(invoke, create_config)[source]¶
A malformed
[tool.<cli>.themes.<name>]table is rejected with a rooted path.
- tests.test_theme.test_validate_themes_config_codes(themes, code)[source]¶
A malformed themes table is reported under a snake-case code, like every other configuration error.
- tests.test_theme.test_command_validators_keep_the_theme_validator(invoke, create_config)[source]¶
Validators a command declares join the built-in themes validator.
@command(config_validators=...)forwards them to the default--configoption, which keeps the built-in validator beside them: a malformed[tool.<cli>.themes.<name>]table is reported as a configuration validation error, not aTypeError.
- tests.test_theme.test_config_theme_does_not_leak_across_invocations(invoke, create_config)[source]¶
Themes defined in invocation N are not visible in invocation N+1.
- tests.test_theme.test_validate_config_catches_bad_theme(invoke, create_config)[source]¶
--validate-configsurfaces the same ValidationError as the runtime path.
- tests.test_theme.test_theme_auto_resolves_to_detected_palette(invoke, monkeypatch, env, expected_name)[source]¶
--theme=autopicks the built-in palette implied by the terminal background.
- tests.test_theme.test_theme_auto_from_config(invoke, create_config, monkeypatch)[source]¶
theme = "auto"in the config file makes detection the effective default.
- tests.test_theme.test_theme_auto_advertised_in_help_metavar(invoke)[source]¶
The reserved ‘auto’ directive is listed among the values –theme takes.
It resolves to a palette rather than being one, but it works on every Click Extra CLI, and a value named nowhere is a value nobody finds.
- tests.test_theme.test_themechoice_accepts_auto_directive()[source]¶
‘auto’ converts to itself though it is not a registered palette.
- tests.test_theme.test_theme_option_query_background_opt_in()[source]¶
The live OSC 11 query is off unless a CLI explicitly opts in.
- tests.test_theme.test_resolve_auto_theme_forwards_query_flag(monkeypatch)[source]¶
query_backgroundgates the live query that env-var detection ignores.
- tests.test_theme.test_theme_envvar_picks_palette(invoke, monkeypatch, env_value, expected_name)[source]¶
CLICK_EXTRA_THEMEthemes a CLI invoked without--theme.
- tests.test_theme.test_theme_envvar_accepts_auto_directive(invoke, monkeypatch)[source]¶
The reserved ‘auto’ directive resolves from the variable too.
- tests.test_theme.test_theme_flag_outranks_envvar(invoke, monkeypatch)[source]¶
An explicit
--themebeats the machine-wide variable.
- tests.test_theme.test_cli_envvar_outranks_theme_envvar(invoke, monkeypatch)[source]¶
The
<CLI>_THEMEvariable is more specific than the machine-wide one.
- tests.test_theme.test_config_outranks_theme_envvar(invoke, create_config, monkeypatch)[source]¶
A palette pinned in the CLI’s own config file is more specific.
- tests.test_theme.test_unknown_theme_envvar_warns_and_keeps_default(invoke, monkeypatch)[source]¶
A typo in a shell profile must not break every Click Extra CLI at once.
- tests.test_theme.test_theme_from_env_reaches_the_help_screen(invoke, monkeypatch, envvar)[source]¶
A palette named by the environment paints the screen it exists to paint.
Click processes a typed
--helpbefore an untyped--theme, so the palette only reaches the help screen through the pre-pass inCommand._resolve_presentation_eagerly.
tests.test_trail module¶
- tests.test_trail.test_bar_stop_leaves_a_stalled_ticker_behind(monkeypatch)[source]¶
The progress bar’s elapsed-clock ticker gets the same bounded stop.
- tests.test_trail.test_bar_teardown_after_a_stall_returns_at_once(monkeypatch)[source]¶
A second teardown of a bar whose ticker stalled does not wait on it.
The stalled ticker holds the draw lock while it blocks in its write. A Ctrl+C inside a trail runs
finish(), thenstop(), and the second one must not take that lock and wait for the terminal to drain.
- tests.test_trail.test_interrupted_trail_closes_with_a_finisher(progress_bar, jobs, stream_factory)[source]¶
A Ctrl+C inside the trail still ends it on a
✘line saying how far it got, and an outcome landing afterwards prints below that line.
- tests.test_trail.test_demo_trail_help_lists_renderings(invoke)[source]¶
trail --helpdocuments its purpose and the rendering-selecting options.
- tests.test_trail.test_demo_trail_rejects_unknown_spinner(invoke)[source]¶
An unknown –spinner name is rejected by the Choice type before any work.
- tests.test_trail.test_demo_trail_prints_plain_lines_off_tty(invoke, monkeypatch, args)[source]¶
Every rendering runs the batch to completion and, off a TTY, prints each outcome and the finisher as plain lines, with no indicator drawn.
- tests.test_trail.test_sequential_trail_echoes_lines_and_finisher()[source]¶
A sequential batch on a TTY echoes each outcome, then a timed finisher.
- tests.test_trail.test_sequential_trail_echoes_off_tty()[source]¶
A non-interactive stream gets the trail lines and finisher by default.
- tests.test_trail.test_sequential_trail_invisible_stays_silent_off_tty()[source]¶
visible=Falsesilences the trail on a non-interactive stream too.
- tests.test_trail.test_trail_forced_spinner_draws_on_pipe()[source]¶
live="always"animates the aggregate spinner on a non-interactive stream.
- tests.test_trail.test_sequential_trail_echo_opt_out()[source]¶
echo_sequential=Falsesilences a sequential batch, even on a TTY.
- tests.test_trail.test_concurrent_trail_buffers_until_spinner_draws()[source]¶
Outcomes marked before the aggregate spinner first draws are buffered, then flushed above it; the finisher becomes the spinner’s kept line.
The draw delay guarantees the first mark lands before the first frame, making the buffering deterministic instead of racing the animation thread.
- tests.test_trail.test_concurrent_trail_invisible_stays_silent()[source]¶
visible=Falsekeeps the concurrent spinner and its buffer off screen.
- tests.test_trail.test_trail_echoes_a_batch_its_indicator_never_drew(rendering, finished)[source]¶
A batch finishing inside
delaystill leaves its whole record on screen.Its indicator never drew, so the lines it buffered never reached the stream. A sequential batch of the same outcomes prints them, and so must this one, in order: before the finisher, or on its own when the batch is left without one.
- tests.test_trail.test_trail_leaves_nothing_for_an_undrawn_batch_without_echo(options, stream_class, finished)[source]¶
The echo of an undrawn batch obeys the same gates as a sequential one.
- tests.test_trail.test_trail_stays_plain_above_a_drawn_indicator_without_color(monkeypatch, rendering)[source]¶
With color off, a drawn indicator’s trail lines and finisher carry no SGR.
Those lines reach the stream through a raw write rather than
click.echo, so the glyph’s paint and any escape embedded in the message have to be stripped before the write.
- tests.test_trail.test_concurrent_trail_marks_are_thread_safe()[source]¶
Concurrent mark() calls from worker threads all land in the tally.
- tests.test_trail.test_concurrent_trail_uses_chosen_spinner_preset()[source]¶
spinner=picks the concurrent aggregate spinner’s animation.
- tests.test_trail.test_concurrent_spinner_eta_mode()[source]¶
clock=’eta’ drives the concurrent spinner’s estimate from a hidden Click bar (stepped per outcome), and the finisher still shows the elapsed total.
- tests.test_trail.test_operation_trail_appends_per_operation_timing()[source]¶
With timer on (the default), a
secondsvalue appends each operation’s own duration to its trail line, independent of the others.
- tests.test_trail.test_operation_handle_times_from_its_creation()[source]¶
An operation() handle marks its outcome with the elapsed since it began.
- tests.test_trail.test_operation_trail_timer_false_drops_all_timing()[source]¶
timer=False silences both the per-item and the finisher clock.
- tests.test_trail.test_operation_trail_timer_callable_formats_durations()[source]¶
A callable timer formats the per-item durations it is handed.
- tests.test_trail.test_progress_bar_trail_renders_bar_and_finisher()[source]¶
progress_bar=Truedrives a determinate bar with outcomes above it.
- tests.test_trail.test_progress_bar_trail_shows_empty_bar_on_entry()[source]¶
The bar’s 0/total state draws on entry, not only once the first outcome advances it, matching the spinner indicator that animates its tally at once.
- tests.test_trail.test_progress_bar_clock_defaults_to_elapsed()[source]¶
clock=’elapsed’ (the default) draws a stopwatch from the start via item_show_func, with Click’s ETA off and a ticker to keep it moving.
- tests.test_trail.test_progress_bar_clock_eta_uses_click_eta()[source]¶
clock=’eta’ keeps Click’s estimated-time display and runs no ticker.
- tests.test_trail.test_progress_bar_elapsed_clock_ticks_between_marks()[source]¶
The elapsed clock advances on its own between outcomes, with no mark.
- tests.test_trail.test_operation_trail_rejects_invalid_clock()[source]¶
clockmust be ‘elapsed’ or ‘eta’.
- tests.test_trail.test_operation_trail_rejects_invalid_live()[source]¶
livemust be ‘auto’, ‘always’ or ‘never’.
- tests.test_trail.test_invisible_trail_draws_nothing_even_when_live_always()[source]¶
visible=Falsewins overlive="always": no indicator, no line.
- tests.test_trail.test_progress_bar_trail_works_concurrently()[source]¶
progress_bar=Truealso drives a concurrent batch from worker threads.
- tests.test_trail.test_trail_echoes_live_where_no_indicator_draws(monkeypatch, rendering, stream_class, term, options)[source]¶
Where no indicator draws, each outcome prints as soon as it is marked.
Nothing owns the live line there, so no line waits for a first frame that never comes, and no cursor control reaches the stream.
- tests.test_trail.test_progress_bar_requires_positive_total()[source]¶
A determinate bar needs a length, so
totalmust be positive.
tests.test_tree module¶
Tests for the command-tree rendering feature.
- tests.test_tree.OBSERVATORY_TREE = 'observatory Weather observatory control center.\n├── help [COMMAND_PATH]... Show help for a command.\n├── report CITY Print the forecast for a city.\n├── station (st) Manage remote weather stations.\n│ ├── calibrate Recalibrate the sensors.\n│ ├── help [COMMAND_PATH]... Show help for a command.\n│ └── reboot Power-cycle the station. (DEPRECATED)\n└── status Report the current conditions.'¶
Expected unstyled tree of the
observatoryfixture CLI.Descriptions are column-aligned on the longest rail-plus-label row, hidden commands are skipped, aliases are parenthesized, operand metavars follow the command name and deprecated commands carry their marker. The
helpentries are the implicit subcommand every click-extra group registers.
- tests.test_tree.test_render_tree_theme_styling()[source]¶
Nodes reuse the same theme slots and styling as help screens.
- tests.test_tree.test_render_tree_paints_each_choice_of_an_operand()[source]¶
An operand enumerating its values draws them the way a help screen does.
Painted one value at a time rather than as a single metavar span, so a reader picks the accepted words out of the punctuation around them.
- tests.test_tree.test_render_tree_paints_the_deprecation_label()[source]¶
The marker takes the slot the help screen paints it with.
A description reaches the tree already carrying Click’s label, and it is the only part of a node the name-and-metavar pass does not style.
- tests.test_tree.test_render_tree_wraps_descriptions()[source]¶
Long descriptions wrap in the aligned column, rail running through.
- tests.test_tree.test_tree_option_accessible_ascii_rail(runner)[source]¶
Accessibility mode degrades the box-drawing rail to pure ASCII.
- tests.test_tree.test_wrap_tree_drills_into_subcommand(runner, kitchen_script)[source]¶
Extra arguments after SCRIPT re-root the tree at the nested subcommand.
tests.test_types module¶
- tests.test_types.test_click_choice_behavior()[source]¶
Lockdown the behavior of method inherited from Click’s Choice type.
- Return type:
- tests.test_types.test_enum_string_choices(enum_definition, choice_source, result)[source]¶
- Return type:
- tests.test_types.test_enum_choice_show_aliases(enum_definition, choice_source, show_aliases, result)[source]¶
Test that EnumChoice correctly handles Enum with aliases.
- Return type:
- tests.test_types.kebab_case(choice)[source]¶
Reshape a Python identifier into a CLI-friendly choice string.
- Return type:
- class tests.test_types.AliasedStrategy(*values)[source]¶
Bases:
EnumAliases declared in the class body, which every supported Python handles.
Unlike
_add_alias_(), this needs no Python 3.13.- SELECT_OLDER = 1¶
- SELECT_NEWEST = 2¶
- DISCARD_NEWEST = 1¶
- DISCARD_OLDER = 2¶
- class tests.test_types.TransformedFormat(*values)[source]¶
Bases:
Enum- PLAIN_TEXT = 'plain_text'¶
- RICH_HTML = 'rich_html'¶
- tests.test_types.test_enum_choice_transform(choice_source, transform, expected)[source]¶
transformreshapes the choice string produced by any source.- Return type:
- tests.test_types.test_enum_choice_transform_with_aliases(choice_source)[source]¶
transformis what makesshow_aliasesusable with non-identifier choices.- Return type:
- tests.test_types.test_enum_choice_transform_collision(show_aliases)[source]¶
A
transformcollapsing two spellings into one is rejected.- Return type:
- tests.test_types.test_enum_choice_transform_non_string()[source]¶
A
transformreturning a non-string blames itself, not the choice source.- Return type:
- tests.test_types.test_enum_choice_transform_raising()[source]¶
A
transformblowing up is reported with the string it choked on.- Return type:
- class tests.test_types.MyEnum(*values)[source]¶
Bases:
EnumProduce different strings for keys/names, values and str().
- FIRST_VALUE = 'first-value'¶
- SECOND_VALUE = 'second-value'¶
- tests.test_types.test_enum_choice_shell_complete(source, expected)[source]¶
Completion offers normalized choice strings, never the
Enum.memberform.Completion routes through
Choice.normalize_choice(), so anEnumChoicesuggests parseable strings (case-folded, as it is case-insensitive by default) instead ofMyEnum.FIRST_VALUE.- Return type:
- tests.test_types.test_enum_choice_rejects_non_enum_choices(choices)[source]¶
Anything but an
Enumclass is refused with aTypeError.- Return type:
- tests.test_types.test_enum_choice_rejects_unknown_choice_source()[source]¶
A string naming no
ChoiceSourcemember is refused with aValueError.- Return type:
- tests.test_types.test_enum_choice_command(invoke, cmd_decorator, opt_decorator, case_sensitive, valid_args, invalid_args)[source]¶
Test EnumChoice used within an option.
- Return type:
- tests.test_types.test_enum_choice_default_value(invoke, cmd_decorator, opt_decorator, opt_type, default_value)[source]¶
Test EnumChoice used within an option with a default value.
- Return type:
- tests.test_types.test_enum_choice_multiple_default_value(invoke, cmd_decorator, opt_decorator, default_value, expected)[source]¶
A
multiple=TrueEnumChoice resolves each member of its tuple default.Regression test for the
get_default()override stringifying the whole default tuple (str((MyEnum.FOO,))) instead of mapping each member, which made the default trip Click’sValue must be an iterablecheck.- Return type:
- tests.test_types.test_enum_choice_none_default(invoke, cmd_decorator, opt_decorator)[source]¶
A single-value EnumChoice option defaulting to
Nonestays unset.Regression test for the
get_default()override turning aNonedefault into the choice string"None", which then failed its own validation.- Return type:
- tests.test_types.test_enum_choice_variadic_default_value(invoke, cmd_decorator, opt_decorator, default_value, expected)[source]¶
A variadic (nargs=-1) EnumChoice argument resolves each member of its default.
Companion to test_enum_choice_multiple_default_value covering the other branch of the get_default() override: the nargs == -1 path taken by arguments rather than the multiple path taken by options. Both map get_choice_string() over the members of a tuple default instead of stringifying the whole tuple.
- Return type:
- tests.test_types.test_enum_choice_callback(invoke, cmd_decorator, opt_decorator, choice_source, cli_value)[source]¶
Test that option callbacks receive properly converted Enum members.
- Return type:
- tests.test_types.test_multi_choice_parses_input(raw, expected)[source]¶
convert()splits on the separator, strips whitespace, drops empties.- Return type:
- tests.test_types.test_multi_choice_validates_against_choices()[source]¶
Unknown tokens raise
BadParameterviaself.fail().- Return type:
- tests.test_types.test_multi_choice_validates_an_already_parsed_sequence()[source]¶
A list is checked like a raw token, not waved through as pre-parsed.
A configuration file hands the option a list where the command line hands it one string, so skipping the check on a sequence lets an unknown value travel to whatever consumes the selection.
- Return type:
- tests.test_types.test_multi_choice_case_insensitive_normalizes()[source]¶
The default matches case-insensitively and returns the canonical case.
- Return type:
- tests.test_types.test_multi_choice_case_sensitive_matches_exactly()[source]¶
case_sensitive=Truerejects a token differing only in case.- Return type:
- tests.test_types.test_choice_types_default_to_case_insensitive(choice_type)[source]¶
Every choice type click-extra defines matches regardless of case by default.
- Return type:
- tests.test_types.test_multi_choice_in_click_option()[source]¶
End-to-end:
MultiChoiceplugs into a Click option likeChoicedoes.- Return type:
- tests.test_types.test_duration_passthrough_timedelta()[source]¶
An already-parsed timedelta is returned unchanged (idempotent conversion).
- Return type:
- tests.test_types.test_duration_rejects_calendar_units(value)[source]¶
Months and years are explicitly rejected because their length is ambiguous.
- Return type:
- tests.test_types.test_duration_absolute_timestamp(value)[source]¶
An RFC 3339 timestamp is converted to
now - timestampat parse time.- Return type:
- tests.test_types.test_duration_future_timestamp_parses_to_none()[source]¶
A timestamp in the future parses to
None, read as “no cutoff”.- Return type:
- tests.test_types.test_duration_in_click_option()[source]¶
End-to-end:
Durationplugs into a Click option.- Return type:
- tests.test_types.test_parse_duration_passthrough_timedelta()[source]¶
An existing timedelta is returned unchanged, like Duration.convert.
- Return type:
tests.test_utils module¶
Test the generic helpers of the private utility module.
- tests.test_utils.test_exception_chain_ends_on_a_loop()[source]¶
Two exceptions naming each other are each yielded once.
- tests.test_utils.test_exception_chain_prefers_the_cause()[source]¶
The cause an exception was raised from wins over the one it replaced.
- tests.test_utils.test_flatten_collapses_nested_iterables()[source]¶
Every nesting level is walked, strings and bytes staying whole.
- tests.test_utils.test_deep_merge_merges_dicts_and_takes_every_other_value_from_the_overlay()[source]¶
- tests.test_utils.SET_NESTED_PATHS: tuple[tuple[tuple[str, ...], object], ...] = ((('orchard', 'apple', 'count'), 3), (('orchard', 'apple', 'color'), 'red'), (('orchard', 'pear'), {'count': 1}), (('orchard', 'pear'), {'color': 'green'}), (('orchard', 'pear', 'count'), 2), (('garden',), 'tomato'), (('garden', 'basil'), True), (('orchard', 'apple'), ['fuji', 'gala']), (('orchard', 'apple', 'count'), 4), (('barn',), {'hay': {'bales': 10}}), (('barn', 'hay'), {'straw': 2}))¶
Paths and values covering every case of a merge: a fresh path, a shared prefix, two dictionaries merging, a dictionary replacing a value, and the reverse.
- tests.test_utils.test_set_nested_matches_merging_one_path_trees()[source]¶
Setting each path in place builds the tree the one-path merges built.
- tests.test_utils.test_parse_bool(text, expected)[source]¶
The spellings read the same whatever their case and surrounding blanks.
- tests.test_utils.test_boolean_states_match_configparser()[source]¶
The copy holds the spellings the standard library reads, no more.
- tests.test_utils.BLANK_TREE: dict[str, object] = {'city': 'Lyon', 'forecast': {'rain': None, 'sky': '', 'wind': {}}, 'harbor': {'boats': 0, 'docks': [], 'flags': False}, 'lake': None, 'orchard': {}, 'river': ''}¶
A tree holding a blank value of each kind, at the top and one level down.
tests.test_version module¶
Test the --version option.
- tests.test_version.test_debug_output(invoke, cmd_decorator, option_decorator, assert_output_regex)[source]¶
- tests.test_version.test_set_version_positional(invoke)[source]¶
Click drop-in: an explicit version may be the first positional argument.
@version_option("1.2.3.4")is equivalent to thefields={"version": "1.2.3.4"}form used bytest_set_version().
- tests.test_version.test_version_option_dashed_positional_is_flag()[source]¶
A leading-dash positional names the option flag, not the version.
- tests.test_version.test_version_option_conflicting_version()[source]¶
The version cannot be supplied both positionally and via fields=.
- tests.test_version.test_custom_message(invoke, cmd_decorator, message, regex_stdout, assert_output_regex)[source]¶
- tests.test_version.test_build_resolver_answers_and_is_a_template_field(field_id)[source]¶
Every build resolver answers on the host running it, and has a field.
A build fact is unlike a git one on both counts: nothing can fail to resolve, since the host is right here, and nothing can fall back later, so a resolver whose field is missing from the template would bake a value no message could ever print.
- tests.test_version.test_build_time_honors_source_date_epoch(monkeypatch)[source]¶
A reproducible build pins the stamp instead of reading the clock.
- tests.test_version.test_debug_version_run_resolves_no_host_name(invoke, monkeypatch)[source]¶
A
--verbosity DEBUGrun lists every version field without a DNS query.A reverse DNS query blocks for as long as the resolver takes to answer, which has no bound on a host whose resolver never does.
Asserting on the calls rather than on a duration keeps the guard away from a timing threshold, which a loaded runner would flake on.
- tests.test_version.test_debug_listing_reaches_a_level_below_debug(invoke, caplog)[source]¶
A logger set below
DEBUG, like a custom trace level, lists the fields too.
- tests.test_version.test_context_meta_laziness(invoke, cmd_decorator)[source]¶
Accessing a single field from
ctx.metamust not evaluate unrelated fields.Ensures that the
_LazyVersionDictdefers property evaluation: readingclick_extra.versionshould not trigger expensive properties like the git fields.
- tests.test_version.test_module_version_parent_package_fallback(monkeypatch)[source]¶
module_versionfalls back to parent package’s__version__.Simulates the Nuitka use-case: a CLI whose module is
myapp.__main__(no__version__), with the parent packagemyappproviding it.
- tests.test_version.test_main_entry_point_survives_absent_distribution_metadata(monkeypatch)[source]¶
A compiled binary reads its version off the package it started in.
A Nuitka standalone binary ships no distribution metadata, so
distribution_of()answersNonefor the CLI’s own package just as it does for ecosystem plumbing, and the CLI module resolves toclick_extra.__main__. That entry point must survive: traded for the root command’s callback module, it loses the__main__exemption and--versionrenders nothing at all.
- tests.test_version.test_prebaked_fields_read_from_the_package(monkeypatch, module_name)[source]¶
A field baked into the package
__init__.pyreaches the CLI module beside it.click-extra prebake allwrites its dunders into the__init__.pyof each package a console script enters, while the CLI sits in a sibling module.
- tests.test_version.test_prebaked_fields_skip_the_package_of_ecosystem_plumbing(monkeypatch)[source]¶
A walk ending on a
CliRunnernever reads Click Extra’s own baked fields.
- tests.test_version.test_exec_name(module_name, package, expected)[source]¶
A
__main__entry point is named after its package, however it started.python -m orchardruns the entry point under the__main__name, while a console script or a compiled binary imports it asorchard.__main__.
- tests.test_version.test_package_version_resolves_import_name_to_distribution(monkeypatch)[source]¶
When
package_nameis an import name that differs from its installed distribution name (PILvsPillow),package_versionresolves it viapackages_distributions()instead of returningNone.
- tests.test_version.test_package_version_ambiguous_import_name_returns_none(monkeypatch)[source]¶
When an import name maps to several installed distributions,
package_versionreturnsNonerather than guessing.
- tests.test_version.test_package_version_unknown_returns_none(monkeypatch)[source]¶
When the name resolves to no distribution,
package_versionreturnsNone.
- tests.test_version.test_cli_frame_fallback()[source]¶
cli_frame()falls back to the outermost frame when all frames are from the Click ecosystem.
- tests.test_version.STANDALONE_SCRIPT = 'import click\nfrom click_extra import echo, version_option\n\n\n@click.command\n@version_option(\n message=(\n "{prog_name} | {exec_name} | {package_name}"\n " | {module_version} | {version}"\n )\n)\ndef weather():\n echo("Sunny.")\n\n\nif __name__ == "__main__":\n weather()\n'¶
A CLI run straight from a file as
__main__, with no package around it.Its version message renders every field the unpackaged case resolves on its own, so a single invocation pins them all.
- tests.test_version.test_standalone_script(tmp_path, dunder, expected_version)[source]¶
A standalone script is named after its file, and versioned by
__version__.With no package to read metadata from,
exec_namefalls back to the script’s file name andpackage_nametoNone. The version is read from a__version__variable defined alongside the CLI, and staysNonewhen the script defines none.Only a real interpreter reaches that code path: a CLI declared inside a test function belongs to the test module, and that module has a package.
- tests.test_version.COMMAND_STRING_CLI = 'from click_extra import command, echo\n\n__version__ = "1.2.3"\n\n\n@command\ndef weather():\n echo("Sunny.")\n\n\nweather()\n'¶
A Click Extra CLI for
python -c, which runs it with no file behind it.
- tests.test_version.test_command_string_cli(tmp_path, args, expected)[source]¶
A CLI run by
python -cresolves its version fields without a file.The version option of a Click Extra command looks for the CLI’s module on every run, not only on
--version. The debug level renders every field, including the ones read off the module’s file:exec_namekeeps the__main__module name.
- tests.test_version.NUITKA_RUNNER = 'import sys\nimport {module} as user_main\nsys.exit(user_main.{function}())\n'¶
What Nuitka compiles for
--main-entry-point={{name}}={{module}}:{{function}}.A
__main__with no package and no__version__, which binds the entry point module and calls its function: a compiled binary starts here.
- tests.test_version.test_nuitka_runner_resolves_the_entry_point_version(tmp_path, module, function)[source]¶
The runner of a compiled binary prints the version
python -mprints.An entry point function stops the stack walk in its own module. A command object leaves no frame of its module on the stack, and the walk ends on the runner: the module of the command’s callback then names the CLI.
- tests.test_version.test_package_version_without_a_cli_frame(tmp_path, runner_source)[source]¶
A CLI reads its package version when no frame of its module is on the stack.
No distribution provides the package, as in a compiled binary, so the version can only come from the
__version__of its__init__.py. The runner Nuitka compiles calls the command object from a packageless__main__. Anatexithook calls it once the main module has left the stack, like the target of_thread.start_new_thread()or a call from a C host: the walk then sees only the Click ecosystem, and falls back to themain()of the command. A thread and an executor call it from the standard library, where the walk stops.
- tests.test_version.SUBCOMMAND_VERSION_CLI = 'from click_extra import echo, group, version_option\n\n\n@group\ndef orchard():\n pass\n\n\n@orchard.command()\n@version_option(fields={"version": "1.2.3"})\ndef harvest():\n echo("Apples picked.")\n\n\norchard()\n'¶
A group whose subcommand carries a version option of its own.
- tests.test_version.test_subcommand_version_option_outside_a_test_runner(tmp_path, args, expected)[source]¶
The version option of a subcommand resolves where no test runner is loaded.
The stack walk of a subcommand crosses the
invoke()of its group, which it checks against the test runner of Click. A process that never importedclick.testinghas no such runner to check against: a test process always has, so only a new interpreter can tell.
- tests.test_version.test_version_fields_forwarded_to_version_option(invoke)[source]¶
version_fieldson@commandforwards toVersionOption.
- tests.test_version.test_version_fields_forwarded_on_group(invoke)[source]¶
version_fieldsworks on@grouptoo.
- tests.test_version.test_version_fields_multiple(invoke)[source]¶
Multiple fields can be overridden at once.
- tests.test_version.test_version_fields_rejects_unknown(invoke)[source]¶
Unknown field names raise
TypeError.
- tests.test_version.test_unknown_field_is_rejected_the_same_way_everywhere(build)[source]¶
Every way to name a template field rejects an unknown one alike.
- tests.test_version.test_color_option_precedence(invoke)[source]¶
A plain
click.commandonly decolors--versionwhen--no-colorprecedes it on the command line.Click evaluates eager parameters in the order the user typed them (callback evaluation order), so a
--no-colorplaced after--versionlands too late: the version screen has already rendered in color and exited.click-extra’s own
@commandsettles the color options in a pre-pass before any eager screen renders, so the color choice is honored whatever its position. SeeCommand._resolve_presentation_eagerlyand the order-independenttest_color_settles_before_eager_help_and_versionintest_color.py. This test pins the residual behavior of the plain-Click path, which that pre-pass does not reach.
- tests.test_version.test_dev_version_appends_git_hash(invoke, cmd_decorator)[source]¶
A
.devversion gets a+hashsuffix appended (or not, if git is unavailable).
- tests.test_version.test_prebaked_dev_version_not_double_suffixed(invoke, cmd_decorator)[source]¶
A version with an existing
+is returned as-is: no second hash appended.
- tests.test_version.test_release_version_unchanged(invoke, cmd_decorator)[source]¶
A non-dev version is never modified.
- tests.test_version.init_file(tmp_path)[source]¶
Helper that creates a temporary __init__.py with the given content.
- tests.test_version.test_prebake_cli_resolves_module_from_config(invoke, tmp_path, monkeypatch)[source]¶
click-extra prebakeresolves its target from[tool.click-extra.prebake].
- tests.test_version.test_prebake_dev_version(init_file)[source]¶
A
.devversion gets+hashappended in the file.
- tests.test_version.test_prebake_single_quotes(init_file)[source]¶
Single-quoted
__version__is also handled.
- tests.test_version.test_prebake_already_baked_skipped(init_file)[source]¶
A version with existing
+is left untouched.
- tests.test_version.test_prebake_release_skipped(init_file)[source]¶
A release version (no
.dev) is not modified.
- tests.test_version.test_prebake_no_version_in_file(init_file)[source]¶
A file without
__version__returnsNone.
- tests.test_version.test_prebake_missing_local_version_raises(init_file)[source]¶
Calling without
local_versionraisesTypeError.
- tests.test_version.test_prebake_idempotent(init_file)[source]¶
Running prebake twice does not double-suffix.
- tests.test_version.test_prebake_preserves_surrounding_content(init_file)[source]¶
Content around
__version__is not disturbed.
- tests.test_version.test_prebake_dunder_empty_replaced(init_file)[source]¶
An empty dunder variable gets replaced.
- tests.test_version.test_prebake_dunder_single_quotes(init_file)[source]¶
Single-quoted empty dunder is also handled.
- tests.test_version.test_prebake_dunder_nonempty_skipped(init_file)[source]¶
A dunder with an existing non-empty value is left untouched.
- tests.test_version.test_prebake_dunder_not_found(init_file)[source]¶
A file without the target dunder returns
None.
- tests.test_version.test_prebake_dunder_idempotent(init_file)[source]¶
Running prebake_dunder twice does not overwrite.
- tests.test_version.test_prebake_dunder_preserves_surrounding_content(init_file)[source]¶
Content around the target dunder is not disturbed.
- tests.test_version.test_prebake_dunder_full_sha(init_file)[source]¶
A full 40-character SHA is handled correctly.
- tests.test_version.test_prebaked_git_branch()[source]¶
A pre-baked
__git_branch__dunder is used over subprocess.
- tests.test_version.test_prebaked_git_long_hash()[source]¶
A pre-baked
__git_long_hash__dunder is used over subprocess.
- tests.test_version.test_prebaked_git_tag_sha()[source]¶
A pre-baked
__git_tag_sha__dunder is resolved.
- tests.test_version.test_prebaked_empty_dunder_ignored()[source]¶
An empty dunder is not treated as a pre-baked value.
- tests.test_version.test_prebaked_non_string_ignored()[source]¶
A non-string dunder is not treated as a pre-baked value.
- tests.test_version.test_prebaked_git_distance()[source]¶
A pre-baked
__git_distance__dunder is used over subprocess.
- tests.test_version.test_prebaked_git_dirty()[source]¶
A pre-baked
__git_dirty__dunder is used over subprocess.
- tests.test_version.test_run_git_decodes_a_branch_name(tmp_path, branch, expected)[source]¶
A branch name decodes as UTF-8, and a byte that is not becomes a replacement character.
The branch goes in
packed-refs: a loose ref is a file, and APFS refuses a file name that is no UTF-8.
- tests.test_version.test_run_git_returns_nothing_or_raises_on_failure(tmp_path, monkeypatch)[source]¶
A failing command returns
None, or raises when asked to.
- tests.test_version.test_run_git_gives_up_after_its_timeout(tmp_path, monkeypatch)[source]¶
A command running past its timeout returns
None, or raises when asked.
- tests.test_version.test_run_git_keeps_an_empty_last_field(tmp_path)[source]¶
Only the final newline goes: the empty field ending a line stays.
A lightweight tag points at no tag object, so the date read through one is empty, which ends the line on a tab.
- tests.test_version.test_find_git_root(tmp_path)[source]¶
The walk stops at the nearest
.gitentry, a file included.
- tests.test_version.test_archival_field_unsubstituted_ignored(field_id)[source]¶
A raw checkout keeps the $Format placeholders, which must be ignored.
- tests.test_version.test_archival_field_exact_tag_distance_zero()[source]¶
A bare describe-name (no -N-g suffix) means distance zero.
- tests.test_version.test_archival_resolves_git_fields(tmp_path)[source]¶
Git fields resolve from .git_archival.json when there is no live git.
- tests.test_version.install_distribution(tmp_path, monkeypatch, direct_url)[source]¶
Install a fake
apricotdistribution, with adirect_url.jsonholding direct_url unless it isNone.- Return type:
- tests.test_version.test_direct_url_resolves_git_hashes(tmp_path, monkeypatch)[source]¶
A CLI installed from a Git URL names its commit, with no live git and no archive.
- tests.test_version.test_version_dev_hash_assembly(module_version, expected)[source]¶
The git short hash is appended to dev releases only.
- tests.test_version.terminal_width(monkeypatch)[source]¶
Pin the width
VersionScreen.rendermeasures itself against.The function is patched, and
COLUMNSdeliberately left alone.shutil.get_terminal_sizereads that variable before it measures the terminal, so exporting it would pin the width just as well. It would also resize pytest’s own reports for as long as a narrow pin is held.The replacement accepts keyword arguments because pytest measures its progress line with
shutil.get_terminal_size(fallback=(80, 24)). A positional-only stand-in raisesTypeErrorthere and aborts the run with anINTERNALERROR. See #1934.
- tests.test_version.PLAIN_LOGO = ('###', '#', '#####')¶
A deliberately ragged mark: no caller should have to square its own artwork.
- tests.test_version.STYLED_LOGO = ('\x1b[32m###\x1b[0m', '\x1b[31m#\x1b[0m')¶
The same, carrying color, to prove the measurement discounts escape sequences.
- tests.test_version.test_screen_measures_its_logo(logo, width)[source]¶
The width comes from the artwork, never from the caller.
- tests.test_version.test_screen_squares_a_ragged_logo(logo)[source]¶
Every line is padded out to the widest, so the facts seat against a block.
Padding here rather than demanding it is what lets a caller hand over whatever its renderer produced. It cannot repair this afterwards even if it wanted to:
str.ljustcounts the escape sequences it cannot see, so on a styled line it silently does nothing.
- tests.test_version.test_screen_padding_closes_an_open_style()[source]¶
Padding after an unterminated style cannot inherit it.
A mark whose last run is left open would otherwise paint its own trailing blanks, and the gutter with them, dragging a background across the facts.
- tests.test_version.test_screen_facts_may_be_deferred()[source]¶
A callable is not called until the screen is drawn.
The point of accepting one: a CLI reporting something costly should pay for it when
--versionis asked for, not on every invocation that might have been.
- tests.test_version.test_screen_draws_a_callable_logo_once_per_render(terminal_width)[source]¶
Measuring and padding the mark share one drawing of it.
- tests.test_version.test_screen_sizes_its_label_column_to_the_longest_label()[source]¶
No declared width to outgrow: the column is measured, then gutter-separated.
- tests.test_version.test_screen_facts_replace_a_default_row_in_place()[source]¶
|over the defaults swaps a row where it sits, and appends a new one.
- tests.test_version.test_env_summary_joins_the_default_facts()[source]¶
The one-line summary says what the version screen’s default rows say.
- tests.test_version.test_screen_opens_on_a_blank_line_and_never_trails_whitespace(terminal_width)[source]¶
- tests.test_version.test_screen_seats_every_fact_at_one_column(terminal_width)[source]¶
The facts start at the same column on every line that carries one.
- tests.test_version.test_screen_uses_the_options_own_styles(terminal_width)[source]¶
The header is painted like the plain message, not like something new.
- tests.test_version.test_version_option_without_a_screen_is_unchanged(invoke)[source]¶
A CLI that never asks for a screen keeps the one-line message.
- tests.test_version.test_version_message_follows_the_theme(invoke, theme, expected)[source]¶
The message is painted from the active palette, not from a frozen copy.
A palette captured at import would print the program name bright white under every theme, which on a light terminal is white on white.
- tests.test_version.test_colors_reach_output(monkeypatch, color, stdout_is_a_tty, expected)[source]¶
autoreadssys.stdout, an explicit tri-state never looks at it.The stream half is the part worth pinning:
click.echoreaches stdout through a wrapper whose public accessor Click deprecates, and this resolution has to answer whatechoanswers without it.
- tests.test_version.test_sphinx_runner_resets_version_resolution()[source]¶
Each documented invocation resolves
--versionfor itself.The resolution chain opens on a stack walk and memoizes, which suits a CLI: one invocation, one process. A documentation build renders many commands in one process, and a render carrying no CLI frame (writing roff for a command tree, say) resolves the chain from a stack the walk cannot read. That answer would stand for the rest of the build, and a later
--versionexample would publish a screen with no version on it.This guards the mechanism, not the build: it plants a stale resolution by hand rather than reproducing the frame shape a real
sphinx-buildhas, so it holdsClickRunner.invoketo clearing the memo and says nothing about which renders would otherwise poison it.
- tests.test_version.test_reset_resolution_clears_every_memoized_field()[source]¶
reset_resolution()drops the whole chain, not a chosen few fields.
- tests.test_version.test_reset_resolution_keeps_a_pinned_field()[source]¶
A field pinned through
fields=is not a resolution, so it survives.An override lands in the instance dict under the name of the property it shadows, so a reset that goes by name alone erases it. That silently turns a pinned version into
Noneon every documented invocation.