assert_cmd-2.0.17/.cargo_vcs_info.json0000644000000001360000000000100132470ustar { "git": { "sha1": "c5c7f0f63bc70a412be41f721e72c0a465c89541" }, "path_in_vcs": "" }assert_cmd-2.0.17/Cargo.lock0000644000000236210000000000100112260ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "anstream" version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" [[package]] name = "anstyle-parse" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" dependencies = [ "anstyle", "windows-sys", ] [[package]] name = "assert_cmd" version = "2.0.17" dependencies = [ "anstream", "anstyle", "automod", "bstr", "doc-comment", "escargot", "libc", "predicates", "predicates-core", "predicates-tree", "wait-timeout", ] [[package]] name = "automod" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebb4bd301db2e2ca1f5be131c24eb8ebf2d9559bc3744419e93baf8ddea7e670" dependencies = [ "proc-macro2", "quote", "syn 2.0.57", ] [[package]] name = "bstr" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fca0852af221f458706eb0725c03e4ed6c46af9ac98e6a689d5e634215d594dd" dependencies = [ "memchr", "once_cell", "regex-automata", "serde", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "colorchoice" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "difflib" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[package]] name = "doc-comment" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "either" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" [[package]] name = "escargot" version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05a3ac187a16b5382fef8c69fd1bad123c67b7cf3932240a2d43dcdd32cded88" dependencies = [ "log", "once_cell", "serde", "serde_json", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" [[package]] name = "libc" version = "0.2.137" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" [[package]] name = "log" version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "once_cell" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" [[package]] name = "predicates" version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" dependencies = [ "anstyle", "difflib", "itertools", "predicates-core", ] [[package]] name = "predicates-core" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" [[package]] name = "predicates-tree" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032" dependencies = [ "predicates-core", "termtree", ] [[package]] name = "proc-macro2" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "regex-automata" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" [[package]] name = "ryu" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" [[package]] name = "serde" version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" dependencies = [ "proc-macro2", "quote", "syn 1.0.103", ] [[package]] name = "serde_json" version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "syn" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11a6ae1e52eb25aab8f3fb9fca13be982a373b8f1157ca14b897a825ba4a2d35" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "termtree" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b" [[package]] name = "unicode-ident" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" [[package]] name = "utf8parse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" assert_cmd-2.0.17/Cargo.toml0000644000000116710000000000100112530ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.74" name = "assert_cmd" version = "2.0.17" authors = [ "Pascal Hertleif ", "Ed Page ", ] build = "build.rs" include = [ "build.rs", "src/**/*", "Cargo.toml", "Cargo.lock", "LICENSE*", "README.md", "benches/**/*", "examples/**/*", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Test CLI Applications." homepage = "https://github.com/assert-rs/assert_cmd" documentation = "http://docs.rs/assert_cmd/" readme = "README.md" keywords = [ "cli", "test", "assert", "command", "duct", ] categories = ["development-tools::testing"] license = "MIT OR Apache-2.0" repository = "https://github.com/assert-rs/assert_cmd.git" [package.metadata.docs.rs] all-features = true rustdoc-args = [ "--cfg", "docsrs", "--generate-link-to-definition", ] [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" min = 1 replace = "{{version}}" search = "Unreleased" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = "...{{tag_name}}" search = '\.\.\.HEAD' [[package.metadata.release.pre-release-replacements]] file = "CHANGELOG.md" min = 1 replace = "{{date}}" search = "ReleaseDate" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = """ ## [Unreleased] - ReleaseDate """ search = "" [[package.metadata.release.pre-release-replacements]] exactly = 1 file = "CHANGELOG.md" replace = """ [Unreleased]: https://github.com/assert-rs/assert_cmd/compare/{{tag_name}}...HEAD""" search = "" [features] color = [ "dep:anstream", "predicates/color", ] color-auto = ["color"] [lib] name = "assert_cmd" path = "src/lib.rs" [[bin]] name = "bin_fixture" path = "src/bin/bin_fixture.rs" [[example]] name = "example_fixture" path = "examples/example_fixture.rs" [[example]] name = "failure" path = "examples/failure.rs" [dependencies.anstream] version = "0.6.7" optional = true [dependencies.anstyle] version = "1.0.0" [dependencies.bstr] version = "1.0.1" [dependencies.doc-comment] version = "0.3" [dependencies.predicates] version = "3.0.1" features = ["diff"] default-features = false [dependencies.predicates-core] version = "1.0.6" [dependencies.predicates-tree] version = "1.0.1" [dependencies.wait-timeout] version = "0.2.0" [dev-dependencies.automod] version = "1.0.14" [dev-dependencies.escargot] version = "0.5" [target."cfg(any())".dependencies.libc] version = "0.2.137" [lints.clippy] bool_assert_comparison = "allow" branches_sharing_code = "allow" checked_conversions = "warn" collapsible_else_if = "allow" create_dir = "warn" dbg_macro = "warn" debug_assert_with_mut_call = "warn" doc_markdown = "warn" empty_enum = "warn" enum_glob_use = "warn" expl_impl_clone_on_copy = "warn" explicit_deref_methods = "warn" explicit_into_iter_loop = "warn" fallible_impl_from = "warn" filter_map_next = "warn" flat_map_option = "warn" float_cmp_const = "warn" fn_params_excessive_bools = "warn" from_iter_instead_of_collect = "warn" if_same_then_else = "allow" implicit_clone = "warn" imprecise_flops = "warn" inconsistent_struct_constructor = "warn" inefficient_to_string = "warn" infinite_loop = "warn" invalid_upcast_comparisons = "warn" large_digit_groups = "warn" large_stack_arrays = "warn" large_types_passed_by_value = "warn" let_and_return = "allow" linkedlist = "warn" lossy_float_literal = "warn" macro_use_imports = "warn" mem_forget = "warn" mutex_integer = "warn" needless_continue = "warn" needless_for_each = "warn" negative_feature_names = "warn" path_buf_push_overwrite = "warn" ptr_as_ptr = "warn" rc_mutex = "warn" redundant_feature_names = "warn" ref_option_ref = "warn" rest_pat_in_fully_bound_structs = "warn" result_large_err = "allow" same_functions_in_if_condition = "warn" self_named_module_files = "warn" semicolon_if_nothing_returned = "warn" str_to_string = "warn" string_add = "warn" string_add_assign = "warn" string_lit_as_bytes = "warn" string_to_string = "warn" todo = "warn" trait_duplication_in_bounds = "warn" uninlined_format_args = "warn" verbose_file_reads = "warn" wildcard_imports = "warn" zero_sized_map_values = "warn" [lints.rust] unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" unused_lifetimes = "warn" unused_macro_rules = "warn" unused_qualifications = "warn" [lints.rust.rust_2018_idioms] level = "warn" priority = -1 assert_cmd-2.0.17/Cargo.toml.orig000064400000000000000000000077731046102023000147440ustar 00000000000000[workspace] resolver = "2" [workspace.package] repository = "https://github.com/assert-rs/assert_cmd.git" license = "MIT OR Apache-2.0" edition = "2021" rust-version = "1.74" # MSRV include = [ "build.rs", "src/**/*", "Cargo.toml", "Cargo.lock", "LICENSE*", "README.md", "benches/**/*", "examples/**/*" ] [workspace.lints.rust] rust_2018_idioms = { level = "warn", priority = -1 } unreachable_pub = "warn" unsafe_op_in_unsafe_fn = "warn" unused_lifetimes = "warn" unused_macro_rules = "warn" unused_qualifications = "warn" [workspace.lints.clippy] bool_assert_comparison = "allow" branches_sharing_code = "allow" checked_conversions = "warn" collapsible_else_if = "allow" create_dir = "warn" dbg_macro = "warn" debug_assert_with_mut_call = "warn" doc_markdown = "warn" empty_enum = "warn" enum_glob_use = "warn" expl_impl_clone_on_copy = "warn" explicit_deref_methods = "warn" explicit_into_iter_loop = "warn" fallible_impl_from = "warn" filter_map_next = "warn" flat_map_option = "warn" float_cmp_const = "warn" fn_params_excessive_bools = "warn" from_iter_instead_of_collect = "warn" if_same_then_else = "allow" implicit_clone = "warn" imprecise_flops = "warn" inconsistent_struct_constructor = "warn" inefficient_to_string = "warn" infinite_loop = "warn" invalid_upcast_comparisons = "warn" large_digit_groups = "warn" large_stack_arrays = "warn" large_types_passed_by_value = "warn" let_and_return = "allow" # sometimes good to name what you are returning linkedlist = "warn" lossy_float_literal = "warn" macro_use_imports = "warn" mem_forget = "warn" mutex_integer = "warn" needless_continue = "warn" needless_for_each = "warn" negative_feature_names = "warn" path_buf_push_overwrite = "warn" ptr_as_ptr = "warn" rc_mutex = "warn" redundant_feature_names = "warn" ref_option_ref = "warn" rest_pat_in_fully_bound_structs = "warn" result_large_err = "allow" same_functions_in_if_condition = "warn" self_named_module_files = "warn" semicolon_if_nothing_returned = "warn" str_to_string = "warn" string_add = "warn" string_add_assign = "warn" string_lit_as_bytes = "warn" string_to_string = "warn" todo = "warn" trait_duplication_in_bounds = "warn" uninlined_format_args = "warn" verbose_file_reads = "warn" wildcard_imports = "warn" zero_sized_map_values = "warn" [package] name = "assert_cmd" version = "2.0.17" description = "Test CLI Applications." authors = ["Pascal Hertleif ", "Ed Page "] homepage = "https://github.com/assert-rs/assert_cmd" documentation = "http://docs.rs/assert_cmd/" readme = "README.md" categories = ["development-tools::testing"] keywords = ["cli", "test", "assert", "command", "duct"] repository.workspace = true license.workspace = true edition.workspace = true rust-version.workspace = true include.workspace = true [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] [package.metadata.release] pre-release-replacements = [ {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/assert-rs/assert_cmd/compare/{{tag_name}}...HEAD", exactly=1}, ] [features] color = ["dep:anstream", "predicates/color"] color-auto = ["color"] [[bin]] name = "bin_fixture" [dependencies] predicates = { version = "3.0.1", default-features = false, features = ["diff"] } predicates-core = "1.0.6" predicates-tree = "1.0.1" doc-comment = "0.3" wait-timeout = "0.2.0" bstr = "1.0.1" anstream = { version = "0.6.7", optional = true } anstyle = "1.0.0" [target.'cfg(any())'.dependencies] libc = "0.2.137" # HACK: bad minimal dep in wait-timeout [dev-dependencies] escargot = "0.5" automod = "1.0.14" [lints] workspace = true assert_cmd-2.0.17/LICENSE-APACHE000064400000000000000000000261361046102023000137730ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. assert_cmd-2.0.17/LICENSE-MIT000064400000000000000000000020461046102023000134750ustar 00000000000000Copyright (c) Individual contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. assert_cmd-2.0.17/README.md000064400000000000000000000054621046102023000133250ustar 00000000000000# assert_cmd > **Assert `process::Command`** - Easy command initialization and assertions. [![Documentation](https://img.shields.io/badge/docs-master-blue.svg)][Documentation] ![License](https://img.shields.io/crates/l/assert_cmd.svg) [![Crates Status](https://img.shields.io/crates/v/assert_cmd.svg)][Crates.io] `assert_cmd` aims to simplify the process for doing integration testing of CLIs, including: - Finding your crate's binary to test - Assert on the result of your program's run. ## Example Here's a trivial example: ```rust,no_run use assert_cmd::Command; let mut cmd = Command::cargo_bin("bin_fixture").unwrap(); cmd.assert().success(); ``` See the [docs](http://docs.rs/assert_cmd) for more. ## Relevant crates Other crates that might be useful in testing command line programs. * [escargot][escargot] for more control over configuring the crate's binary. * [duct][duct] for orchestrating multiple processes. * or [commandspec] for easier writing of commands * [rexpect][rexpect] for testing interactive programs. * [`assert_fs`][assert_fs] for filesystem fixtures and assertions. * or [tempfile][tempfile] for scratchpad directories. * [dir-diff][dir-diff] for testing file side-effects. * [cross][cross] for cross-platform testing. [escargot]: http://docs.rs/escargot [rexpect]: https://crates.io/crates/rexpect [dir-diff]: https://crates.io/crates/dir-diff [tempfile]: https://crates.io/crates/tempfile [duct]: https://crates.io/crates/duct [assert_fs]: https://crates.io/crates/assert_fs [commandspec]: https://crates.io/crates/commandspec [cross]: https://github.com/cross-rs/cross ## License Licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) * MIT license ([LICENSE-MIT](LICENSE-MIT) or ) at your option. ## Testimonials fitzgen > assert_cmd is just such a pleasure to use every single time, I fall in love all over again > > bravo bravo WG-cli passcod > Running commands and dealing with output can be complex in many many ways, so assert_cmd smoothing that is excellent, very much welcome, and improves ergonomics significantly. volks73 > I have used [assert_cmd] in other projects and I am extremely pleased with it coreyja > [assert_cmd] pretty much IS my testing strategy so far, though my app under test is pretty small. > > This library has made it really easy to add some test coverage to my project, even when I am just learning how to write Rust! ## Contribution Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. [Crates.io]: https://crates.io/crates/assert_cmd [Documentation]: https://docs.rs/assert_cmd assert_cmd-2.0.17/build.rs000064400000000000000000000012051046102023000135020ustar 00000000000000use std::env; use std::fs; use std::io::Write; use std::path; fn main() { println!("cargo:rerun-if-changed=build.rs"); // env::ARCH doesn't include full triplet, and AFAIK there isn't a nicer way of getting the full triplet // (see lib.rs for the rest of this hack) let out = path::PathBuf::from(env::var_os("OUT_DIR").expect("run within cargo")) .join("current_target.txt"); let default_target = env::var("TARGET").expect("run as cargo build script"); let mut file = fs::File::create(out).expect("can write to OUT_DIR"); file.write_all(default_target.as_bytes()) .expect("can write to OUT_DIR"); } assert_cmd-2.0.17/examples/example_fixture.rs000064400000000000000000000013331046102023000174240ustar 00000000000000#![allow(clippy::exit)] use std::env; use std::error::Error; use std::io; use std::io::Write; use std::process; fn run() -> Result<(), Box> { if let Ok(text) = env::var("stdout") { println!("{text}"); } if let Ok(text) = env::var("stderr") { eprintln!("{text}"); } let code = env::var("exit") .ok() .map(|v| v.parse::()) .map(|r| r.map(Some)) .unwrap_or(Ok(None))? .unwrap_or(0); process::exit(code); } fn main() { let code = match run() { Ok(_) => 0, Err(ref e) => { write!(&mut io::stderr(), "{e}").expect("writing to stderr won't fail"); 1 } }; process::exit(code); } assert_cmd-2.0.17/examples/failure.rs000064400000000000000000000003501046102023000156500ustar 00000000000000#[allow(clippy::wildcard_imports)] // false positive use assert_cmd::prelude::*; use std::process::Command; fn main() { Command::new("ls") .args(["non-existent"]) .assert() .code(&[3, 42] as &[i32]); } assert_cmd-2.0.17/src/assert.rs000064400000000000000000000745151046102023000145110ustar 00000000000000//! [`std::process::Output`] assertions. use std::borrow::Cow; use std::error::Error; use std::fmt; use std::process; use std::str; #[cfg(feature = "color")] use anstream::panic; use predicates::str::PredicateStrExt; use predicates_tree::CaseTreeExt; use crate::output::output_fmt; use crate::output::DebugBytes; /// Assert the state of an [`Output`]. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// let mut cmd = Command::cargo_bin("bin_fixture") /// .unwrap(); /// cmd.assert() /// .success(); /// ``` /// /// [`Output`]: std::process::Output pub trait OutputAssertExt { /// Wrap with an interface for that provides assertions on the [`Output`]. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// let mut cmd = Command::cargo_bin("bin_fixture") /// .unwrap(); /// cmd.assert() /// .success(); /// ``` /// /// [`Output`]: std::process::Output fn assert(self) -> Assert; } impl OutputAssertExt for process::Output { fn assert(self) -> Assert { Assert::new(self) } } impl OutputAssertExt for &mut process::Command { fn assert(self) -> Assert { let output = match self.output() { Ok(output) => output, Err(err) => { panic!("Failed to spawn {self:?}: {err}"); } }; Assert::new(output).append_context("command", format!("{self:?}")) } } /// Assert the state of an [`Output`]. /// /// Create an `Assert` through the [`OutputAssertExt`] trait. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// let mut cmd = Command::cargo_bin("bin_fixture") /// .unwrap(); /// cmd.assert() /// .success(); /// ``` /// /// [`Output`]: std::process::Output pub struct Assert { output: process::Output, context: Vec<(&'static str, Box)>, } impl Assert { /// Create an `Assert` for a given [`Output`]. /// /// [`Output`]: std::process::Output pub fn new(output: process::Output) -> Self { Self { output, context: vec![], } } fn into_error(self, reason: AssertReason) -> AssertError { AssertError { assert: self, reason, } } /// Clarify failures with additional context. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .assert() /// .append_context("main", "no args") /// .success(); /// ``` pub fn append_context(mut self, name: &'static str, context: D) -> Self where D: fmt::Display + Send + Sync + 'static, { self.context.push((name, Box::new(context))); self } /// Access the contained [`Output`]. /// /// [`Output`]: std::process::Output pub fn get_output(&self) -> &process::Output { &self.output } /// Ensure the command succeeded. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .assert() /// .success(); /// ``` #[track_caller] pub fn success(self) -> Self { self.try_success().unwrap_or_else(AssertError::panic) } /// `try_` variant of [`Assert::success`]. pub fn try_success(self) -> AssertResult { if !self.output.status.success() { let actual_code = self.output.status.code(); return Err(self.into_error(AssertReason::UnexpectedFailure { actual_code })); } Ok(self) } /// Ensure the command failed. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("exit", "1") /// .assert() /// .failure(); /// ``` #[track_caller] pub fn failure(self) -> Self { self.try_failure().unwrap_or_else(AssertError::panic) } /// Variant of [`Assert::failure`] that returns an [`AssertResult`]. pub fn try_failure(self) -> AssertResult { if self.output.status.success() { return Err(self.into_error(AssertReason::UnexpectedSuccess)); } Ok(self) } /// Ensure the command aborted before returning a code. #[track_caller] pub fn interrupted(self) -> Self { self.try_interrupted().unwrap_or_else(AssertError::panic) } /// Variant of [`Assert::interrupted`] that returns an [`AssertResult`]. pub fn try_interrupted(self) -> AssertResult { if self.output.status.code().is_some() { return Err(self.into_error(AssertReason::UnexpectedCompletion)); } Ok(self) } /// Ensure the command returned the expected code. /// /// This uses [`IntoCodePredicate`] to provide short-hands for common cases. /// /// See [`predicates`] for more predicates. /// /// # Examples /// /// Accepting a predicate: /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// use predicates::prelude::*; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("exit", "42") /// .assert() /// .code(predicate::eq(42)); /// ``` /// /// Accepting an exit code: /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("exit", "42") /// .assert() /// .code(42); /// ``` /// /// Accepting multiple exit codes: /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("exit", "42") /// .assert() /// .code(&[2, 42] as &[i32]); /// ``` /// #[track_caller] pub fn code(self, pred: I) -> Self where I: IntoCodePredicate

, P: predicates_core::Predicate, { self.try_code(pred).unwrap_or_else(AssertError::panic) } /// Variant of [`Assert::code`] that returns an [`AssertResult`]. pub fn try_code(self, pred: I) -> AssertResult where I: IntoCodePredicate

, P: predicates_core::Predicate, { self.code_impl(&pred.into_code()) } fn code_impl(self, pred: &dyn predicates_core::Predicate) -> AssertResult { let actual_code = if let Some(actual_code) = self.output.status.code() { actual_code } else { return Err(self.into_error(AssertReason::CommandInterrupted)); }; if let Some(case) = pred.find_case(false, &actual_code) { return Err(self.into_error(AssertReason::UnexpectedReturnCode { case_tree: CaseTree(case.tree()), })); } Ok(self) } /// Ensure the command wrote the expected data to `stdout`. /// /// This uses [`IntoOutputPredicate`] to provide short-hands for common cases. /// /// See [`predicates`] for more predicates. /// /// # Examples /// /// Accepting a bytes predicate: /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// use predicates::prelude::*; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("stdout", "hello") /// .env("stderr", "world") /// .assert() /// .stdout(predicate::eq(b"hello\n" as &[u8])); /// ``` /// /// Accepting a `str` predicate: /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// use predicates::prelude::*; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("stdout", "hello") /// .env("stderr", "world") /// .assert() /// .stdout(predicate::str::diff("hello\n")); /// ``` /// /// Accepting bytes: /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("stdout", "hello") /// .env("stderr", "world") /// .assert() /// .stdout(b"hello\n" as &[u8]); /// ``` /// /// Accepting a `str`: /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("stdout", "hello") /// .env("stderr", "world") /// .assert() /// .stdout("hello\n"); /// ``` /// #[track_caller] pub fn stdout(self, pred: I) -> Self where I: IntoOutputPredicate

, P: predicates_core::Predicate<[u8]>, { self.try_stdout(pred).unwrap_or_else(AssertError::panic) } /// Variant of [`Assert::stdout`] that returns an [`AssertResult`]. pub fn try_stdout(self, pred: I) -> AssertResult where I: IntoOutputPredicate

, P: predicates_core::Predicate<[u8]>, { self.stdout_impl(&pred.into_output()) } fn stdout_impl(self, pred: &dyn predicates_core::Predicate<[u8]>) -> AssertResult { { let actual = &self.output.stdout; if let Some(case) = pred.find_case(false, actual) { return Err(self.into_error(AssertReason::UnexpectedStdout { case_tree: CaseTree(case.tree()), })); } } Ok(self) } /// Ensure the command wrote the expected data to `stderr`. /// /// This uses [`IntoOutputPredicate`] to provide short-hands for common cases. /// /// See [`predicates`] for more predicates. /// /// # Examples /// /// Accepting a bytes predicate: /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// use predicates::prelude::*; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("stdout", "hello") /// .env("stderr", "world") /// .assert() /// .stderr(predicate::eq(b"world\n" as &[u8])); /// ``` /// /// Accepting a `str` predicate: /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// use predicates::prelude::*; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("stdout", "hello") /// .env("stderr", "world") /// .assert() /// .stderr(predicate::str::diff("world\n")); /// ``` /// /// Accepting bytes: /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("stdout", "hello") /// .env("stderr", "world") /// .assert() /// .stderr(b"world\n" as &[u8]); /// ``` /// /// Accepting a `str`: /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("stdout", "hello") /// .env("stderr", "world") /// .assert() /// .stderr("world\n"); /// ``` /// #[track_caller] pub fn stderr(self, pred: I) -> Self where I: IntoOutputPredicate

, P: predicates_core::Predicate<[u8]>, { self.try_stderr(pred).unwrap_or_else(AssertError::panic) } /// Variant of [`Assert::stderr`] that returns an [`AssertResult`]. pub fn try_stderr(self, pred: I) -> AssertResult where I: IntoOutputPredicate

, P: predicates_core::Predicate<[u8]>, { self.stderr_impl(&pred.into_output()) } fn stderr_impl(self, pred: &dyn predicates_core::Predicate<[u8]>) -> AssertResult { { let actual = &self.output.stderr; if let Some(case) = pred.find_case(false, actual) { return Err(self.into_error(AssertReason::UnexpectedStderr { case_tree: CaseTree(case.tree()), })); } } Ok(self) } } impl fmt::Display for Assert { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let palette = crate::Palette::color(); for (name, context) in &self.context { writeln!(f, "{:#}=`{:#}`", palette.key(name), palette.value(context))?; } output_fmt(&self.output, f) } } impl fmt::Debug for Assert { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Assert") .field("output", &self.output) .finish() } } /// Used by [`Assert::code`] to convert `Self` into the needed /// [`predicates_core::Predicate`]. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// use predicates::prelude::*; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("exit", "42") /// .assert() /// .code(predicate::eq(42)); /// /// // which can be shortened to: /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("exit", "42") /// .assert() /// .code(42); /// ``` pub trait IntoCodePredicate

where P: predicates_core::Predicate, { /// The type of the predicate being returned. type Predicate; /// Convert to a predicate for testing a program's exit code. fn into_code(self) -> P; } impl

IntoCodePredicate

for P where P: predicates_core::Predicate, { type Predicate = P; fn into_code(self) -> Self::Predicate { self } } /// Keep `predicates` concrete Predicates out of our public API. /// [`predicates_core::Predicate`] used by [`IntoCodePredicate`] for code. /// /// # Example /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("exit", "42") /// .assert() /// .code(42); /// ``` #[derive(Debug)] pub struct EqCodePredicate(predicates::ord::EqPredicate); impl EqCodePredicate { pub(crate) fn new(value: i32) -> Self { let pred = predicates::ord::eq(value); EqCodePredicate(pred) } } impl predicates_core::reflection::PredicateReflection for EqCodePredicate { fn parameters<'a>( &'a self, ) -> Box> + 'a> { self.0.parameters() } /// Nested `Predicate`s of the current `Predicate`. fn children<'a>( &'a self, ) -> Box> + 'a> { self.0.children() } } impl predicates_core::Predicate for EqCodePredicate { fn eval(&self, item: &i32) -> bool { self.0.eval(item) } fn find_case<'a>( &'a self, expected: bool, variable: &i32, ) -> Option> { self.0.find_case(expected, variable) } } impl fmt::Display for EqCodePredicate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl IntoCodePredicate for i32 { type Predicate = EqCodePredicate; fn into_code(self) -> Self::Predicate { Self::Predicate::new(self) } } /// Keep `predicates` concrete Predicates out of our public API. /// [`predicates_core::Predicate`] used by [`IntoCodePredicate`] for iterables of codes. /// /// # Example /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("exit", "42") /// .assert() /// .code(&[2, 42] as &[i32]); /// ``` #[derive(Debug)] pub struct InCodePredicate(predicates::iter::InPredicate); impl InCodePredicate { pub(crate) fn new>(value: I) -> Self { let pred = predicates::iter::in_iter(value); InCodePredicate(pred) } } impl predicates_core::reflection::PredicateReflection for InCodePredicate { fn parameters<'a>( &'a self, ) -> Box> + 'a> { self.0.parameters() } /// Nested `Predicate`s of the current `Predicate`. fn children<'a>( &'a self, ) -> Box> + 'a> { self.0.children() } } impl predicates_core::Predicate for InCodePredicate { fn eval(&self, item: &i32) -> bool { self.0.eval(item) } fn find_case<'a>( &'a self, expected: bool, variable: &i32, ) -> Option> { self.0.find_case(expected, variable) } } impl fmt::Display for InCodePredicate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl IntoCodePredicate for Vec { type Predicate = InCodePredicate; fn into_code(self) -> Self::Predicate { Self::Predicate::new(self) } } impl IntoCodePredicate for &'static [i32] { type Predicate = InCodePredicate; fn into_code(self) -> Self::Predicate { Self::Predicate::new(self.iter().cloned()) } } /// Used by [`Assert::stdout`] and [`Assert::stderr`] to convert Self /// into the needed [`predicates_core::Predicate<[u8]>`]. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// use predicates::prelude::*; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("stdout", "hello") /// .env("stderr", "world") /// .assert() /// .stdout(predicate::str::diff("hello\n").from_utf8()); /// /// // which can be shortened to: /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("stdout", "hello") /// .env("stderr", "world") /// .assert() /// .stdout("hello\n"); /// ``` pub trait IntoOutputPredicate

where P: predicates_core::Predicate<[u8]>, { /// The type of the predicate being returned. type Predicate; /// Convert to a predicate for testing a path. fn into_output(self) -> P; } impl

IntoOutputPredicate

for P where P: predicates_core::Predicate<[u8]>, { type Predicate = P; fn into_output(self) -> Self::Predicate { self } } /// Keep `predicates` concrete Predicates out of our public API. /// [`predicates_core::Predicate`] used by [`IntoOutputPredicate`] for bytes. /// /// # Example /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("stdout", "hello") /// .env("stderr", "world") /// .assert() /// .stderr(b"world\n" as &[u8]); /// ``` #[derive(Debug)] pub struct BytesContentOutputPredicate(Cow<'static, [u8]>); impl BytesContentOutputPredicate { pub(crate) fn new(value: &'static [u8]) -> Self { BytesContentOutputPredicate(Cow::from(value)) } pub(crate) fn from_vec(value: Vec) -> Self { BytesContentOutputPredicate(Cow::from(value)) } } impl predicates_core::reflection::PredicateReflection for BytesContentOutputPredicate {} impl predicates_core::Predicate<[u8]> for BytesContentOutputPredicate { fn eval(&self, item: &[u8]) -> bool { self.0.as_ref() == item } fn find_case( &self, expected: bool, variable: &[u8], ) -> Option> { let actual = self.eval(variable); if expected == actual { Some(predicates_core::reflection::Case::new(Some(self), actual)) } else { None } } } impl fmt::Display for BytesContentOutputPredicate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { predicates::ord::eq(self.0.as_ref()).fmt(f) } } impl IntoOutputPredicate for Vec { type Predicate = BytesContentOutputPredicate; fn into_output(self) -> Self::Predicate { Self::Predicate::from_vec(self) } } impl IntoOutputPredicate for &'static [u8] { type Predicate = BytesContentOutputPredicate; fn into_output(self) -> Self::Predicate { Self::Predicate::new(self) } } /// Keep `predicates` concrete Predicates out of our public API. /// [`predicates_core::Predicate`] used by [`IntoOutputPredicate`] for [`str`]. /// /// # Example /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("stdout", "hello") /// .env("stderr", "world") /// .assert() /// .stderr("world\n"); /// ``` /// /// [`str`]: https://doc.rust-lang.org/std/primitive.str.html #[derive(Debug, Clone)] pub struct StrContentOutputPredicate( predicates::str::Utf8Predicate, ); impl StrContentOutputPredicate { pub(crate) fn from_str(value: &'static str) -> Self { let pred = predicates::str::diff(value).from_utf8(); StrContentOutputPredicate(pred) } pub(crate) fn from_string(value: String) -> Self { let pred = predicates::str::diff(value).from_utf8(); StrContentOutputPredicate(pred) } } impl predicates_core::reflection::PredicateReflection for StrContentOutputPredicate { fn parameters<'a>( &'a self, ) -> Box> + 'a> { self.0.parameters() } /// Nested `Predicate`s of the current `Predicate`. fn children<'a>( &'a self, ) -> Box> + 'a> { self.0.children() } } impl predicates_core::Predicate<[u8]> for StrContentOutputPredicate { fn eval(&self, item: &[u8]) -> bool { self.0.eval(item) } fn find_case<'a>( &'a self, expected: bool, variable: &[u8], ) -> Option> { self.0.find_case(expected, variable) } } impl fmt::Display for StrContentOutputPredicate { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl IntoOutputPredicate for String { type Predicate = StrContentOutputPredicate; fn into_output(self) -> Self::Predicate { Self::Predicate::from_string(self) } } impl IntoOutputPredicate for &'static str { type Predicate = StrContentOutputPredicate; fn into_output(self) -> Self::Predicate { Self::Predicate::from_str(self) } } // Keep `predicates` concrete Predicates out of our public API. /// [`predicates_core::Predicate`] used by [`IntoOutputPredicate`] for /// [`Predicate`][predicates_core::Predicate]. /// /// # Example /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// use predicates::prelude::*; /// /// Command::cargo_bin("bin_fixture") /// .unwrap() /// .env("stdout", "hello") /// .env("stderr", "world") /// .assert() /// .stderr(predicate::str::diff("world\n")); /// ``` #[derive(Debug, Clone)] pub struct StrOutputPredicate>( predicates::str::Utf8Predicate

, ); impl

StrOutputPredicate

where P: predicates_core::Predicate, { pub(crate) fn new(pred: P) -> Self { let pred = pred.from_utf8(); StrOutputPredicate(pred) } } impl

predicates_core::reflection::PredicateReflection for StrOutputPredicate

where P: predicates_core::Predicate, { fn parameters<'a>( &'a self, ) -> Box> + 'a> { self.0.parameters() } /// Nested `Predicate`s of the current `Predicate`. fn children<'a>( &'a self, ) -> Box> + 'a> { self.0.children() } } impl

predicates_core::Predicate<[u8]> for StrOutputPredicate

where P: predicates_core::Predicate, { fn eval(&self, item: &[u8]) -> bool { self.0.eval(item) } fn find_case<'a>( &'a self, expected: bool, variable: &[u8], ) -> Option> { self.0.find_case(expected, variable) } } impl

fmt::Display for StrOutputPredicate

where P: predicates_core::Predicate, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl

IntoOutputPredicate> for P where P: predicates_core::Predicate, { type Predicate = StrOutputPredicate

; fn into_output(self) -> Self::Predicate { Self::Predicate::new(self) } } /// [`Assert`] represented as a [`Result`]. /// /// Produced by the `try_` variants the [`Assert`] methods. /// /// # Example /// /// ```rust /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// let result = Command::new("echo") /// .assert() /// .try_success(); /// assert!(result.is_ok()); /// ``` /// /// [`Result`]: std::result::Result pub type AssertResult = Result; /// [`Assert`] error (see [`AssertResult`]). #[derive(Debug)] pub struct AssertError { assert: Assert, reason: AssertReason, } #[derive(Debug)] enum AssertReason { UnexpectedFailure { actual_code: Option }, UnexpectedSuccess, UnexpectedCompletion, CommandInterrupted, UnexpectedReturnCode { case_tree: CaseTree }, UnexpectedStdout { case_tree: CaseTree }, UnexpectedStderr { case_tree: CaseTree }, } impl AssertError { #[track_caller] fn panic(self) -> T { panic!("{}", self) } /// Returns the [`Assert`] wrapped into the [`Result`] produced by /// the `try_` variants of the [`Assert`] methods. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// use predicates::prelude::*; /// /// let result = Command::new("echo") /// .assert(); /// /// match result.try_success() { /// Ok(assert) => { /// assert.stdout(predicate::eq(b"Success\n" as &[u8])); /// } /// Err(err) => { /// err.assert().stdout(predicate::eq(b"Err but some specific output you might want to check\n" as &[u8])); /// } /// } /// ``` pub fn assert(self) -> Assert { self.assert } } impl Error for AssertError {} impl fmt::Display for AssertError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &self.reason { AssertReason::UnexpectedFailure { actual_code } => writeln!( f, "Unexpected failure.\ncode={}\nstderr=```{}```", actual_code .map(|actual_code| actual_code.to_string()) .unwrap_or_else(|| "".to_owned()), DebugBytes::new(&self.assert.output.stderr), ), AssertReason::UnexpectedSuccess => { writeln!(f, "Unexpected success") } AssertReason::UnexpectedCompletion => { writeln!(f, "Unexpected completion") } AssertReason::CommandInterrupted => { writeln!(f, "Command interrupted") } AssertReason::UnexpectedReturnCode { case_tree } => { writeln!(f, "Unexpected return code, failed {case_tree}") } AssertReason::UnexpectedStdout { case_tree } => { writeln!(f, "Unexpected stdout, failed {case_tree}") } AssertReason::UnexpectedStderr { case_tree } => { writeln!(f, "Unexpected stderr, failed {case_tree}") } }?; write!(f, "{}", self.assert) } } struct CaseTree(predicates_tree::CaseTree); impl fmt::Display for CaseTree { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ::fmt(&self.0, f) } } // Work around `Debug` not being implemented for `predicates_tree::CaseTree`. impl fmt::Debug for CaseTree { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { ::fmt(&self.0, f) } } #[cfg(test)] mod test { use super::*; use predicates::prelude::*; // Since IntoCodePredicate exists solely for conversion, test it under that scenario to ensure // it works as expected. fn convert_code(pred: I) -> P where I: IntoCodePredicate

, P: Predicate, { pred.into_code() } #[test] fn into_code_from_pred() { let pred = convert_code(predicate::eq(10)); assert!(pred.eval(&10)); } #[test] fn into_code_from_i32() { let pred = convert_code(10); assert!(pred.eval(&10)); } #[test] fn into_code_from_vec() { let pred = convert_code(vec![3, 10]); assert!(pred.eval(&10)); } #[test] fn into_code_from_array() { let pred = convert_code(&[3, 10] as &[i32]); assert!(pred.eval(&10)); } // Since IntoOutputPredicate exists solely for conversion, test it under that scenario to ensure // it works as expected. fn convert_output(pred: I) -> P where I: IntoOutputPredicate

, P: Predicate<[u8]>, { pred.into_output() } #[test] fn into_output_from_pred() { let pred = convert_output(predicate::eq(b"Hello" as &[u8])); assert!(pred.eval(b"Hello" as &[u8])); } #[test] fn into_output_from_bytes() { let pred = convert_output(b"Hello" as &[u8]); assert!(pred.eval(b"Hello" as &[u8])); } #[test] fn into_output_from_vec() { let pred = convert_output(vec![b'H', b'e', b'l', b'l', b'o']); assert!(pred.eval(b"Hello" as &[u8])); } #[test] fn into_output_from_str() { let pred = convert_output("Hello"); assert!(pred.eval(b"Hello" as &[u8])); } } assert_cmd-2.0.17/src/bin/bin_fixture.rs000064400000000000000000000015701046102023000162650ustar 00000000000000#![allow(clippy::exit)] use std::env; use std::error::Error; use std::io; use std::io::Write; use std::process; fn run() -> Result<(), Box> { if let Ok(text) = env::var("stdout") { println!("{text}"); } if let Ok(text) = env::var("stderr") { eprintln!("{text}"); } if let Some(timeout) = env::var("sleep").ok().and_then(|s| s.parse().ok()) { std::thread::sleep(std::time::Duration::from_secs(timeout)); } let code = env::var("exit") .ok() .map(|v| v.parse::()) .map(|r| r.map(Some)) .unwrap_or(Ok(None))? .unwrap_or(0); process::exit(code); } fn main() { let code = match run() { Ok(_) => 0, Err(ref e) => { write!(&mut io::stderr(), "{e}").expect("writing to stderr won't fail"); 1 } }; process::exit(code); } assert_cmd-2.0.17/src/cargo.rs000064400000000000000000000156611046102023000143000ustar 00000000000000//! Simplify running `bin`s in a Cargo project. //! //! [`CommandCargoExt`] is an extension trait for [`Command`] to easily launch a crate's //! binaries. //! //! # Examples //! //! Simple case: //! //! ```rust,no_run //! use assert_cmd::prelude::*; //! //! use std::process::Command; //! //! let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")) //! .unwrap(); //! let output = cmd.unwrap(); //! ``` //! //! # Limitations //! //! - Only works within the context of integration tests. See [`escargot`] for a more //! flexible API. //! - Only reuses your existing feature flags, targets, or build mode. //! - Only works with cargo binaries (`cargo test` ensures they are built). //! //! If you run into these limitations, we recommend trying out [`escargot`]: //! //! ```rust,no_run //! use assert_cmd::prelude::*; //! //! use std::process::Command; //! //! let bin_under_test = escargot::CargoBuild::new() //! .bin("bin_fixture") //! .current_release() //! .current_target() //! .run() //! .unwrap(); //! let mut cmd = bin_under_test.command(); //! let output = cmd.unwrap(); //! println!("{:?}", output); //! ``` //! //! Notes: //! - There is a [noticeable per-call overhead][cargo-overhead] for `CargoBuild`. We recommend //! caching the binary location (`.path()` instead of `.command()`) with [`lazy_static`]. //! - `.current_target()` improves platform coverage at the cost of [slower test runs if you don't //! explicitly pass `--target ` on the command line][first-call]. //! //! [`lazy_static`]: https://crates.io/crates/lazy_static //! [`Command`]: std::process::Command //! [`escargot`]: https://crates.io/crates/escargot //! [cargo-overhead]: https://github.com/assert-rs/assert_cmd/issues/6 //! [first-call]: https://github.com/assert-rs/assert_cmd/issues/57 use std::env; use std::error::Error; use std::fmt; use std::path; use std::process; #[doc(inline)] pub use crate::cargo_bin; /// Create a [`Command`] for a `bin` in the Cargo project. /// /// `CommandCargoExt` is an extension trait for [`Command`][std::process::Command] to easily launch a crate's /// binaries. /// /// See the [`cargo` module documentation][super::cargo] for caveats and workarounds. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")) /// .unwrap(); /// let output = cmd.unwrap(); /// println!("{:?}", output); /// ``` /// /// [`Command`]: std::process::Command pub trait CommandCargoExt where Self: Sized, { /// Create a [`Command`] to run a specific binary of the current crate. /// /// See the [`cargo` module documentation][crate::cargo] for caveats and workarounds. /// /// The [`Command`] created with this method may run the binary through a runner, as configured /// in the `CARGO_TARGET__RUNNER` environment variable. This is useful for running /// binaries that can't be launched directly, such as cross-compiled binaries. When using /// this method with [cross](https://github.com/cross-rs/cross), no extra configuration is /// needed. /// /// **NOTE:** Prefer [`cargo_bin!`] as this makes assumptions about cargo /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")) /// .unwrap(); /// let output = cmd.unwrap(); /// println!("{:?}", output); /// ``` /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// let mut cmd = Command::cargo_bin("bin_fixture") /// .unwrap(); /// let output = cmd.unwrap(); /// println!("{:?}", output); /// ``` /// /// [`Command`]: std::process::Command fn cargo_bin>(name: S) -> Result; } impl CommandCargoExt for crate::cmd::Command { fn cargo_bin>(name: S) -> Result { crate::cmd::Command::cargo_bin(name) } } impl CommandCargoExt for process::Command { fn cargo_bin>(name: S) -> Result { cargo_bin_cmd(name) } } pub(crate) fn cargo_bin_cmd>(name: S) -> Result { let path = cargo_bin(name); if path.is_file() { if let Some(runner) = cargo_runner() { let mut cmd = process::Command::new(&runner[0]); cmd.args(&runner[1..]).arg(path); Ok(cmd) } else { Ok(process::Command::new(path)) } } else { Err(CargoError::with_cause(NotFoundError { path })) } } pub(crate) fn cargo_runner() -> Option> { let runner_env = format!( "CARGO_TARGET_{}_RUNNER", CURRENT_TARGET.replace('-', "_").to_uppercase() ); let runner = env::var(runner_env).ok()?; Some(runner.split(' ').map(str::to_string).collect()) } /// Error when finding crate binary. #[derive(Debug)] pub struct CargoError { cause: Option>, } impl CargoError { /// Wrap the underlying error for passing up. pub fn with_cause(cause: E) -> Self where E: Error + Send + Sync + 'static, { let cause = Box::new(cause); Self { cause: Some(cause) } } } impl Error for CargoError {} impl fmt::Display for CargoError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(ref cause) = self.cause { writeln!(f, "Cause: {cause}")?; } Ok(()) } } /// Error when finding crate binary. #[derive(Debug)] struct NotFoundError { path: path::PathBuf, } impl Error for NotFoundError {} impl fmt::Display for NotFoundError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!(f, "Cargo command not found: {}", self.path.display()) } } // Adapted from // https://github.com/rust-lang/cargo/blob/485670b3983b52289a2f353d589c57fae2f60f82/tests/testsuite/support/mod.rs#L507 fn target_dir() -> path::PathBuf { env::current_exe() .ok() .map(|mut path| { path.pop(); if path.ends_with("deps") { path.pop(); } path }) .expect("this should only be used where a `current_exe` can be set") } /// Look up the path to a cargo-built binary within an integration test. /// /// **NOTE:** Prefer [`cargo_bin!`] as this makes assumptions about cargo pub fn cargo_bin>(name: S) -> path::PathBuf { cargo_bin_str(name.as_ref()) } fn cargo_bin_str(name: &str) -> path::PathBuf { let env_var = format!("CARGO_BIN_EXE_{name}"); env::var_os(env_var) .map(|p| p.into()) .unwrap_or_else(|| target_dir().join(format!("{}{}", name, env::consts::EXE_SUFFIX))) } /// The current process' target triplet. const CURRENT_TARGET: &str = include_str!(concat!(env!("OUT_DIR"), "/current_target.txt")); assert_cmd-2.0.17/src/cmd.rs000064400000000000000000000455041046102023000137470ustar 00000000000000//! [`std::process::Command`] customized for testing. use std::ffi; use std::io; use std::io::{Read, Write}; use std::ops::Deref; use std::path; use std::process; use crate::assert::Assert; use crate::assert::OutputAssertExt; use crate::output::DebugBuffer; use crate::output::DebugBytes; use crate::output::OutputError; use crate::output::OutputOkExt; use crate::output::OutputResult; /// [`std::process::Command`] customized for testing. #[derive(Debug)] pub struct Command { cmd: process::Command, stdin: Option, timeout: Option, } impl Command { /// Constructs a new `Command` from a `std` `Command`. pub fn from_std(cmd: process::Command) -> Self { Self { cmd, stdin: None, timeout: None, } } /// Create a `Command` to run a specific binary of the current crate. /// /// See the [`cargo` module documentation][crate::cargo] for caveats and workarounds. /// /// **NOTE:** Prefer [`cargo_bin!`][crate::cargo::cargo_bin!] as this makes assumptions about cargo /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::Command; /// /// let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")) /// .unwrap(); /// let output = cmd.unwrap(); /// println!("{:?}", output); /// ``` /// /// ```rust,no_run /// use assert_cmd::Command; /// /// let mut cmd = Command::cargo_bin("bin_fixture") /// .unwrap(); /// let output = cmd.unwrap(); /// println!("{:?}", output); /// ``` /// pub fn cargo_bin>(name: S) -> Result { let cmd = crate::cargo::cargo_bin_cmd(name)?; Ok(Self::from_std(cmd)) } /// Write `buffer` to `stdin` when the `Command` is run. /// /// # Examples /// /// ```rust /// use assert_cmd::Command; /// /// let mut cmd = Command::new("cat") /// .arg("-et") /// .write_stdin("42") /// .assert() /// .stdout("42"); /// ``` pub fn write_stdin(&mut self, buffer: S) -> &mut Self where S: Into>, { self.stdin = Some(bstr::BString::from(buffer.into())); self } /// Error out if a timeout is reached /// /// ```rust,no_run /// use assert_cmd::Command; /// /// let assert = Command::cargo_bin("bin_fixture") /// .unwrap() /// .timeout(std::time::Duration::from_secs(1)) /// .env("sleep", "100") /// .assert(); /// assert.failure(); /// ``` pub fn timeout(&mut self, timeout: std::time::Duration) -> &mut Self { self.timeout = Some(timeout); self } /// Write `path`s content to `stdin` when the `Command` is run. /// /// Paths are relative to the [`env::current_dir`][env_current_dir] and not /// [`Command::current_dir`][Command_current_dir]. /// /// [env_current_dir]: std::env::current_dir() /// [Command_current_dir]: std::process::Command::current_dir() pub fn pipe_stdin

(&mut self, file: P) -> io::Result<&mut Self> where P: AsRef, { let buffer = std::fs::read(file)?; Ok(self.write_stdin(buffer)) } /// Run a `Command`, returning an [`OutputResult`]. /// /// # Examples /// /// ```rust /// use assert_cmd::Command; /// /// let result = Command::new("echo") /// .args(&["42"]) /// .ok(); /// assert!(result.is_ok()); /// ``` /// pub fn ok(&mut self) -> OutputResult { OutputOkExt::ok(self) } /// Run a `Command`, unwrapping the [`OutputResult`]. /// /// # Examples /// /// ```rust /// use assert_cmd::Command; /// /// let output = Command::new("echo") /// .args(&["42"]) /// .unwrap(); /// ``` /// pub fn unwrap(&mut self) -> process::Output { OutputOkExt::unwrap(self) } /// Run a `Command`, unwrapping the error in the [`OutputResult`]. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::Command; /// /// let err = Command::new("a-command") /// .args(&["--will-fail"]) /// .unwrap_err(); /// ``` /// /// [Output]: std::process::Output pub fn unwrap_err(&mut self) -> OutputError { OutputOkExt::unwrap_err(self) } /// Run a `Command` and make assertions on the [`Output`]. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::Command; /// /// let mut cmd = Command::cargo_bin("bin_fixture") /// .unwrap() /// .assert() /// .success(); /// ``` /// /// [`Output`]: std::process::Output pub fn assert(&mut self) -> Assert { OutputAssertExt::assert(self) } } /// Mirror [`std::process::Command`]'s API impl Command { /// Constructs a new `Command` for launching the program at /// path `program`, with the following default configuration: /// /// * No arguments to the program /// * Inherit the current process's environment /// * Inherit the current process's working directory /// * Inherit stdin/stdout/stderr for `spawn` or `status`, but create pipes for `output` /// /// Builder methods are provided to change these defaults and /// otherwise configure the process. /// /// If `program` is not an absolute path, the `PATH` will be searched in /// an OS-defined way. /// /// The search path to be used may be controlled by setting the /// `PATH` environment variable on the Command, /// but this has some implementation limitations on Windows /// (see issue #37519). /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use assert_cmd::Command; /// /// Command::new("sh").unwrap(); /// ``` pub fn new>(program: S) -> Self { let cmd = process::Command::new(program); Self::from_std(cmd) } /// Adds an argument to pass to the program. /// /// Only one argument can be passed per use. So instead of: /// /// ```no_run /// # assert_cmd::Command::new("sh") /// .arg("-C /path/to/repo") /// # ; /// ``` /// /// usage would be: /// /// ```no_run /// # assert_cmd::Command::new("sh") /// .arg("-C") /// .arg("/path/to/repo") /// # ; /// ``` /// /// To pass multiple arguments see [`args`]. /// /// [`args`]: Command::args() /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use assert_cmd::Command; /// /// Command::new("ls") /// .arg("-l") /// .arg("-a") /// .unwrap(); /// ``` pub fn arg>(&mut self, arg: S) -> &mut Self { self.cmd.arg(arg); self } /// Adds multiple arguments to pass to the program. /// /// To pass a single argument see [`arg`]. /// /// [`arg`]: Command::arg() /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use assert_cmd::Command; /// /// Command::new("ls") /// .args(&["-l", "-a"]) /// .unwrap(); /// ``` pub fn args(&mut self, args: I) -> &mut Self where I: IntoIterator, S: AsRef, { self.cmd.args(args); self } /// Inserts or updates an environment variable mapping. /// /// Note that environment variable names are case-insensitive (but case-preserving) on Windows, /// and case-sensitive on all other platforms. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use assert_cmd::Command; /// /// Command::new("ls") /// .env("PATH", "/bin") /// .unwrap_err(); /// ``` pub fn env(&mut self, key: K, val: V) -> &mut Self where K: AsRef, V: AsRef, { self.cmd.env(key, val); self } /// Adds or updates multiple environment variable mappings. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use assert_cmd::Command; /// use std::process::Stdio; /// use std::env; /// use std::collections::HashMap; /// /// let filtered_env : HashMap = /// env::vars().filter(|&(ref k, _)| /// k == "TERM" || k == "TZ" || k == "LANG" || k == "PATH" /// ).collect(); /// /// Command::new("printenv") /// .env_clear() /// .envs(&filtered_env) /// .unwrap(); /// ``` pub fn envs(&mut self, vars: I) -> &mut Self where I: IntoIterator, K: AsRef, V: AsRef, { self.cmd.envs(vars); self } /// Removes an environment variable mapping. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use assert_cmd::Command; /// /// Command::new("ls") /// .env_remove("PATH") /// .unwrap_err(); /// ``` pub fn env_remove>(&mut self, key: K) -> &mut Self { self.cmd.env_remove(key); self } /// Clears the entire environment map for the child process. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use assert_cmd::Command; /// /// Command::new("ls") /// .env_clear() /// .unwrap_err(); /// ``` pub fn env_clear(&mut self) -> &mut Self { self.cmd.env_clear(); self } /// Sets the working directory for the child process. /// /// # Platform-specific behavior /// /// If the program path is relative (e.g., `"./script.sh"`), it's ambiguous /// whether it should be interpreted relative to the parent's working /// directory or relative to `current_dir`. The behavior in this case is /// platform specific and unstable, and it's recommended to use /// [`canonicalize`] to get an absolute program path instead. /// /// # Examples /// /// Basic usage: /// /// ```no_run /// use assert_cmd::Command; /// /// Command::new("ls") /// .current_dir("/bin") /// .unwrap(); /// ``` /// /// [`canonicalize`]: std::fs::canonicalize() pub fn current_dir>(&mut self, dir: P) -> &mut Self { self.cmd.current_dir(dir); self } /// Executes the `Command` as a child process, waiting for it to finish and collecting all of its /// output. /// /// By default, stdout and stderr are captured (and used to provide the resulting output). /// Stdin is not inherited from the parent and any attempt by the child process to read from /// the stdin stream will result in the stream immediately closing. /// /// # Examples /// /// ```should_panic /// use assert_cmd::Command; /// use std::io::{self, Write}; /// let output = Command::new("/bin/cat") /// .arg("file.txt") /// .output() /// .expect("failed to execute process"); /// /// println!("status: {}", output.status); /// io::stdout().write_all(&output.stdout).unwrap(); /// io::stderr().write_all(&output.stderr).unwrap(); /// /// assert!(output.status.success()); /// ``` pub fn output(&mut self) -> io::Result { let spawn = self.spawn()?; Self::wait_with_input_output(spawn, self.stdin.as_deref().cloned(), self.timeout) } /// If `input`, write it to `child`'s stdin while also reading `child`'s /// stdout and stderr, then wait on `child` and return its status and output. /// /// This was lifted from `std::process::Child::wait_with_output` and modified /// to also write to stdin. fn wait_with_input_output( mut child: process::Child, input: Option>, timeout: Option, ) -> io::Result { #![allow(clippy::unwrap_used)] // changes behavior in some tests fn read(mut input: R) -> std::thread::JoinHandle>> where R: Read + Send + 'static, { std::thread::spawn(move || { let mut ret = Vec::new(); input.read_to_end(&mut ret).map(|_| ret) }) } let stdin = input.and_then(|i| { child .stdin .take() .map(|mut stdin| std::thread::spawn(move || stdin.write_all(&i))) }); let stdout = child.stdout.take().map(read); let stderr = child.stderr.take().map(read); // Finish writing stdin before waiting, because waiting drops stdin. stdin.and_then(|t| t.join().unwrap().ok()); let status = if let Some(timeout) = timeout { wait_timeout::ChildExt::wait_timeout(&mut child, timeout) .transpose() .unwrap_or_else(|| { let _ = child.kill(); child.wait() }) } else { child.wait() }?; let stdout = stdout .and_then(|t| t.join().unwrap().ok()) .unwrap_or_default(); let stderr = stderr .and_then(|t| t.join().unwrap().ok()) .unwrap_or_default(); Ok(process::Output { status, stdout, stderr, }) } fn spawn(&mut self) -> io::Result { // stdout/stderr should only be piped for `output` according to `process::Command::new`. self.cmd.stdin(process::Stdio::piped()); self.cmd.stdout(process::Stdio::piped()); self.cmd.stderr(process::Stdio::piped()); self.cmd.spawn() } /// Returns the path to the program that was given to [`Command::new`]. /// /// # Examples /// /// Basic usage: /// /// ```rust /// use assert_cmd::Command; /// /// let cmd = Command::new("echo"); /// assert_eq!(cmd.get_program(), "echo"); /// ``` pub fn get_program(&self) -> &ffi::OsStr { self.cmd.get_program() } /// Returns an iterator of the arguments that will be passed to the program. /// /// This does not include the path to the program as the first argument; /// it only includes the arguments specified with [`Command::arg`] and /// [`Command::args`]. /// /// # Examples /// /// Basic usage: /// /// ```rust /// use std::ffi::OsStr; /// use assert_cmd::Command; /// /// let mut cmd = Command::new("echo"); /// cmd.arg("first").arg("second"); /// let args: Vec<&OsStr> = cmd.get_args().collect(); /// assert_eq!(args, &["first", "second"]); /// ``` pub fn get_args(&self) -> process::CommandArgs<'_> { self.cmd.get_args() } /// Returns an iterator of the environment variables explicitly set for the child process. /// /// Environment variables explicitly set using [`Command::env`], [`Command::envs`], and /// [`Command::env_remove`] can be retrieved with this method. /// /// Note that this output does not include environment variables inherited from the parent /// process. /// /// Each element is a tuple key/value pair `(&OsStr, Option<&OsStr>)`. A [`None`] value /// indicates its key was explicitly removed via [`Command::env_remove`]. The associated key for /// the [`None`] value will no longer inherit from its parent process. /// /// An empty iterator can indicate that no explicit mappings were added or that /// [`Command::env_clear`] was called. After calling [`Command::env_clear`], the child process /// will not inherit any environment variables from its parent process. /// /// # Examples /// /// Basic usage: /// /// ```rust /// use std::ffi::OsStr; /// use assert_cmd::Command; /// /// let mut cmd = Command::new("ls"); /// cmd.env("TERM", "dumb").env_remove("TZ"); /// let envs: Vec<(&OsStr, Option<&OsStr>)> = cmd.get_envs().collect(); /// assert_eq!(envs, &[ /// (OsStr::new("TERM"), Some(OsStr::new("dumb"))), /// (OsStr::new("TZ"), None) /// ]); /// ``` pub fn get_envs(&self) -> process::CommandEnvs<'_> { self.cmd.get_envs() } /// Returns the working directory for the child process. /// /// This returns [`None`] if the working directory will not be changed. /// /// # Examples /// /// Basic usage: /// /// ```rust /// use std::path::Path; /// use assert_cmd::Command; /// /// let mut cmd = Command::new("ls"); /// assert_eq!(cmd.get_current_dir(), None); /// cmd.current_dir("/bin"); /// assert_eq!(cmd.get_current_dir(), Some(Path::new("/bin"))); /// ``` pub fn get_current_dir(&self) -> Option<&path::Path> { self.cmd.get_current_dir() } } impl From for Command { fn from(cmd: process::Command) -> Self { Command::from_std(cmd) } } impl OutputOkExt for &mut Command { fn ok(self) -> OutputResult { let output = self.output().map_err(OutputError::with_cause)?; if output.status.success() { Ok(output) } else { let error = OutputError::new(output).set_cmd(format!("{:?}", self.cmd)); let error = if let Some(stdin) = self.stdin.as_ref() { error.set_stdin(stdin.deref().clone()) } else { error }; Err(error) } } fn unwrap_err(self) -> OutputError { match self.ok() { Ok(output) => { if let Some(stdin) = self.stdin.as_ref() { panic!( "Completed successfully:\ncommand=`{:?}`\nstdin=```{}```\nstdout=```{}```", self.cmd, DebugBytes::new(stdin), DebugBytes::new(&output.stdout) ) } else { panic!( "Completed successfully:\ncommand=`{:?}`\nstdout=```{}```", self.cmd, DebugBytes::new(&output.stdout) ) } } Err(err) => err, } } } impl OutputAssertExt for &mut Command { fn assert(self) -> Assert { let output = match self.output() { Ok(output) => output, Err(err) => { panic!("Failed to spawn {self:?}: {err}"); } }; let assert = Assert::new(output).append_context("command", format!("{:?}", self.cmd)); if let Some(stdin) = self.stdin.as_ref() { assert.append_context("stdin", DebugBuffer::new(stdin.deref().clone())) } else { assert } } } assert_cmd-2.0.17/src/color.rs000064400000000000000000000026371046102023000143220ustar 00000000000000#[derive(Copy, Clone, Debug, Default)] pub(crate) struct Palette { key: anstyle::Style, value: anstyle::Style, } impl Palette { pub(crate) fn color() -> Self { if cfg!(feature = "color") { Self { key: anstyle::AnsiColor::Blue.on_default() | anstyle::Effects::BOLD, value: anstyle::AnsiColor::Yellow.on_default() | anstyle::Effects::BOLD, } } else { Self::plain() } } pub(crate) fn plain() -> Self { Self::default() } pub(crate) fn key(self, display: D) -> Styled { Styled::new(display, self.key) } pub(crate) fn value(self, display: D) -> Styled { Styled::new(display, self.value) } } #[derive(Debug)] pub(crate) struct Styled { display: D, style: anstyle::Style, } impl Styled { pub(crate) fn new(display: D, style: anstyle::Style) -> Self { Self { display, style } } } impl std::fmt::Display for Styled { #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if f.alternate() { write!(f, "{}", self.style.render())?; self.display.fmt(f)?; write!(f, "{}", self.style.render_reset())?; Ok(()) } else { self.display.fmt(f) } } } assert_cmd-2.0.17/src/lib.rs000064400000000000000000000076301046102023000137500ustar 00000000000000//! **Assert [`Command`]** - Easy command initialization and assertions. //! //! `assert_cmd` aims to simplify the process for doing integration testing of CLIs, including: //! - Finding your crate's binary to test //! - Assert on the result of your program's run. //! //! ## Overview //! //! Create a [`Command`]: //! - `Command::new(path)` //! - `Command::from_std(...)` //! - `Command::cargo_bin(name)` //! //! Configure a [`Command`]: //! - `arg` / `args` //! - `current_dir` //! - `env` / `envs` / `env_remove` / `env_clear` //! - `write_stdin` / `pipe_stdin` //! - `timeout` //! //! Validate a [`Command`]: //! - `ok` / `unwrap` / `unwrap_err` //! - `assert` //! - `success`, see [`Assert`] //! - `failure`, see [`Assert`] //! - `interrupted`, see [`Assert`] //! - `code`, see [`Assert`] //! - `stdout`, see [`Assert`] //! - `stderr`, see [`Assert`] //! - `get_output` for everything else, see [`Assert`] //! //! Note: [`Command`] is provided as a convenience. Extension traits for [`std::process::Command`] //! and `Output` are provided for interoperability: //! - [`CommandCargoExt`] //! - [`OutputOkExt`] //! - [`OutputAssertExt`] //! //! ## Examples //! //! Here's a trivial example: //! ```rust,no_run //! use assert_cmd::Command; //! //! let mut cmd = Command::cargo_bin("bin_fixture").unwrap(); //! cmd.assert().success(); //! ``` //! //! And a little of everything: //! ```rust,no_run //! use assert_cmd::Command; //! //! let mut cmd = Command::cargo_bin("bin_fixture").unwrap(); //! let assert = cmd //! .arg("-A") //! .env("stdout", "hello") //! .env("exit", "42") //! .write_stdin("42") //! .assert(); //! assert //! .failure() //! .code(42) //! .stdout("hello\n"); //! ``` //! //! ## Relevant crates //! //! Other crates that might be useful in testing command line programs. //! * [escargot] for more control over configuring the crate's binary. //! * [duct] for orchestrating multiple processes. //! * or [commandspec] for easier writing of commands //! * [rexpect][rexpect] for testing interactive programs. //! * [assert_fs] for filesystem fixtures and assertions. //! * or [tempfile] for scratchpad directories. //! * [dir-diff] for testing file side-effects. //! //! ## Migrating from `assert_cli` v0.6 //! //! `assert_cmd` is the successor to [the original `assert_cli`][assert_cli]: //! - More flexible, reusable assertions (also used by [assert_fs]). //! - Can integrate with other process-management crates, like `duct`. //! - Addresses several architectural problems. //! //! Key points in migrating from `assert_cli`: //! - The command-under-test is run eagerly, with assertions happening immediately. //! - [`success()`] is not implicit and requires being explicitly called. //! - `stdout`/`stderr` aren't automatically trimmed before being passed to the `Predicate`. //! //! [commandspec]: https://crates.io/crates/commandspec //! [assert_cli]: https://crates.io/crates/assert_cli/0.6.3 //! [dir-diff]: https://crates.io/crates/dir-diff //! [tempfile]: https://crates.io/crates/tempfile //! [escargot]: https://crates.io/crates/escargot //! [duct]: https://crates.io/crates/duct //! [assert_fs]: https://crates.io/crates/assert_fs //! [rexpect]: https://crates.io/crates/rexpect //! [`Command`]: cmd::Command //! [`Assert`]: assert::Assert //! [`success()`]: assert::Assert::success() //! [`CommandCargoExt`]: cargo::CommandCargoExt //! [`OutputOkExt`]: output::OutputOkExt //! [`OutputAssertExt`]: assert::OutputAssertExt #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![warn(clippy::print_stderr)] #![warn(clippy::print_stdout)] mod macros; pub mod assert; pub mod cargo; pub mod cmd; pub mod output; /// Extension traits that are useful to have available. pub mod prelude { pub use crate::assert::OutputAssertExt; pub use crate::cargo::CommandCargoExt; pub use crate::output::OutputOkExt; } pub use crate::cmd::Command; mod color; use color::Palette; doc_comment::doctest!("../README.md"); assert_cmd-2.0.17/src/macros.rs000064400000000000000000000022251046102023000144610ustar 00000000000000/// Allows you to pull the name from your Cargo.toml at compile time. /// /// # Examples /// /// ```should_panic /// use assert_cmd::Command; /// /// let mut cmd = Command::cargo_bin(assert_cmd::crate_name!()).unwrap(); /// let assert = cmd /// .arg("-A") /// .env("stdout", "hello") /// .env("exit", "42") /// .write_stdin("42") /// .assert(); /// assert /// .failure() /// .code(42) /// .stdout("hello\n"); /// ``` #[macro_export] macro_rules! crate_name { () => { env!("CARGO_PKG_NAME") }; } /// The absolute path to a binary target's executable. /// /// The `bin_target_name` is the name of the binary /// target, exactly as-is. /// /// **NOTE:** This is only set when building an integration test or benchmark. /// /// ## Example /// /// ```rust,no_run /// #[test] /// fn cli_tests() { /// trycmd::TestCases::new() /// .default_bin_path(trycmd::cargo_bin!("bin-fixture")) /// .case("tests/cmd/*.trycmd"); /// } /// ``` #[macro_export] #[doc(hidden)] macro_rules! cargo_bin { ($bin_target_name:expr) => { ::std::path::Path::new(env!(concat!("CARGO_BIN_EXE_", $bin_target_name))) }; } assert_cmd-2.0.17/src/output.rs000064400000000000000000000270321046102023000145400ustar 00000000000000//! Simplify one-off runs of programs. use bstr::ByteSlice; use std::error::Error; use std::fmt; use std::process; /// Converts a type to an [`OutputResult`]. /// /// This is for example implemented on [`std::process::Output`]. /// /// # Examples /// /// ```rust /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// let result = Command::new("echo") /// .args(&["42"]) /// .ok(); /// assert!(result.is_ok()); /// ``` /// pub trait OutputOkExt where Self: ::std::marker::Sized, { /// Convert an [`Output`] to an [`OutputResult`]. /// /// # Examples /// /// ```rust /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// let result = Command::new("echo") /// .args(&["42"]) /// .ok(); /// assert!(result.is_ok()); /// ``` /// /// [`Output`]: std::process::Output fn ok(self) -> OutputResult; /// Unwrap a [`Output`] but with a prettier message than `.ok().unwrap()`. /// /// # Examples /// /// ```rust /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// let output = Command::new("echo") /// .args(&["42"]) /// .unwrap(); /// ``` /// /// [`Output`]: std::process::Output fn unwrap(self) -> process::Output { match self.ok() { Ok(output) => output, Err(err) => panic!("{}", err), } } /// Unwrap a [`Output`] but with a prettier message than `ok().err().unwrap()`. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// let err = Command::new("a-command") /// .args(&["--will-fail"]) /// .unwrap_err(); /// ``` /// /// [`Output`]: std::process::Output fn unwrap_err(self) -> OutputError { match self.ok() { Ok(output) => panic!( "Command completed successfully\nstdout=```{}```", DebugBytes::new(&output.stdout) ), Err(err) => err, } } } impl OutputOkExt for process::Output { fn ok(self) -> OutputResult { if self.status.success() { Ok(self) } else { let error = OutputError::new(self); Err(error) } } } impl OutputOkExt for &mut process::Command { fn ok(self) -> OutputResult { let output = self.output().map_err(OutputError::with_cause)?; if output.status.success() { Ok(output) } else { let error = OutputError::new(output).set_cmd(format!("{self:?}")); Err(error) } } fn unwrap_err(self) -> OutputError { match self.ok() { Ok(output) => panic!( "Completed successfully:\ncommand=`{:?}`\nstdout=```{}```", self, DebugBytes::new(&output.stdout) ), Err(err) => err, } } } /// [`Output`] represented as a [`Result`]. /// /// Generally produced by [`OutputOkExt`]. /// /// # Examples /// /// ```rust /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// let result = Command::new("echo") /// .args(&["42"]) /// .ok(); /// assert!(result.is_ok()); /// ``` /// /// [`Output`]: std::process::Output /// [`Result`]: std::result::Result pub type OutputResult = Result; /// [`Command`] error. /// /// Generally produced by [`OutputOkExt`]. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// let err = Command::new("a-command") /// .args(&["--will-fail"]) /// .unwrap_err(); /// ``` /// /// [`Command`]: std::process::Command #[derive(Debug)] pub struct OutputError { cmd: Option, stdin: Option, cause: OutputCause, } impl OutputError { /// Convert [`Output`] into an [`Error`]. /// /// [`Output`]: std::process::Output /// [`Error`]: std::error::Error pub fn new(output: process::Output) -> Self { Self { cmd: None, stdin: None, cause: OutputCause::Expected(Output { output }), } } /// For errors that happen in creating a [`Output`]. /// /// [`Output`]: std::process::Output pub fn with_cause(cause: E) -> Self where E: Error + Send + Sync + 'static, { Self { cmd: None, stdin: None, cause: OutputCause::Unexpected(Box::new(cause)), } } /// Add the command line for additional context. pub fn set_cmd(mut self, cmd: String) -> Self { self.cmd = Some(cmd); self } /// Add the `stdin` for additional context. pub fn set_stdin(mut self, stdin: Vec) -> Self { self.stdin = Some(bstr::BString::from(stdin)); self } /// Access the contained [`Output`]. /// /// # Examples /// /// ```rust,no_run /// use assert_cmd::prelude::*; /// /// use std::process::Command; /// /// let err = Command::new("a-command") /// .args(&["--will-fail"]) /// .unwrap_err(); /// let output = err /// .as_output() /// .unwrap(); /// assert_eq!(Some(42), output.status.code()); /// ``` /// /// [`Output`]: std::process::Output pub fn as_output(&self) -> Option<&process::Output> { match self.cause { OutputCause::Expected(ref e) => Some(&e.output), OutputCause::Unexpected(_) => None, } } } impl Error for OutputError {} impl fmt::Display for OutputError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let palette = crate::Palette::color(); if let Some(ref cmd) = self.cmd { writeln!(f, "{:#}={:#}", palette.key("command"), palette.value(cmd))?; } if let Some(ref stdin) = self.stdin { writeln!( f, "{:#}={:#}", palette.key("stdin"), palette.value(DebugBytes::new(stdin)) )?; } write!(f, "{:#}", self.cause) } } #[derive(Debug)] enum OutputCause { Expected(Output), Unexpected(Box), } impl fmt::Display for OutputCause { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { OutputCause::Expected(ref e) => write!(f, "{e:#}"), OutputCause::Unexpected(ref e) => write!(f, "{e:#}"), } } } #[derive(Debug)] struct Output { output: process::Output, } impl fmt::Display for Output { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { output_fmt(&self.output, f) } } pub(crate) fn output_fmt(output: &process::Output, f: &mut fmt::Formatter<'_>) -> fmt::Result { let palette = crate::Palette::color(); if let Some(code) = output.status.code() { writeln!(f, "{:#}={:#}", palette.key("code"), palette.value(code))?; } else { writeln!( f, "{:#}={:#}", palette.key("code"), palette.value("") )?; } write!( f, "{:#}={:#}\n{:#}={:#}\n", palette.key("stdout"), palette.value(DebugBytes::new(&output.stdout)), palette.key("stderr"), palette.value(DebugBytes::new(&output.stderr)), )?; Ok(()) } #[derive(Debug)] pub(crate) struct DebugBytes<'a> { bytes: &'a [u8], } impl<'a> DebugBytes<'a> { pub(crate) fn new(bytes: &'a [u8]) -> Self { DebugBytes { bytes } } } impl fmt::Display for DebugBytes<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { format_bytes(self.bytes, f) } } #[derive(Debug)] pub(crate) struct DebugBuffer { buffer: bstr::BString, } impl DebugBuffer { pub(crate) fn new(buffer: Vec) -> Self { DebugBuffer { buffer: buffer.into(), } } } impl fmt::Display for DebugBuffer { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { format_bytes(&self.buffer, f) } } fn format_bytes(data: &[u8], f: &mut impl fmt::Write) -> fmt::Result { #![allow(clippy::assertions_on_constants)] const LINES_MIN_OVERFLOW: usize = 80; const LINES_MAX_START: usize = 20; const LINES_MAX_END: usize = 40; const LINES_MAX_PRINTED: usize = LINES_MAX_START + LINES_MAX_END; const BYTES_MIN_OVERFLOW: usize = 8192; const BYTES_MAX_START: usize = 2048; const BYTES_MAX_END: usize = 2048; const BYTES_MAX_PRINTED: usize = BYTES_MAX_START + BYTES_MAX_END; assert!(LINES_MAX_PRINTED < LINES_MIN_OVERFLOW); assert!(BYTES_MAX_PRINTED < BYTES_MIN_OVERFLOW); let lines_total = data.as_bstr().lines_with_terminator().count(); let multiline = 1 < lines_total; if LINES_MIN_OVERFLOW <= lines_total { let lines_omitted = lines_total - LINES_MAX_PRINTED; let start_lines = data.as_bstr().lines_with_terminator().take(LINES_MAX_START); let end_lines = data .as_bstr() .lines_with_terminator() .skip(LINES_MAX_START + lines_omitted); writeln!(f, "<{lines_total} lines total>")?; write_debug_bstrs(f, true, start_lines)?; writeln!(f, "<{lines_omitted} lines omitted>")?; write_debug_bstrs(f, true, end_lines) } else if BYTES_MIN_OVERFLOW <= data.len() { write!( f, "<{} bytes total>{}", data.len(), if multiline { "\n" } else { "" } )?; write_debug_bstrs( f, multiline, data[..BYTES_MAX_START].lines_with_terminator(), )?; write!( f, "<{} bytes omitted>{}", data.len() - BYTES_MAX_PRINTED, if multiline { "\n" } else { "" } )?; write_debug_bstrs( f, multiline, data[data.len() - BYTES_MAX_END..].lines_with_terminator(), ) } else { write_debug_bstrs(f, multiline, data.lines_with_terminator()) } } fn write_debug_bstrs<'a>( f: &mut impl fmt::Write, multiline: bool, mut lines: impl Iterator, ) -> fmt::Result { if multiline { writeln!(f, "```")?; for mut line in lines { let mut newline = false; if line.last() == Some(&b'\n') { line = &line[..line.len() - 1]; newline = true; } let s = format!("{:?}", line.as_bstr()); write!( f, "{}{}", &s[1..s.len() - 1], if newline { "\n" } else { "" } )?; } writeln!(f, "```") } else { write!(f, "{:?}", lines.next().unwrap_or(&[]).as_bstr()) } } #[cfg(test)] mod test { #[test] fn format_bytes() { let mut s = String::new(); for i in 0..80 { s.push_str(&format!("{i}\n")); } let mut buf = String::new(); super::format_bytes(s.as_bytes(), &mut buf).unwrap(); assert_eq!( "<80 lines total> ``` 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 ``` <20 lines omitted> ``` 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 ``` ", buf ); } #[test] fn no_trailing_newline() { let s = "no\ntrailing\nnewline"; let mut buf = String::new(); super::format_bytes(s.as_bytes(), &mut buf).unwrap(); assert_eq!( "``` no trailing newline``` ", buf ); } }