diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0e0baa9..ca7d480c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -169,7 +169,7 @@ jobs: # # Also make sure to update the MSRV in the cargo-semver-checks-action CI: # https://github.com/obi1kenobi/cargo-semver-checks-action/blob/main/.github/workflows/test-action.yml#L18 - toolchain: ["1.77", "1.78", "1.79", "stable", "beta"] + toolchain: ["1.77", "1.78", "1.79", "1.80", "stable", "beta"] experimental: [false] include: - toolchain: "nightly" @@ -1084,69 +1084,87 @@ jobs: persist-credentials: false path: 'semver' - - name: Checkout tokio - uses: actions/checkout@v4 - with: - persist-credentials: false - repository: 'tokio-rs/tokio' - ref: 'd7b7c6131774ab631be6529fef3680abfeeb4781' - path: 'subject' - - - name: Install rust - id: toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - cache: false - rustflags: "" - - - name: Restore cargo index and rustdoc target dir - uses: Swatinem/rust-cache@v2 - with: - workspaces: | - subject/target/semver-checks/local-tokio-1_25_0 - subject/target/semver-checks/local-tokio_macros-1_8_2 - subject/target/semver-checks/local-tokio_stream-0_1_12 - subject/target/semver-checks/local-tokio_test-0_4_2 - subject/target/semver-checks/local-tokio_util-0_7_7 - - - name: Restore binary - id: cache-binary - uses: actions/cache/restore@v4 - with: - path: bins/ - key: bins-${{ runner.os }}-${{ github.run_id }}-${{ github.run_attempt }} - fail-on-cache-miss: true - - - name: Restore rustdoc - id: cache - uses: actions/cache/restore@v4 - with: - key: ${{ runner.os }}-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('semver/**/Cargo.lock') }}-${{ github.job }}-rustdoc - path: subject/target/semver-checks/cache - - # This test caught two bugs: - # - The default baseline was set to the current path instead of the default registry version. - # - Specifying `--exclude` together with a crate manifest that is within a workspace - # (but doesn't *itself* define the workspace) would cause the entire workspace to - # get tested, even though only a single crate's manifest was specified. - - name: Run semver-checks on tokio-stream crate manifest only + - name: Disabled due to \#902 + env: + GH_TOKEN: ${{ github.token }} run: | cd semver - ../bins/cargo-semver-checks semver-checks check-release --manifest-path="../subject/tokio-stream/Cargo.toml" --release-type minor --exclude benches --exclude examples --exclude stress-test --exclude tests-build --exclude tests-integration --verbose + STATE="$(gh issue view 902 --json state --jq .state)" - # This test caught a bug where `--exclude` was silently ignored - # if `--workspace` wasn't set at the same time. - - name: Run semver-checks on workspace manifest with explicit exclusions - run: | - cd semver - ../bins/cargo-semver-checks semver-checks check-release --manifest-path="../subject/Cargo.toml" --release-type minor --exclude examples --exclude stress-test --exclude tests-build --exclude tests-integration --verbose + if [[ "$STATE" == 'OPEN' ]]; then + echo 'Test disabled due to https://github.com/obi1kenobi/cargo-semver-checks/issues/902' + elif [[ "$STATE" == 'CLOSED' ]]; then + echo 'Please re-enable this test since the underlying issue has been closed' + exit 1 + else + echo 'Unknown state for issue #902:' + echo "$STATE" + exit 1 + fi - - name: Save rustdoc - uses: actions/cache/save@v4 - if: steps.cache.outputs.cache-hit != 'true' - with: - key: ${{ runner.os }}-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('semver/**/Cargo.lock') }}-${{ github.job }}-rustdoc - path: subject/target/semver-checks/cache + # - name: Checkout tokio + # uses: actions/checkout@v4 + # with: + # persist-credentials: false + # repository: 'tokio-rs/tokio' + # ref: 'd7b7c6131774ab631be6529fef3680abfeeb4781' + # path: 'subject' + + # - name: Install rust + # id: toolchain + # uses: actions-rust-lang/setup-rust-toolchain@v1 + # with: + # cache: false + # rustflags: "" + + # - name: Restore cargo index and rustdoc target dir + # uses: Swatinem/rust-cache@v2 + # with: + # workspaces: | + # subject/target/semver-checks/local-tokio-1_25_0 + # subject/target/semver-checks/local-tokio_macros-1_8_2 + # subject/target/semver-checks/local-tokio_stream-0_1_12 + # subject/target/semver-checks/local-tokio_test-0_4_2 + # subject/target/semver-checks/local-tokio_util-0_7_7 + + # - name: Restore binary + # id: cache-binary + # uses: actions/cache/restore@v4 + # with: + # path: bins/ + # key: bins-${{ runner.os }}-${{ github.run_id }}-${{ github.run_attempt }} + # fail-on-cache-miss: true + + # - name: Restore rustdoc + # id: cache + # uses: actions/cache/restore@v4 + # with: + # key: ${{ runner.os }}-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('semver/**/Cargo.lock') }}-${{ github.job }}-rustdoc + # path: subject/target/semver-checks/cache + + # # This test caught two bugs: + # # - The default baseline was set to the current path instead of the default registry version. + # # - Specifying `--exclude` together with a crate manifest that is within a workspace + # # (but doesn't *itself* define the workspace) would cause the entire workspace to + # # get tested, even though only a single crate's manifest was specified. + # - name: Run semver-checks on tokio-stream crate manifest only + # run: | + # cd semver + # ../bins/cargo-semver-checks semver-checks check-release --manifest-path="../subject/tokio-stream/Cargo.toml" --release-type minor --exclude benches --exclude examples --exclude stress-test --exclude tests-build --exclude tests-integration --verbose + + # # This test caught a bug where `--exclude` was silently ignored + # # if `--workspace` wasn't set at the same time. + # - name: Run semver-checks on workspace manifest with explicit exclusions + # run: | + # cd semver + # ../bins/cargo-semver-checks semver-checks check-release --manifest-path="../subject/Cargo.toml" --release-type minor --exclude examples --exclude stress-test --exclude tests-build --exclude tests-integration --verbose + + # - name: Save rustdoc + # uses: actions/cache/save@v4 + # if: steps.cache.outputs.cache-hit != 'true' + # with: + # key: ${{ runner.os }}-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('semver/**/Cargo.lock') }}-${{ github.job }}-rustdoc + # path: subject/target/semver-checks/cache run-on-tokio-implicit: # cargo-semver-checks crashed here due to improper CLI argument handling: @@ -1161,60 +1179,78 @@ jobs: with: persist-credentials: false path: 'semver' - - - name: Checkout tokio - uses: actions/checkout@v4 - with: - persist-credentials: false - repository: 'tokio-rs/tokio' - ref: 'd7b7c6131774ab631be6529fef3680abfeeb4781' - path: 'subject' - - - name: Install rust - id: toolchain - uses: actions-rust-lang/setup-rust-toolchain@v1 - with: - cache: false - rustflags: "" - - - name: Restore cargo index and rustdoc target dir - uses: Swatinem/rust-cache@v2 - with: - workspaces: | - subject/target/semver-checks/local-tokio-1_25_0 - subject/target/semver-checks/local-tokio_macros-1_8_2 - subject/target/semver-checks/local-tokio_stream-0_1_12 - subject/target/semver-checks/local-tokio_test-0_4_2 - subject/target/semver-checks/local-tokio_util-0_7_7 - - - name: Restore binary - id: cache-binary - uses: actions/cache/restore@v4 - with: - path: bins/ - key: bins-${{ runner.os }}-${{ github.run_id }}-${{ github.run_attempt }} - fail-on-cache-miss: true - - - name: Restore rustdoc - id: cache - uses: actions/cache/restore@v4 - with: - key: ${{ runner.os }}-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('semver/**/Cargo.lock') }}-${{ github.job }}-rustdoc - path: subject/target/semver-checks/cache - - # This test caught a bug where `publish = false` items in a workspace were semver-checked - # unless either explicit `--workspace` was present or was implied e.g. via `--exclude`. - - name: Run semver-checks on workspace manifest with implicit exclusions + - name: Disabled due to \#902 + env: + GH_TOKEN: ${{ github.token }} run: | cd semver - ../bins/cargo-semver-checks semver-checks check-release --manifest-path="../subject/Cargo.toml" --release-type minor --verbose + STATE="$(gh issue view 902 --json state --jq .state)" - - name: Save rustdoc - uses: actions/cache/save@v4 - if: steps.cache.outputs.cache-hit != 'true' - with: - key: ${{ runner.os }}-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('semver/**/Cargo.lock') }}-${{ github.job }}-rustdoc - path: subject/target/semver-checks/cache + if [[ "$STATE" == 'OPEN' ]]; then + echo 'Test disabled due to https://github.com/obi1kenobi/cargo-semver-checks/issues/902' + elif [[ "$STATE" == 'CLOSED' ]]; then + echo 'Please re-enable this test since the underlying issue has been closed' + exit 1 + else + echo 'Unknown state for issue #902:' + echo "$STATE" + exit 1 + fi + + + # - name: Checkout tokio + # uses: actions/checkout@v4 + # with: + # persist-credentials: false + # repository: 'tokio-rs/tokio' + # ref: 'd7b7c6131774ab631be6529fef3680abfeeb4781' + # path: 'subject' + + # - name: Install rust + # id: toolchain + # uses: actions-rust-lang/setup-rust-toolchain@v1 + # with: + # cache: false + # rustflags: "" + + # - name: Restore cargo index and rustdoc target dir + # uses: Swatinem/rust-cache@v2 + # with: + # workspaces: | + # subject/target/semver-checks/local-tokio-1_25_0 + # subject/target/semver-checks/local-tokio_macros-1_8_2 + # subject/target/semver-checks/local-tokio_stream-0_1_12 + # subject/target/semver-checks/local-tokio_test-0_4_2 + # subject/target/semver-checks/local-tokio_util-0_7_7 + + # - name: Restore binary + # id: cache-binary + # uses: actions/cache/restore@v4 + # with: + # path: bins/ + # key: bins-${{ runner.os }}-${{ github.run_id }}-${{ github.run_attempt }} + # fail-on-cache-miss: true + + # - name: Restore rustdoc + # id: cache + # uses: actions/cache/restore@v4 + # with: + # key: ${{ runner.os }}-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('semver/**/Cargo.lock') }}-${{ github.job }}-rustdoc + # path: subject/target/semver-checks/cache + + # # This test caught a bug where `publish = false` items in a workspace were semver-checked + # # unless either explicit `--workspace` was present or was implied e.g. via `--exclude`. + # - name: Run semver-checks on workspace manifest with implicit exclusions + # run: | + # cd semver + # ../bins/cargo-semver-checks semver-checks check-release --manifest-path="../subject/Cargo.toml" --release-type minor --verbose + + # - name: Save rustdoc + # uses: actions/cache/save@v4 + # if: steps.cache.outputs.cache-hit != 'true' + # with: + # key: ${{ runner.os }}-${{ steps.toolchain.outputs.cachekey }}-${{ hashFiles('semver/**/Cargo.lock') }}-${{ github.job }}-rustdoc + # path: subject/target/semver-checks/cache run-on-clap: # clap v3.2.0 added a semver violation diff --git a/Cargo.lock b/Cargo.lock index 875b2cab..aa543f08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -101,9 +101,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8" [[package]] name = "arc-swap" @@ -304,9 +304,9 @@ dependencies = [ [[package]] name = "cargo-config2" -version = "0.1.27" +version = "0.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd6dbf7694882745446c74e2dfc4f63b2139c6d2d5692fe33c0999709c40b9e" +checksum = "1124054becb9262cc15c5e96e82f0d782f2aed3a3034d1f71a6385a6fa9e9595" dependencies = [ "home", "serde", @@ -325,7 +325,7 @@ dependencies = [ [[package]] name = "cargo-semver-checks" -version = "0.34.0" +version = "0.35.0" dependencies = [ "anstream", "anstyle", @@ -346,6 +346,7 @@ dependencies = [ "human-panic", "ignore", "insta", + "insta-cmd", "itertools 0.13.0", "log", "predicates", @@ -391,9 +392,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.14" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d2eb3cd3d1bf4529e31c215ee6f93ec5a3d536d9f578f93d9d33ee19562932" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" dependencies = [ "shlex", ] @@ -406,9 +407,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.16" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", "clap_derive", @@ -437,9 +438,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", @@ -516,9 +517,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -737,9 +738,9 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "filetime" -version = "0.2.24" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" dependencies = [ "cfg-if", "libc", @@ -1372,9 +1373,9 @@ dependencies = [ [[package]] name = "gix-path" -version = "0.10.10" +version = "0.10.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d5b8722112fa2fa87135298780bc833b0e9f6c56cc82795d209804b3a03484" +checksum = "ebfc4febd088abdcbc9f1246896e57e37b7a34f6909840045a1767c6dafac7af" dependencies = [ "bstr", "gix-trace", @@ -1548,9 +1549,9 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f924267408915fddcd558e3f37295cc7d6a3e50f8bd8b606cee0808c3915157e" +checksum = "6cae0e8661c3ff92688ce1c8b8058b3efb312aba9492bbe93661a21705ab431b" [[package]] name = "gix-transport" @@ -1676,9 +1677,9 @@ dependencies = [ [[package]] name = "handlebars" -version = "6.0.0" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5226a0e122dc74917f3a701484482bed3ee86d016c7356836abbaa033133a157" +checksum = "ce25b617d1375ef96eeb920ae717e3da34a02fc979fe632c75128350f9e1f74a" dependencies = [ "log", "pest", @@ -1806,9 +1807,9 @@ dependencies = [ [[package]] name = "hyper-rustls" -version = "0.27.2" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http", @@ -1870,9 +1871,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown", @@ -1881,9 +1882,9 @@ dependencies = [ [[package]] name = "insta" -version = "1.39.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810ae6042d48e2c9e9215043563a58a80b877bc863228a74cf10c49d4620a6f5" +checksum = "6593a41c7a73841868772495db7dc1e8ecab43bb5c0b6da2059246c4b506ab60" dependencies = [ "console", "lazy_static", @@ -1894,11 +1895,22 @@ dependencies = [ "similar", ] +[[package]] +name = "insta-cmd" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffeeefa927925cced49ccb01bf3e57c9d4cd132df21e576eb9415baeab2d3de6" +dependencies = [ + "insta", + "serde", + "serde_json", +] + [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "is_terminal_polyfill" @@ -1932,9 +1944,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jiff" -version = "0.1.10" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ef8bc400f8312944a9f879db116fed372c4f0859af672eba2a80f79c767dd19" +checksum = "8a45489186a6123c128fdf6016183fcfab7113e1820eb813127e036e287233fb" dependencies = [ "jiff-tzdb-platform", "windows-sys 0.59.0", @@ -1942,15 +1954,15 @@ dependencies = [ [[package]] name = "jiff-tzdb" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05fac328b3df1c0f18a3c2ab6cb7e06e4e549f366017d796e3e66b6d6889abe6" +checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653" [[package]] name = "jiff-tzdb-platform" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8da387d5feaf355954c2c122c194d6df9c57d865125a67984bb453db5336940" +checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329" dependencies = [ "jiff-tzdb", ] @@ -2156,9 +2168,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.3" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] @@ -2235,9 +2247,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.11" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "9c73c26c01b8c87956cea613c907c9d6ecffd8d18a2a5908e5de0adfaa185cea" dependencies = [ "memchr", "thiserror", @@ -2246,9 +2258,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.11" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +checksum = "664d22978e2815783adbdd2c588b455b1bd625299ce36b2a99881ac9627e6d8d" dependencies = [ "pest", "pest_generator", @@ -2256,9 +2268,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.11" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +checksum = "a2d5487022d5d33f4c30d91c22afa240ce2a644e87fe08caad974d4eab6badbe" dependencies = [ "pest", "pest_meta", @@ -2269,9 +2281,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.11" +version = "2.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +checksum = "0091754bbd0ea592c4deb3a122ce8ecbb0753b738aa82bc055fcc2eccc8d8174" dependencies = [ "once_cell", "pest", @@ -2378,9 +2390,9 @@ checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" [[package]] name = "quinn" -version = "0.11.3" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b22d8e7369034b9a7132bc2008cac12f2013c8132b45e0554e6e20e2617f2156" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" dependencies = [ "bytes", "pin-project-lite", @@ -2396,9 +2408,9 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.11.6" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba92fb39ec7ad06ca2582c0ca834dfeadcaf06ddfc8e635c80aa7e1c05315fdd" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" dependencies = [ "bytes", "rand", @@ -2413,15 +2425,15 @@ dependencies = [ [[package]] name = "quinn-udp" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bffec3605b73c6f1754535084a85229fa8a30f86014e6c81aeec4abb68b0285" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" dependencies = [ "libc", "once_cell", "socket2", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2632,9 +2644,9 @@ checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] @@ -2686,9 +2698,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.34" +version = "0.38.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" dependencies = [ "bitflags 2.6.0", "errno", @@ -2729,9 +2741,9 @@ checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" -version = "0.102.6" +version = "0.102.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" +checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" dependencies = [ "ring", "rustls-pki-types", @@ -2755,11 +2767,11 @@ dependencies = [ [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2779,18 +2791,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -2799,9 +2811,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -2898,9 +2910,9 @@ dependencies = [ [[package]] name = "similar-asserts" -version = "1.5.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e041bb827d1bfca18f213411d51b665309f1afb37a04a5d1464530e13779fc0f" +checksum = "cfe85670573cd6f0fa97940f26e7e6601213c3b0555246c24234131f88c5709e" dependencies = [ "console", "serde", @@ -2970,9 +2982,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.76" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -3133,9 +3145,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -3159,9 +3171,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -3272,9 +3284,9 @@ dependencies = [ [[package]] name = "trustfall-rustdoc-adapter" -version = "28.1.3" +version = "28.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f215d49f2108689a3cf1c4ba4e48a47d40301567a1df3106836f882bd433a682" +checksum = "3c75c87b6555d5d888e2d7047d820cd4586302e42e9dd881ee28800f225b82a7" dependencies = [ "rustdoc-types 0.24.0", "trustfall", @@ -3282,9 +3294,9 @@ dependencies = [ [[package]] name = "trustfall-rustdoc-adapter" -version = "29.1.3" +version = "29.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c78be19778059e175300fe71f3869d3aa4991c72af09c65b232d1870904b287b" +checksum = "0f2c2b64014ee66c61be248d12ee8db2d6973db90b6f68046e89f13588ca3475" dependencies = [ "rustdoc-types 0.25.0", "trustfall", @@ -3292,9 +3304,9 @@ dependencies = [ [[package]] name = "trustfall-rustdoc-adapter" -version = "30.1.3" +version = "30.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78f89d7ebd7029268433ac0e647a45f1f6a829102441c95d4908dcea8803356e" +checksum = "0b6a00f2f51bc5f88028ad5ef064e40a9e8af5b313d7e37834c315b681cae2f4" dependencies = [ "rustdoc-types 0.26.0", "trustfall", @@ -3302,9 +3314,9 @@ dependencies = [ [[package]] name = "trustfall-rustdoc-adapter" -version = "32.1.3" +version = "32.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b381607f0009a87215190cc3ed5469eabfb4c057fc40117364de794fda09f86" +checksum = "f826c74e11761b4ef919396c16b3472ed767c81ce40ea6f234d9ba2ac79b65bd" dependencies = [ "rustdoc-types 0.28.1", "trustfall", @@ -3312,9 +3324,9 @@ dependencies = [ [[package]] name = "trustfall-rustdoc-adapter" -version = "33.1.3" +version = "33.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11e9b5aeb138eb9c4eb323fbcb1118816cebce7735ecb4916164c86b109c00f3" +checksum = "970e7763e31dffc6ee25699cde56c11a45e33c505ab4b08da5e1f4c0861e44f4" dependencies = [ "rustdoc-types 0.29.1", "trustfall", @@ -3350,19 +3362,19 @@ dependencies = [ [[package]] name = "trustfall_rustdoc" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "285db3f0985971888b37632f9b28529ee069c9c941489c7049bf0f456e842c68" +checksum = "838964586da4bf4aa7831f864411fddf7712140ff897f6d9279563081102aa5f" dependencies = [ "anyhow", "serde", "serde_json", "trustfall", - "trustfall-rustdoc-adapter 28.1.3", - "trustfall-rustdoc-adapter 29.1.3", - "trustfall-rustdoc-adapter 30.1.3", - "trustfall-rustdoc-adapter 32.1.3", - "trustfall-rustdoc-adapter 33.1.3", + "trustfall-rustdoc-adapter 28.1.6", + "trustfall-rustdoc-adapter 29.1.6", + "trustfall-rustdoc-adapter 30.1.6", + "trustfall-rustdoc-adapter 32.1.6", + "trustfall-rustdoc-adapter 33.1.6", ] [[package]] @@ -3598,9 +3610,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.3" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd" +checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" dependencies = [ "rustls-pki-types", ] diff --git a/Cargo.toml b/Cargo.toml index 370d191b..4841450b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cargo-semver-checks" -version = "0.34.0" +version = "0.35.0" edition = "2021" authors = ["Predrag Gruevski "] license = "Apache-2.0 OR MIT" @@ -55,6 +55,7 @@ predicates = "3.1.0" insta = { version = "1.39.0", features = ["ron", "filters"] } fs-err = "2.11.0" regex = "1.10.6" +insta-cmd = "0.6.0" # In dev and test profiles, compile all dependencies with optimizations enabled, # but still checking debug assertions and overflows. diff --git a/src/config.rs b/src/config.rs index 06701a6e..0de4a174 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,6 +1,7 @@ use anstream::{AutoStream, ColorChoice}; use anstyle::{AnsiColor, Color, Reset, Style}; -use std::io::Write; +use clap::ValueEnum; +use std::{collections::HashSet, io::Write}; use crate::templating::make_handlebars_registry; @@ -14,6 +15,7 @@ pub struct GlobalConfig { minimum_rustc_version: semver::Version, stdout: AutoStream>, stderr: AutoStream>, + feature_flags: HashSet, } impl Default for GlobalConfig { @@ -38,6 +40,7 @@ impl GlobalConfig { minimum_rustc_version: semver::Version::new(1, 77, 0), stdout: AutoStream::new(Box::new(std::io::stdout()), stdout_choice), stderr: AutoStream::new(Box::new(std::io::stderr()), stderr_choice), + feature_flags: HashSet::new(), } } @@ -284,6 +287,120 @@ impl GlobalConfig { ColorChoice::Never | ColorChoice::Auto => false, } } + + /// Set (overwrite) the [`FeatureFlag`] set. + #[inline] + pub fn set_feature_flags(&mut self, flags: HashSet) -> &mut Self { + self.feature_flags = flags; + self + } + + /// Enable a single [feature flag](FeatureFlag). + #[inline] + pub fn enable_feature_flag(&mut self, flag: FeatureFlag) -> &mut Self { + self.feature_flags.insert(flag); + self + } + + /// Test for whether a specific feature flag is enabled. If the flag has been + /// stabilized, this will always return true. + #[must_use] + #[inline] + pub fn feature_flag_enabled(&self, flag: FeatureFlag) -> bool { + flag.stable || self.feature_flags.contains(&flag) + } + + /// Returns a set of all enabled feature flags. + #[must_use] + #[inline] + pub fn feature_flags(&self) -> &HashSet { + &self.feature_flags + } +} + +/// A feature flag for gating unstable `cargo-semver-checks` features. +/// +/// ## Feature-gating code +/// +/// To only execute a block of code when a given feature flag `flag` has been enabled, +/// wrap the block in `if config.feature_flag_enabled(flag)`, where `config` is the +/// program's [`GlobalConfig`]. +/// +/// ## Adding a new unstable feature flag +/// +/// Create a new associated constant in the `impl FeatureFlag` block with an identifier, +/// help message, and `stable: false`, and **add this constant** to the +/// [`ALL_FLAGS`](Self::ALL_FLAGS) slice. +/// +/// ## Stabilizing a feature flag +/// +/// Set `stable: true` on the associated constant for that flag. To keep the transition +/// from unstable flag to stable feature, mark the associated constant `#[deprecated]`, but +/// don't remove it. This will warn downstream code and `cargo-semver-checks` binary users +/// that the flag has been stabilized and may be removed in a future release, and hide it +/// from `-Z help`, without breaking downstream code and binary users right when it is stabilized. +/// +/// When you stabilize a flag and mark it as `#[deprecated]`, remove any `if` blocks +/// that feature-gate code. Testing if a flag is enabled with [`GlobalConfig::feature_flag_enabled`] +/// will always return `true` when a flag has been stabilized. +/// +/// ## See also +/// +/// - [`GlobalConfig::feature_flag_enabled] +/// - [`GlobalConfig::feature_flags] +/// - [`GlobalConfig::set_feature_flags] +/// - [`GlobalConfig::enable_feature_flag] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct FeatureFlag { + /// `kebab-case` identifier for this feature flag. + pub id: &'static str, + /// Optional help message for this flag. + /// + /// If this contains multiple lines. the first should be able to serve as + /// a one-line 'short help' when needed, and the full help string will be + /// used as a `long help`. + pub help: Option<&'static str>, + /// Whether this flag is stable and enabled by default. Stable flags may + /// be removed in future releases of `cargo-semver-checks` as the feature + /// is stabilized and feature-gated code is unconditionally executed. + pub stable: bool, +} + +impl FeatureFlag { + /// Print a list of the current unstable feature flags. + pub const HELP: Self = Self { + id: "help", + help: Some("Print a list of the current unstable feature flags"), + stable: false, + }; + + /// Enables the use of unstable CLI flags. + pub const UNSTABLE_OPTIONS: Self = Self { + id: "unstable-options", + help: Some( + "Enables the use of unstable CLI flags.\n\ + Run `cargo semver-checks -Z help` to list them", + ), + stable: false, + }; + + /// All feature flags that currently exist in `cargo-semver-checks`. + pub const ALL_FLAGS: &'static [Self] = &[Self::HELP, Self::UNSTABLE_OPTIONS]; +} + +impl ValueEnum for FeatureFlag { + #[inline] + fn value_variants<'a>() -> &'a [Self] { + Self::ALL_FLAGS + } + + fn to_possible_value(&self) -> Option { + Some( + clap::builder::PossibleValue::new(self.id) + .hide(self.stable) + .help(self.help), + ) + } } #[cfg(test)] @@ -495,4 +612,19 @@ mod tests { assert!(config.err_color_choice()); assert!(config.out_color_choice()); } + + #[test] + fn stable_flags_always_enabled() { + let config = GlobalConfig::new(); + assert!(config.feature_flag_enabled(FeatureFlag { + id: "ad-hoc", + help: None, + stable: true + })); + assert!(!config.feature_flag_enabled(FeatureFlag { + id: "ad-hoc", + help: None, + stable: false, + })); + } } diff --git a/src/lib.rs b/src/lib.rs index cfe5aadd..89f6c0c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ use std::path::{Path, PathBuf}; use std::sync::Arc; use std::time::Instant; -pub use config::GlobalConfig; +pub use config::{FeatureFlag, GlobalConfig}; pub use query::{ ActualSemverUpdate, LintLevel, OverrideMap, OverrideStack, QueryOverride, RequiredSemverUpdate, SemverQuery, @@ -378,7 +378,9 @@ impl Check { } pub fn check_release(&self, config: &mut GlobalConfig) -> anyhow::Result { - let rustdoc_cmd = RustdocCommand::new().deps(false).silence(config.is_info()); + let rustdoc_cmd = RustdocCommand::new() + .deps(false) + .silence(!config.is_verbose()); // If both the current and baseline rustdoc are given explicitly as a file path, // we don't need to use the installed rustc, and this check can be skipped. diff --git a/src/lints/enum_tuple_variant_changed_kind.ron b/src/lints/enum_tuple_variant_changed_kind.ron new file mode 100644 index 00000000..bbc12877 --- /dev/null +++ b/src/lints/enum_tuple_variant_changed_kind.ron @@ -0,0 +1,71 @@ +SemverQuery( + id: "enum_tuple_variant_changed_kind", + human_readable_name: "An enum tuple variant changed kind", + description: "A public enum tuple variant that isn't #[non_exhaustive] changed kind", + required_update: Major, + lint_level: Deny, + // TODO: If the Rust reference gains a more detailed explanation of enum *variants*, + // switch the explanation to point there instead. The current link isn't great. + reference_link: Some("https://doc.rust-lang.org/reference/items/enumerations.html"), + query: r#" + { + CrateDiff { + baseline { + item { + ... on Enum { + visibility_limit @filter(op: "=", value: ["$public"]) + enum_name: name @output @tag + + importable_path { + path @output @tag + public_api @filter(op: "=", value: ["$true"]) + } + + variant { + ... on TupleVariant { + kind: __typename @output @tag + attrs @filter(op: "not_contains", value: ["$non_exhaustive"]) + public_api_eligible @filter(op: "=", value: ["$true"]) + variant_name: name @output @tag + } + } + } + } + } + current { + item { + ... on Enum { + visibility_limit @filter(op: "=", value: ["$public"]) + name @filter(op: "=", value: ["%enum_name"]) + + importable_path { + path @filter(op: "=", value: ["%path"]) + public_api @filter(op: "=", value: ["$true"]) + } + + # Don't check for exhaustiveness and public API eligibility here. + # Variants that changed kind and gained `#[doc(hidden)]` should + # report for both lints, since there is no implied continuity + # between the old variant and the new one with the same name. + variant { + name @filter(op: "=", value: ["%variant_name"]) + new_kind: __typename @filter(op: "!=", value: ["%kind"]) @output + + span_: span @optional { + filename @output + begin_line @output + } + } + } + } + } + } + }"#, + arguments: { + "public": "public", + "true": true, + "non_exhaustive": "#[non_exhaustive]", + }, + error_message: "A public enum's exhaustive tuple variant has changed to a different kind of enum variant, breaking possible instantiations and patterns.", + per_result_error_template: Some("variant {{enum_name}}::{{variant_name}} in {{span_filename}}:{{span_begin_line}}"), +) diff --git a/src/lints/enum_unit_variant_changed_kind.ron b/src/lints/enum_unit_variant_changed_kind.ron new file mode 100644 index 00000000..7f213b54 --- /dev/null +++ b/src/lints/enum_unit_variant_changed_kind.ron @@ -0,0 +1,71 @@ +SemverQuery( + id: "enum_unit_variant_changed_kind", + human_readable_name: "An enum unit variant changed kind", + description: "A public enum unit variant that isn't #[non_exhaustive] changed kind", + required_update: Major, + lint_level: Deny, + // TODO: If the Rust reference gains a more detailed explanation of enum *variants*, + // switch the explanation to point there instead. The current link isn't great. + reference_link: Some("https://doc.rust-lang.org/reference/items/enumerations.html"), + query: r#" + { + CrateDiff { + baseline { + item { + ... on Enum { + visibility_limit @filter(op: "=", value: ["$public"]) + enum_name: name @output @tag + + importable_path { + path @output @tag + public_api @filter(op: "=", value: ["$true"]) + } + + variant { + ... on PlainVariant { + kind: __typename @output @tag + attrs @filter(op: "not_contains", value: ["$non_exhaustive"]) + public_api_eligible @filter(op: "=", value: ["$true"]) + variant_name: name @output @tag + } + } + } + } + } + current { + item { + ... on Enum { + visibility_limit @filter(op: "=", value: ["$public"]) + name @filter(op: "=", value: ["%enum_name"]) + + importable_path { + path @filter(op: "=", value: ["%path"]) + public_api @filter(op: "=", value: ["$true"]) + } + + # Don't check for exhaustiveness and public API eligibility here. + # Variants that changed kind and gained `#[doc(hidden)]` should + # report for both lints, since there is no implied continuity + # between the old variant and the new one with the same name. + variant { + name @filter(op: "=", value: ["%variant_name"]) + new_kind: __typename @filter(op: "!=", value: ["%kind"]) @output + + span_: span @optional { + filename @output + begin_line @output + } + } + } + } + } + } + }"#, + arguments: { + "public": "public", + "true": true, + "non_exhaustive": "#[non_exhaustive]", + }, + error_message: "A public enum's exhaustive unit variant has changed to a different kind of enum variant, breaking possible instantiations and patterns.", + per_result_error_template: Some("variant {{enum_name}}::{{variant_name}} in {{span_filename}}:{{span_begin_line}}"), +) diff --git a/src/lints/variant_marked_non_exhaustive.ron b/src/lints/enum_variant_marked_non_exhaustive.ron similarity index 98% rename from src/lints/variant_marked_non_exhaustive.ron rename to src/lints/enum_variant_marked_non_exhaustive.ron index fb354dcd..ca806b36 100644 --- a/src/lints/variant_marked_non_exhaustive.ron +++ b/src/lints/enum_variant_marked_non_exhaustive.ron @@ -1,5 +1,5 @@ SemverQuery( - id: "variant_marked_non_exhaustive", + id: "enum_variant_marked_non_exhaustive", human_readable_name: "enum variant marked #[non_exhaustive]", description: "An exhaustive enum variant has been marked #[non_exhaustive].", reference: Some("An exhaustive enum variant has been marked #[non_exhaustive], preventing it from being constructed using a literal from outside its own crate."), diff --git a/src/lints/trait_method_added.ron b/src/lints/trait_method_added.ron new file mode 100644 index 00000000..c5ebd43b --- /dev/null +++ b/src/lints/trait_method_added.ron @@ -0,0 +1,60 @@ +SemverQuery( + id: "trait_method_added", + human_readable_name: "pub trait method added", + description: "A non-sealed public trait added a new method without a default implementation", + required_update: Major, + lint_level: Deny, + reference_link: Some("https://doc.rust-lang.org/cargo/reference/semver.html#trait-new-item-no-default"), + query: r#" + { + CrateDiff { + current { + item { + ... on Trait { + visibility_limit @filter(op: "=", value: ["$public"]) @output + name @output + + importable_path { + path @output @tag + public_api @filter(op: "=", value: ["$true"]) + } + + method { + method_name: name @output @tag + has_body @filter(op: "!=", value: ["$true"]) + + span_: span @optional { + filename @output + begin_line @output + } + } + } + } + } + baseline { + item { + ... on Trait { + visibility_limit @filter(op: "=", value: ["$public"]) + sealed @filter(op: "!=", value: ["$true"]) + + importable_path { + path @filter(op: "=", value: ["%path"]) + public_api @filter(op: "=", value: ["$true"]) + } + + method @fold @transform(op: "count") @filter(op: "=", value: ["$zero"]) { + name @filter(op: "=", value: ["%method_name"]) + } + } + } + } + } + }"#, + arguments: { + "true": true, + "public": "public", + "zero": 0, + }, + error_message: "A non-sealed public trait added a new method without a default implementation, which breaks downstream implementations of the trait", + per_result_error_template: Some("trait method {{join \"::\" path}}::{{method_name}} in file {{span_filename}}:{{span_begin_line}}"), +) diff --git a/src/lints/trait_default_impl_removed.ron b/src/lints/trait_method_default_impl_removed.ron similarity index 98% rename from src/lints/trait_default_impl_removed.ron rename to src/lints/trait_method_default_impl_removed.ron index 4af6a117..cbde9607 100644 --- a/src/lints/trait_default_impl_removed.ron +++ b/src/lints/trait_method_default_impl_removed.ron @@ -1,5 +1,5 @@ SemverQuery( - id: "trait_default_impl_removed", + id: "trait_method_default_impl_removed", human_readable_name: "pub trait default method impl removed", description: "A non-sealed public trait default method impl was removed", required_update: Major, diff --git a/src/main.rs b/src/main.rs index d9e0ea7f..6ed9d5ae 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,13 @@ #![forbid(unsafe_code)] -use std::{env, path::PathBuf}; +use std::{collections::HashSet, env, path::PathBuf}; use anstyle::{AnsiColor, Color, Reset, Style}; use cargo_config2::Config; use cargo_semver_checks::{ - GlobalConfig, PackageSelection, ReleaseType, Rustdoc, ScopeSelection, SemverQuery, + FeatureFlag, GlobalConfig, PackageSelection, ReleaseType, Rustdoc, ScopeSelection, SemverQuery, }; -use clap::{Args, Parser, Subcommand}; +use clap::{Args, CommandFactory, Parser, Subcommand}; use std::io::Write; #[cfg(test)] @@ -18,17 +18,23 @@ fn main() { let Cargo::SemverChecks(args) = Cargo::parse(); + let feature_flags = HashSet::from_iter(args.unstable_features.clone()); + configure_color(args.color_choice); let mut config = GlobalConfig::new(); + config.set_log_level(args.verbosity.log_level()); + config.set_feature_flags(feature_flags); + + exit_on_error(true, || validate_feature_flags(&mut config, &args)); + // --bugreport: generate a bug report URL if args.bugreport { print_issue_url(&mut config); std::process::exit(0); - } else if args.list { + } + // --list: print a list of all lints + else if args.list { exit_on_error(true, || { - let mut config = GlobalConfig::new(); - config.set_log_level(args.check_release.verbosity.log_level()); - let queries = SemverQuery::all_queries(); let mut rows = vec![["id", "type", "description"], ["==", "====", "==========="]]; for query in queries.values() { @@ -58,7 +64,9 @@ fn main() { config.shell_note("Use `--explain ` to see more details") }); std::process::exit(0); - } else if let Some(id) = args.explain.as_deref() { + } + // --explain ID: print detailed information about a lint + else if let Some(id) = args.explain.as_deref() { exit_on_error(true, || { let queries = SemverQuery::all_queries(); let query = queries.get(id).ok_or_else(|| { @@ -84,14 +92,70 @@ fn main() { }); std::process::exit(0); } + // -Z help: print information on all unstable FeatureFlags (-Z flag) + else if config.feature_flag_enabled(FeatureFlag::HELP) { + config + .log_info(|config| { + let header = Style::new() + .bold() + .fg_color(Some(Color::Ansi(AnsiColor::Cyan))); + let option = Style::new().bold(); + + let mut stdout = config.stdout(); + + writeln!(stdout, "{header}Unstable feature flags:{header:#}")?; + writeln!(stdout, "{header}{:<20}{header:#}Description", "-Z name",)?; + + for flag in FeatureFlag::ALL_FLAGS.iter().filter(|x| !x.stable) { + write!(stdout, "{option}{:<20}{option:#}", flag.id)?; + + if let Some(help) = flag.help { + let mut lines = help.lines(); + + if let Some(first) = lines.next() { + writeln!(stdout, "{first}")?; + + for line in lines { + writeln!(stdout, "{:<20}{line}", "")?; + } + } + } else { + writeln!(stdout)?; + } + } + + // helper struct for rendering help for just the unstable options. + #[derive(Parser)] + #[clap( + disable_help_flag = true, + help_template = "{options}", + mut_args = |arg| arg.hide(false), + )] + struct HelpPrinter { + #[command(flatten)] + args: UnstableOptions, + } + + write!( + stdout, + "{header}Unstable options:{header:#}\n\ + {}", + HelpPrinter::command().render_long_help() + ) + .expect("print failed"); + + Ok(()) + }) + .expect("write failed"); + + std::process::exit(0); + } let check_release = match args.command { Some(SemverChecksCommands::CheckRelease(c)) => c, None => args.check_release, }; - config.set_log_level(check_release.verbosity.log_level()); - let check: cargo_semver_checks::Check = check_release.into(); let report = exit_on_error(config.is_error(), || check.check_release(&mut config)); @@ -241,6 +305,69 @@ struct SemverChecks { /// Choose whether to output colors #[arg(long = "color", global = true, value_name = "WHEN", value_enum)] color_choice: Option, + + // docstring for help is on the `clap_verbosity_flag::Verbosity` struct itself + #[command(flatten)] + verbosity: clap_verbosity_flag::Verbosity, + + /// Enable unstable feature flags, run `cargo semver-checks -Z help` for more help. + #[arg( + short = 'Z', + value_name = "FLAG", + global = true, + hide_possible_values = true // show explictly with -Z help + )] + unstable_features: Vec, +} + +/// Encapsulated unstable CLI flags. These will only be used if +/// `-Z unstable-options` is passed to `cargo-semver-checks`. +/// +/// Note for adding arguments: make sure your added argument has a default value to detect +/// when arguments are passed without `-Z unstable-options`, so make sure the behavior +/// when the arg is its default value is the same as the behavior on stable +/// `cargo-semver-checks` when this flag is not passed. +/// +/// Also make sure to add `#[arg(hide = true)]` to your argument so it doesn't show +/// up in stable help when it is not valid. Users can run +/// `cargo semver-checks -Z help` to show help messages +/// instead, so a docstring help message will be shown then. +#[derive(Debug, Clone, Args, Default, PartialEq, Eq)] +#[clap(hide = true)] +#[non_exhaustive] +struct UnstableOptions { + /// Enable printing witness hints, examples of potentially-broken downstream code. + #[arg(long, hide = true)] + witness_hints: bool, +} + +impl UnstableOptions { + /// Returns a list of command line flags set when fields in this struct are + /// not their default values, used for detecting and printing when unstable options + /// are set without `-Z unstable-options`. + /// + /// When you add a new unstable option, the exhaustive let pattern below will not compile. + /// Fix this by adding the new field to the let pattern, then adding a similar if statement + /// to the ones below to detect when the field is not its default value, and insert the + /// command line flag that caused this into the list. See the implementation + /// for examples. + /// + /// When you remove an unstable option (e.g., to stabilize it), remove the field from + /// the match pattern, and remove the if block corresponding to that struct field. + #[must_use] + fn non_default(&self) -> Vec { + let mut list = Vec::new(); + + // If this has a compilation error from adding or removing fields, see this function's + // docstring for how to fix this function's implementation. + let Self { witness_hints } = self; + + if *witness_hints { + list.push("--witness-hints".into()); + } + + list + } } /// Check your crate for semver violations. @@ -357,17 +484,31 @@ struct CheckRelease { /// Add a feature to the set of features being checked. /// The feature will be used in both the baseline and the current version /// of the crate. - #[arg(long, value_name = "NAME", help_heading = "Features")] + #[arg( + long, + value_delimiter = ',', + value_name = "NAME", + help_heading = "Features" + )] features: Vec, /// Add a feature to the set of features being checked. /// The feature will be used in the baseline version of the crate only. - #[arg(long, value_name = "NAME", help_heading = "Features")] + #[arg( + long, + value_delimiter = ',', + value_name = "NAME", + help_heading = "Features" + )] baseline_features: Vec, - /// Add a feature to the set of features being checked. /// The feature will be used in the current version of the crate only. - #[arg(long, value_name = "NAME", help_heading = "Features")] + #[arg( + long, + value_delimiter = ',', + value_name = "NAME", + help_heading = "Features" + )] current_features: Vec, /// Use all the features, including features named @@ -391,9 +532,8 @@ struct CheckRelease { #[arg(long = "target")] build_target: Option, - // docstring for help is on the `clap_verbosity_flag::Verbosity` struct itself - #[command(flatten)] - verbosity: clap_verbosity_flag::Verbosity, + #[clap(flatten)] + unstable_options: UnstableOptions, } impl From for cargo_semver_checks::Check { @@ -491,6 +631,54 @@ impl From for cargo_semver_checks::Check { } } +/// Helper function to encapsulate the logic of validating that unstable options +/// were not used without `-Z unstable-options` and issuing deprecation warnings +/// for any stable feature flags that were explicitly specified. +fn validate_feature_flags(config: &mut GlobalConfig, args: &SemverChecks) -> anyhow::Result<()> { + // needed to avoid borrow checker errors when printing with config. + let stable_flags: Vec<_> = config + .feature_flags() + .iter() + .filter(|x| x.stable) + .copied() + .collect(); + + for stable_flag in stable_flags { + config + .shell_warn(format_args!( + "the feature flag {} has been stabilized and may be removed + from the list of feature flags in a future release.", + stable_flag.id + )) + .expect("printing failed"); + } + + if !config.feature_flag_enabled(FeatureFlag::UNSTABLE_OPTIONS) { + let unstable_options = match &args.command { + Some(SemverChecksCommands::CheckRelease(cr)) => &cr.unstable_options, + None => &args.check_release.unstable_options, + }; + + let non_default_options = unstable_options.non_default(); + + if !non_default_options.is_empty() { + let mut message = String::from( + "the following options are not supported without `-Z unstable-options`:\n", + ); + + for option in non_default_options { + use std::fmt::Write as _; + + writeln!(&mut message, " - `{option}`").expect("writes to strings are infallible"); + } + + anyhow::bail!(message); + } + } + + Ok(()) +} + #[test] fn verify_cli() { use clap::CommandFactory; @@ -515,3 +703,34 @@ fn features_empty_string_is_no_op() { assert_eq!(Check::from(no_features), Check::from(empty_features)); } + +/// Test to assert that all flags added to the [`UnstableOptions`] are +/// hidden and won't show up in stable `--help`. +#[test] +fn all_unstable_features_are_hidden() { + // Helper struct to get a `Command` to use reflection on the unstable options. + #[derive(Debug, Parser)] + struct Wrapper { + #[clap(flatten)] + inner: UnstableOptions, + } + + let unstable_options = Wrapper::command(); + let cargo_command = Cargo::command(); + let semver_checks = cargo_command + .find_subcommand("semver-checks") + .expect("expected semver-checks command"); + + for option in unstable_options.get_arguments() { + let argument = semver_checks + .get_arguments() + .find(|x| x.get_id() == option.get_id()) + .expect("expected unstable argument"); + + assert!( + argument.is_hide_set(), + "unstable argument {} should be hidden by default", + argument.get_id() + ); + } +} diff --git a/src/query.rs b/src/query.rs index db58ad1f..f7eb4412 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,5 +1,6 @@ use std::{collections::BTreeMap, sync::Arc}; +use ron::extensions::Extensions; use serde::{Deserialize, Serialize}; use trustfall::TransparentValue; @@ -125,16 +126,28 @@ pub struct SemverQuery { } impl SemverQuery { + /// Deserializes a [`SemverQuery`] from a [`ron`]-encoded string slice. + /// + /// Returns an `Err` if the deserialization fails. + pub fn from_ron_str(query_text: &str) -> ron::Result { + let mut deserializer = ron::Deserializer::from_str_with_options( + query_text, + ron::Options::default().with_default_extension(Extensions::IMPLICIT_SOME), + )?; + + Self::deserialize(&mut deserializer) + } + pub fn all_queries() -> BTreeMap { let mut queries = BTreeMap::default(); for (id, query_text) in get_queries() { - let query: SemverQuery = ron::from_str(query_text).unwrap_or_else(|e| { + let query = Self::from_ron_str(query_text).unwrap_or_else(|e| { panic!( "\ -Failed to parse a query: {e} -```ron -{query_text} -```" + Failed to parse a query: {e} + ```ron + {query_text} + ```" ); }); assert_eq!(id, query.id, "Query id must match file name"); @@ -462,7 +475,7 @@ mod tests { pub(in crate::query) fn check_query_execution(query_name: &str) { let query_text = std::fs::read_to_string(format!("./src/lints/{query_name}.ron")).unwrap(); - let semver_query: SemverQuery = ron::from_str(&query_text).unwrap(); + let semver_query = SemverQuery::from_ron_str(&query_text).unwrap(); let expected_result_text = std::fs::read_to_string(format!("./test_outputs/{query_name}.output.ron")) @@ -792,10 +805,13 @@ add_lints!( enum_struct_variant_field_added, enum_struct_variant_field_missing, enum_struct_variant_field_now_doc_hidden, + enum_tuple_variant_changed_kind, enum_tuple_variant_field_added, enum_tuple_variant_field_missing, enum_tuple_variant_field_now_doc_hidden, + enum_unit_variant_changed_kind, enum_variant_added, + enum_variant_marked_non_exhaustive, enum_variant_missing, exported_function_changed_abi, function_abi_no_longer_unwind, @@ -840,7 +856,8 @@ add_lints!( trait_associated_type_added, trait_associated_type_default_removed, trait_associated_type_now_doc_hidden, - trait_default_impl_removed, + trait_method_default_impl_removed, + trait_method_added, trait_method_missing, trait_method_now_doc_hidden, trait_method_unsafe_added, @@ -863,5 +880,4 @@ add_lints!( union_now_doc_hidden, union_pub_field_now_doc_hidden, unit_struct_changed_kind, - variant_marked_non_exhaustive, ); diff --git a/src/rustdoc_cmd.rs b/src/rustdoc_cmd.rs index 39047ea9..507448f4 100644 --- a/src/rustdoc_cmd.rs +++ b/src/rustdoc_cmd.rs @@ -53,6 +53,10 @@ impl RustdocCommand { crate_source: &CrateSource, crate_data: &CrateDataForRustdoc, ) -> anyhow::Result { + let crate_name = crate_source.name()?; + let version = crate_source.version()?; + let pkg_spec = format!("{crate_name}@{version}"); + // Generate an empty placeholder project with a dependency on the crate // whose rustdoc we need. We take this indirect generation path to avoid issues like: // https://github.com/obi1kenobi/cargo-semver-checks/issues/167#issuecomment-1382367128 @@ -62,6 +66,100 @@ impl RustdocCommand { let placeholder_manifest_path = save_placeholder_rustdoc_manifest(build_dir.as_path(), placeholder_manifest) .context("failed to save placeholder rustdoc manifest")?; + if matches!(crate_source, CrateSource::ManifestPath { .. }) { + // We have to run `cargo update` inside the newly-generated project, to ensure + // all dependencies of the library we're scanning are up-to-date. + // + // Otherwise, we risk having a newer version of a dependency in the baseline arm, + // and an older version of the same dependency in the current arm. If that dependency + // started providing stronger guarantees in the newer version, such as newly starting + // to implementing an auto-trait on an existing type, the baseline could contain + // types that inherit that stronger guarantee whereas the current would not. + // That would be reported as a breaking change -- incorrectly so. + // + // cargo does not guarantee it'll update dependencies to a newer lockfile + // if using a path dependency. This bit us in this case: + // https://github.com/obi1kenobi/cargo-semver-checks/issues/167#issuecomment-2324959305 + let stderr = if self.silence { + std::process::Stdio::piped() + } else { + // Print cargo update progress + std::process::Stdio::inherit() + }; + + let mut cmd = std::process::Command::new("cargo"); + cmd.stdout(std::process::Stdio::null()) // Don't pollute output + .stderr(stderr) + .current_dir(build_dir.as_path()) + .arg("update") + .arg("--manifest-path") + .arg(&placeholder_manifest_path); + + // Respect our configured color choice. + cmd.arg(if config.err_color_choice() { + "--color=always" + } else { + "--color=never" + }); + + let output = cmd.output()?; + if !output.status.success() { + if self.silence { + config.log_error(|config| { + let mut stderr = config.stderr(); + let delimiter = "-----"; + writeln!( + stderr, + "error: running 'cargo update' on crate {crate_name} failed with output:" + )?; + writeln!( + stderr, + "{delimiter}\n{}\n{delimiter}\n", + String::from_utf8_lossy(&output.stderr) + )?; + writeln!( + stderr, + "error: failed to update dependencies for crate {crate_name} v{version}" + )?; + Ok(()) + })?; + } else { + config.log_error(|config| { + writeln!( + config.stderr(), + "error: running 'cargo update' on crate {crate_name} v{version} failed, see stderr output above" + )?; + Ok(()) + })?; + } + config.log_error(|config| { + let features = + crate_source.feature_list_from_config(config, crate_data.feature_config); + let mut stderr = config.stderr(); + writeln!( + stderr, + "note: this is unlikely to be a bug in cargo-semver-checks," + )?; + writeln!( + stderr, + " and is probably an issue with the crate's Cargo.toml" + )?; + writeln!( + stderr, + "note: the following command can be used to reproduce the compilation error:" + )?; + let repro_base = produce_repro_workspace(crate_name, crate_source, &features); + writeln!( + stderr, + "{repro_base}cargo update\n" + )?; + Ok(()) + })?; + anyhow::bail!( + "aborting due to failure to run 'cargo update' for crate {crate_name} v{version}" + ); + } + } let metadata = cargo_metadata::MetadataCommand::new() .manifest_path(&placeholder_manifest_path) @@ -82,10 +180,6 @@ impl RustdocCommand { std::process::Stdio::inherit() }; - let crate_name = crate_source.name()?; - let version = crate_source.version()?; - let pkg_spec = format!("{crate_name}@{version}"); - // Generating rustdoc JSON for a crate also involves checking that crate's dependencies. // The check is done by rustc, not rustdoc, so it's subject to RUSTFLAGS not RUSTDOCFLAGS. // We don't want rustc to fail that check if the user has set RUSTFLAGS="-Dwarnings" here. @@ -130,7 +224,7 @@ impl RustdocCommand { cmd.arg("--no-deps"); } - // respect our configured color choice + // Respect our configured color choice cmd.arg(if config.err_color_choice() { "--color=always" } else { @@ -183,32 +277,8 @@ impl RustdocCommand { stderr, "note: the following command can be used to reproduce the compilation error:" )?; - let selector = match crate_source { - CrateSource::Registry { version, .. } => format!("{crate_name}@={version}"), - CrateSource::ManifestPath { manifest } => format!( - "--path {}", - manifest - .path - .parent() - .expect("source Cargo.toml had no parent path") - .to_str() - .expect("failed to create path string") - ), - }; - let feature_list = if features.is_empty() { - "".to_string() - } else { - format!("--features {} ", features.into_iter().join(",")) - }; - writeln!( - stderr, - " \ -cargo new --lib example && - cd example && - echo '[workspace]' >> Cargo.toml && - cargo add {selector} --no-default-features {feature_list}&& - cargo check\n" - )?; + let repro_base = produce_repro_workspace(crate_name, crate_source, &features); + writeln!(stderr, "{repro_base}cargo check\n")?; Ok(()) })?; anyhow::bail!( @@ -345,6 +415,38 @@ in the metadata and stderr didn't mention it was lacking a lib target. This is p } } +fn produce_repro_workspace( + crate_name: &str, + crate_source: &CrateSource, + features: &[String], +) -> String { + let selector = match crate_source { + CrateSource::Registry { version, .. } => format!("{crate_name}@={version}"), + CrateSource::ManifestPath { manifest } => format!( + "--path {}", + manifest + .path + .parent() + .expect("source Cargo.toml had no parent path") + .to_str() + .expect("failed to create path string") + ), + }; + let feature_list = if features.is_empty() { + "".to_string() + } else { + format!("--features {} ", features.iter().join(",")) + }; + format!( + " \ + cargo new --lib example && + cd example && + echo '[workspace]' >> Cargo.toml && + cargo add {selector} --no-default-features {feature_list}&& + " + ) +} + impl Default for RustdocCommand { fn default() -> Self { Self::new() diff --git a/src/snapshot_tests.rs b/src/snapshot_tests.rs index deb6e550..471d6e4c 100644 --- a/src/snapshot_tests.rs +++ b/src/snapshot_tests.rs @@ -153,7 +153,7 @@ fn assert_integration_test(test_name: &str, invocation: &[&str]) { config.set_stdout(Box::new(stdout.clone())); config.set_stderr(Box::new(stderr.clone())); config.set_color_choice(false); - config.set_log_level(arguments.check_release.verbosity.log_level()); + config.set_log_level(arguments.verbosity.log_level()); let check = Check::from(arguments.check_release); @@ -172,6 +172,12 @@ fn assert_integration_test(test_name: &str, invocation: &[&str]) { // Remove cargo blocking lines (e.g. from `cargo doc` output) as the amount of blocks // is not reproducible. settings.add_filter(" Blocking waiting for file lock on package cache\n", ""); + // Filter out the current `cargo-semver-checks` version in links to lint references, + // as this will break across version changes. + settings.add_filter( + r"v\d+\.\d+\.\d+(-[\w\.-]+)?/src/lints", + "[VERSION]/src/lints", + ); // The `settings` are applied to the current thread as long as the returned // drop guard `_grd` is alive, so we use a `let` binding to keep it alive diff --git a/test_crates/enum_tuple_variant_changed_kind/new/Cargo.toml b/test_crates/enum_tuple_variant_changed_kind/new/Cargo.toml new file mode 100644 index 00000000..194c053e --- /dev/null +++ b/test_crates/enum_tuple_variant_changed_kind/new/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "enum_tuple_variant_changed_kind" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/enum_tuple_variant_changed_kind/new/src/lib.rs b/test_crates/enum_tuple_variant_changed_kind/new/src/lib.rs new file mode 100644 index 00000000..db1e42d8 --- /dev/null +++ b/test_crates/enum_tuple_variant_changed_kind/new/src/lib.rs @@ -0,0 +1,63 @@ +// ---- Should be reported ---- +pub enum TestStruct { + WillBecomeStructLike{} +} + +pub enum TestUnit { + WillBecomeUnitLike +} + +pub enum MultipleTest { + WillBecomeStructLike{}, + WillBecomeUnitLike, + WillStayTupleLike(()), +} + +pub enum TestBecomeDocHidden { + #[doc(hidden)] + WillBecomeStructLike{} +} + +pub enum TestBecomeNonExhaustive { + #[non_exhaustive] + WillBecomeStructLike{} +} + +// ---- Should not be reported ---- +pub enum TestTuple { + WillStayTupleLike(()) +} + +pub enum TestStructNonExhaustive { + #[non_exhaustive] + WillBecomeStructLike{} +} + +pub enum TestUnitNonExhaustive { + #[non_exhaustive] + WillBecomeUnitLike +} + +pub enum MultipleTestNonExhaustive { + #[non_exhaustive] + WillBecomeStructLike{}, + #[non_exhaustive] + WillBecomeUnitLike, + #[non_exhaustive] + WillStayTupleLike(()), +} + +pub enum TestDocHidden { + #[doc(hidden)] + WillBecomeStructLike{}, + #[doc(hidden)] + WillBecomeUnitLike, + #[doc(hidden)] + WillStayTupleLike(()), +} + +pub enum MultipleStayTheSame { + StructLike{}, + TupleLike(()), + UnitLike +} diff --git a/test_crates/enum_tuple_variant_changed_kind/old/Cargo.toml b/test_crates/enum_tuple_variant_changed_kind/old/Cargo.toml new file mode 100644 index 00000000..194c053e --- /dev/null +++ b/test_crates/enum_tuple_variant_changed_kind/old/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "enum_tuple_variant_changed_kind" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/enum_tuple_variant_changed_kind/old/src/lib.rs b/test_crates/enum_tuple_variant_changed_kind/old/src/lib.rs new file mode 100644 index 00000000..4142cfe0 --- /dev/null +++ b/test_crates/enum_tuple_variant_changed_kind/old/src/lib.rs @@ -0,0 +1,61 @@ +// ---- Should be reported ---- +pub enum TestStruct { + WillBecomeStructLike(()) +} + +pub enum TestUnit { + WillBecomeUnitLike(()) +} + +pub enum MultipleTest { + WillBecomeStructLike(()), + WillBecomeUnitLike(()), + WillStayTupleLike(()), +} + +pub enum TestBecomeDocHidden { + WillBecomeStructLike(()) +} + +pub enum TestBecomeNonExhaustive { + WillBecomeStructLike(()) +} + +// ---- Should not be reported ---- +pub enum TestTuple { + WillStayTupleLike(()) +} + +pub enum TestStructNonExhaustive { + #[non_exhaustive] + WillBecomeStructLike(()) +} + +pub enum TestUnitNonExhaustive { + #[non_exhaustive] + WillBecomeUnitLike +} + +pub enum MultipleTestNonExhaustive { + #[non_exhaustive] + WillBecomeStructLike(()), + #[non_exhaustive] + WillBecomeUnitLike(()), + #[non_exhaustive] + WillStayTupleLike(()), +} + +pub enum TestDocHidden { + #[doc(hidden)] + WillBecomeStructLike(()), + #[doc(hidden)] + WillBecomeUnitLike(()), + #[doc(hidden)] + WillStayTupleLike(()), +} + +pub enum MultipleStayTheSame { + StructLike{}, + TupleLike(()), + UnitLike +} diff --git a/test_crates/enum_unit_variant_changed_kind/new/Cargo.toml b/test_crates/enum_unit_variant_changed_kind/new/Cargo.toml new file mode 100644 index 00000000..990b12be --- /dev/null +++ b/test_crates/enum_unit_variant_changed_kind/new/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "enum_unit_variant_changed_kind" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/enum_unit_variant_changed_kind/new/src/lib.rs b/test_crates/enum_unit_variant_changed_kind/new/src/lib.rs new file mode 100644 index 00000000..79501a97 --- /dev/null +++ b/test_crates/enum_unit_variant_changed_kind/new/src/lib.rs @@ -0,0 +1,63 @@ +// ---- Should be reported ---- +pub enum TestStruct { + WillBecomeStructLike{} +} + +pub enum TestTuple { + WillBecomeTupleLike(()) +} + +pub enum MultipleTest { + WillBecomeStructLike{}, + WillBecomeTupleLike(()), + WillStayUnitLike +} + +pub enum TestBecomeDocHidden { + #[doc(hidden)] + WillBecomeStructLike{} +} + +pub enum TestBecomeNonExhaustive { + #[non_exhaustive] + WillBecomeStructLike{} +} + +// ---- Should not be reported ---- +pub enum TestUnit { + WillStayUnitLike +} + +pub enum TestStructNonExhaustive { + #[non_exhaustive] + WillBecomeStructLike{} +} + +pub enum TestTupleNonExhaustive { + #[non_exhaustive] + WillBecomeTupleLike(()) +} + +pub enum MultipleTestNonExhaustive { + #[non_exhaustive] + WillBecomeStructLike{}, + #[non_exhaustive] + WillBecomeTupleLike(()), + #[non_exhaustive] + WillStayUnitLike +} + +pub enum TestDocHidden { + #[doc(hidden)] + WillBecomeStructLike{}, + #[doc(hidden)] + WillBecomeTupleLike(()), + #[doc(hidden)] + WillStayUnitLike +} + +pub enum MultipleStayTheSame { + StructLike{}, + TupleLike(()), + UnitLike +} diff --git a/test_crates/enum_unit_variant_changed_kind/old/Cargo.toml b/test_crates/enum_unit_variant_changed_kind/old/Cargo.toml new file mode 100644 index 00000000..990b12be --- /dev/null +++ b/test_crates/enum_unit_variant_changed_kind/old/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "enum_unit_variant_changed_kind" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/enum_unit_variant_changed_kind/old/src/lib.rs b/test_crates/enum_unit_variant_changed_kind/old/src/lib.rs new file mode 100644 index 00000000..68af78a8 --- /dev/null +++ b/test_crates/enum_unit_variant_changed_kind/old/src/lib.rs @@ -0,0 +1,61 @@ +// ---- Should be reported ---- +pub enum TestStruct { + WillBecomeStructLike +} + +pub enum TestTuple { + WillBecomeTupleLike +} + +pub enum MultipleTest { + WillBecomeStructLike, + WillBecomeTupleLike, + WillStayUnitLike +} + +pub enum TestBecomeDocHidden { + WillBecomeStructLike +} + +pub enum TestBecomeNonExhaustive { + WillBecomeStructLike +} + +// ---- Should not be reported ---- +pub enum TestUnit { + WillStayUnitLike +} + +pub enum TestStructNonExhaustive { + #[non_exhaustive] + WillBecomeStructLike +} + +pub enum TestTupleNonExhaustive { + #[non_exhaustive] + WillBecomeTupleLike +} + +pub enum MultipleTestNonExhaustive { + #[non_exhaustive] + WillBecomeStructLike, + #[non_exhaustive] + WillBecomeTupleLike, + #[non_exhaustive] + WillStayUnitLike +} + +pub enum TestDocHidden { + #[doc(hidden)] + WillBecomeStructLike, + #[doc(hidden)] + WillBecomeTupleLike, + #[doc(hidden)] + WillStayUnitLike +} + +pub enum MultipleStayTheSame { + StructLike{}, + TupleLike(()), + UnitLike +} diff --git a/test_crates/feature_flags_validation/new/Cargo.toml b/test_crates/feature_flags_validation/new/Cargo.toml new file mode 100644 index 00000000..80e0302e --- /dev/null +++ b/test_crates/feature_flags_validation/new/Cargo.toml @@ -0,0 +1,19 @@ +[package] +publish = false +name = "feature_flags_validation" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[features] +default = ["std", "alloc"] +foo = [] +std = [] +bar = [] +alloc = [] +unstable = [] +nightly = [] +bench = [] +no_std = [] +__foo = [] diff --git a/test_crates/feature_flags_validation/new/src/lib.rs b/test_crates/feature_flags_validation/new/src/lib.rs new file mode 100644 index 00000000..5ce39bbc --- /dev/null +++ b/test_crates/feature_flags_validation/new/src/lib.rs @@ -0,0 +1,8 @@ +#[cfg(feature = "foo")] +pub fn foo_becomes_gated() {} + +#[cfg(feature = "bar")] +pub fn bar_becomes_gated() {} + +#[cfg(any(feature = "unstable", feature = "nightly",))] +pub fn unstable_function() {} diff --git a/test_crates/feature_flags_validation/old/Cargo.toml b/test_crates/feature_flags_validation/old/Cargo.toml new file mode 100644 index 00000000..3e4bffa3 --- /dev/null +++ b/test_crates/feature_flags_validation/old/Cargo.toml @@ -0,0 +1,20 @@ +[package] +publish = false +name = "feature_flags_validation" +version = "0.1.0" +edition = "2021" + +[dependencies] + +[features] +default = ["std", "alloc"] +std = [] +alloc = [] +unstable = [] +nightly = [] +bench = [] +no_std = [] +__foo = [] +unstable-foo=[] +unstable_foo=[] +_bar=[] diff --git a/test_crates/feature_flags_validation/old/src/lib.rs b/test_crates/feature_flags_validation/old/src/lib.rs new file mode 100644 index 00000000..cdcf404c --- /dev/null +++ b/test_crates/feature_flags_validation/old/src/lib.rs @@ -0,0 +1,17 @@ +#[cfg(not(all(feature = "std", feature = "alloc")))] +compile_error!("`std` and `alloc` features are currently required to build this awesome crate"); + +pub fn foo_becomes_gated() {} +pub fn bar_becomes_gated() {} + +#[cfg(any( + feature = "unstable", + feature = "nightly", + feature = "bench", + feature = "no_std", + feature = "__foo", + feature = "unstable-foo", + feature = "unstable_foo", + feature = "_bar" +))] +pub fn unstable_function() {} diff --git a/test_crates/trait_default_impl_removed/new/Cargo.toml b/test_crates/trait_method_added/new/Cargo.toml similarity index 68% rename from test_crates/trait_default_impl_removed/new/Cargo.toml rename to test_crates/trait_method_added/new/Cargo.toml index f3a5cc86..878fba75 100644 --- a/test_crates/trait_default_impl_removed/new/Cargo.toml +++ b/test_crates/trait_method_added/new/Cargo.toml @@ -1,6 +1,6 @@ [package] publish = false -name = "trait_default_impl_removed" +name = "trait_method_added" version = "0.1.0" edition = "2021" diff --git a/test_crates/trait_method_added/new/src/lib.rs b/test_crates/trait_method_added/new/src/lib.rs new file mode 100644 index 00000000..564a786a --- /dev/null +++ b/test_crates/trait_method_added/new/src/lib.rs @@ -0,0 +1,49 @@ +mod sealed { + pub(crate) trait Sealed {} +} + +// ---- Should be reported ---- +pub trait WillGainMethodWithoutDefault { + fn one_method(self); +} + +pub trait WillGainAnotherMethodWithoutDefault { + fn one_method(self); + fn two_method(self); +} + +pub trait WillGainMultipleMethodsWithoutDefault { + fn one_method(self); + fn two_method(self); +} + +pub trait WillGainMethodWithoutDefaultAndSeal: sealed::Sealed { + fn one_method(self); +} + +pub trait WIllGainDocHiddenMethodWithoutDefault { + #[doc(hidden)] + fn one_method(self); +} + +// ---- Should not be reported ---- +pub trait WillGainMethodWithDefault { + fn one_method(self) {} +} + +pub trait WillGainAnotherMethodWithDefault { + fn one_method(self); + fn two_method(self) {} +} + +pub trait WillGainMethodWithoutDefaultSealed: sealed::Sealed { + fn one_method(self); +} + +pub trait WillGainMethodWithoutDefaultAndLoseSeal { + fn one_method(self); +} + +pub trait WillKeepAMethodWithoutDefault { + fn one_method(self); +} diff --git a/test_crates/trait_default_impl_removed/old/Cargo.toml b/test_crates/trait_method_added/old/Cargo.toml similarity index 68% rename from test_crates/trait_default_impl_removed/old/Cargo.toml rename to test_crates/trait_method_added/old/Cargo.toml index f3a5cc86..878fba75 100644 --- a/test_crates/trait_default_impl_removed/old/Cargo.toml +++ b/test_crates/trait_method_added/old/Cargo.toml @@ -1,6 +1,6 @@ [package] publish = false -name = "trait_default_impl_removed" +name = "trait_method_added" version = "0.1.0" edition = "2021" diff --git a/test_crates/trait_method_added/old/src/lib.rs b/test_crates/trait_method_added/old/src/lib.rs new file mode 100644 index 00000000..004952ea --- /dev/null +++ b/test_crates/trait_method_added/old/src/lib.rs @@ -0,0 +1,31 @@ +mod sealed { + pub(crate) trait Sealed {} +} + +// ---- Should be reported ---- +pub trait WillGainMethodWithoutDefault {} + +pub trait WillGainAnotherMethodWithoutDefault { + fn one_method(self); +} + +pub trait WillGainMultipleMethodsWithoutDefault {} + +pub trait WillGainMethodWithoutDefaultAndSeal {} + +pub trait WIllGainDocHiddenMethodWithoutDefault {} + +// ---- Should not be reported ---- +pub trait WillGainMethodWithDefault {} + +pub trait WillGainAnotherMethodWithDefault { + fn one_method(self); +} + +pub trait WillGainMethodWithoutDefaultSealed: sealed::Sealed {} + +pub trait WillGainMethodWithoutDefaultAndLoseSeal: sealed::Sealed {} + +pub trait WillKeepAMethodWithoutDefault { + fn one_method(self); +} diff --git a/test_crates/trait_method_default_impl_removed/new/Cargo.toml b/test_crates/trait_method_default_impl_removed/new/Cargo.toml new file mode 100644 index 00000000..ea95420e --- /dev/null +++ b/test_crates/trait_method_default_impl_removed/new/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "trait_method_default_impl_removed" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/trait_default_impl_removed/new/src/lib.rs b/test_crates/trait_method_default_impl_removed/new/src/lib.rs similarity index 100% rename from test_crates/trait_default_impl_removed/new/src/lib.rs rename to test_crates/trait_method_default_impl_removed/new/src/lib.rs diff --git a/test_crates/trait_method_default_impl_removed/old/Cargo.toml b/test_crates/trait_method_default_impl_removed/old/Cargo.toml new file mode 100644 index 00000000..ea95420e --- /dev/null +++ b/test_crates/trait_method_default_impl_removed/old/Cargo.toml @@ -0,0 +1,7 @@ +[package] +publish = false +name = "trait_method_default_impl_removed" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/test_crates/trait_default_impl_removed/old/src/lib.rs b/test_crates/trait_method_default_impl_removed/old/src/lib.rs similarity index 100% rename from test_crates/trait_default_impl_removed/old/src/lib.rs rename to test_crates/trait_method_default_impl_removed/old/src/lib.rs diff --git a/test_outputs/enum_tuple_variant_changed_kind.output.ron b/test_outputs/enum_tuple_variant_changed_kind.output.ron new file mode 100644 index 00000000..d42df580 --- /dev/null +++ b/test_outputs/enum_tuple_variant_changed_kind.output.ron @@ -0,0 +1,102 @@ +{ + "./test_crates/enum_tuple_variant_changed_kind/": [ + { + "enum_name": String("TestStruct"), + "kind": String("TupleVariant"), + "new_kind": String("StructVariant"), + "path": List([ + String("enum_tuple_variant_changed_kind"), + String("TestStruct"), + ]), + "span_begin_line": Uint64(3), + "span_filename": String("src/lib.rs"), + "variant_name": String("WillBecomeStructLike"), + }, + { + "enum_name": String("TestUnit"), + "kind": String("TupleVariant"), + "new_kind": String("PlainVariant"), + "path": List([ + String("enum_tuple_variant_changed_kind"), + String("TestUnit"), + ]), + "span_begin_line": Uint64(7), + "span_filename": String("src/lib.rs"), + "variant_name": String("WillBecomeUnitLike"), + }, + { + "enum_name": String("MultipleTest"), + "kind": String("TupleVariant"), + "new_kind": String("StructVariant"), + "path": List([ + String("enum_tuple_variant_changed_kind"), + String("MultipleTest"), + ]), + "span_begin_line": Uint64(11), + "span_filename": String("src/lib.rs"), + "variant_name": String("WillBecomeStructLike"), + }, + { + "enum_name": String("MultipleTest"), + "kind": String("TupleVariant"), + "new_kind": String("PlainVariant"), + "path": List([ + String("enum_tuple_variant_changed_kind"), + String("MultipleTest"), + ]), + "span_begin_line": Uint64(12), + "span_filename": String("src/lib.rs"), + "variant_name": String("WillBecomeUnitLike"), + }, + { + "enum_name": String("TestBecomeDocHidden"), + "kind": String("TupleVariant"), + "new_kind": String("StructVariant"), + "path": List([ + String("enum_tuple_variant_changed_kind"), + String("TestBecomeDocHidden"), + ]), + "span_begin_line": Uint64(18), + "span_filename": String("src/lib.rs"), + "variant_name": String("WillBecomeStructLike"), + }, + { + "enum_name": String("TestBecomeNonExhaustive"), + "kind": String("TupleVariant"), + "new_kind": String("StructVariant"), + "path": List([ + String("enum_tuple_variant_changed_kind"), + String("TestBecomeNonExhaustive"), + ]), + "span_begin_line": Uint64(23), + "span_filename": String("src/lib.rs"), + "variant_name": String("WillBecomeStructLike"), + }, + ], + "./test_crates/enum_tuple_variant_field_missing/": [ + { + "enum_name": String("PublicEnum"), + "kind": String("TupleVariant"), + "new_kind": String("PlainVariant"), + "path": List([ + String("enum_tuple_variant_field_missing"), + String("PublicEnum"), + ]), + "span_begin_line": Uint64(5), + "span_filename": String("src/lib.rs"), + "variant_name": String("TupleVariantBecomesPlain"), + }, + { + "enum_name": String("PublicEnum"), + "kind": String("TupleVariant"), + "new_kind": String("StructVariant"), + "path": List([ + String("enum_tuple_variant_field_missing"), + String("PublicEnum"), + ]), + "span_begin_line": Uint64(7), + "span_filename": String("src/lib.rs"), + "variant_name": String("TupleVariantBecomesStruct"), + }, + ] +} diff --git a/test_outputs/enum_unit_variant_changed_kind.output.ron b/test_outputs/enum_unit_variant_changed_kind.output.ron new file mode 100644 index 00000000..479522cb --- /dev/null +++ b/test_outputs/enum_unit_variant_changed_kind.output.ron @@ -0,0 +1,90 @@ +{ + "./test_crates/enum_tuple_variant_field_added/": [ + { + "enum_name": String("PublicEnum"), + "kind": String("PlainVariant"), + "new_kind": String("TupleVariant"), + "path": List([ + String("enum_tuple_variant_field_added"), + String("PublicEnum"), + ]), + "span_begin_line": Uint64(13), + "span_filename": String("src/lib.rs"), + "variant_name": String("PlainVariantBecomesTuple"), + }, + ], + "./test_crates/enum_unit_variant_changed_kind/": [ + { + "enum_name": String("TestStruct"), + "kind": String("PlainVariant"), + "new_kind": String("StructVariant"), + "path": List([ + String("enum_unit_variant_changed_kind"), + String("TestStruct"), + ]), + "span_begin_line": Uint64(3), + "span_filename": String("src/lib.rs"), + "variant_name": String("WillBecomeStructLike"), + }, + { + "enum_name": String("TestTuple"), + "kind": String("PlainVariant"), + "new_kind": String("TupleVariant"), + "path": List([ + String("enum_unit_variant_changed_kind"), + String("TestTuple"), + ]), + "span_begin_line": Uint64(7), + "span_filename": String("src/lib.rs"), + "variant_name": String("WillBecomeTupleLike"), + }, + { + "enum_name": String("MultipleTest"), + "kind": String("PlainVariant"), + "new_kind": String("StructVariant"), + "path": List([ + String("enum_unit_variant_changed_kind"), + String("MultipleTest"), + ]), + "span_begin_line": Uint64(11), + "span_filename": String("src/lib.rs"), + "variant_name": String("WillBecomeStructLike"), + }, + { + "enum_name": String("MultipleTest"), + "kind": String("PlainVariant"), + "new_kind": String("TupleVariant"), + "path": List([ + String("enum_unit_variant_changed_kind"), + String("MultipleTest"), + ]), + "span_begin_line": Uint64(12), + "span_filename": String("src/lib.rs"), + "variant_name": String("WillBecomeTupleLike"), + }, + { + "enum_name": String("TestBecomeDocHidden"), + "kind": String("PlainVariant"), + "new_kind": String("StructVariant"), + "path": List([ + String("enum_unit_variant_changed_kind"), + String("TestBecomeDocHidden"), + ]), + "span_begin_line": Uint64(18), + "span_filename": String("src/lib.rs"), + "variant_name": String("WillBecomeStructLike"), + }, + { + "enum_name": String("TestBecomeNonExhaustive"), + "kind": String("PlainVariant"), + "new_kind": String("StructVariant"), + "path": List([ + String("enum_unit_variant_changed_kind"), + String("TestBecomeNonExhaustive"), + ]), + "span_begin_line": Uint64(23), + "span_filename": String("src/lib.rs"), + "variant_name": String("WillBecomeStructLike"), + }, + ], +} diff --git a/test_outputs/variant_marked_non_exhaustive.output.ron b/test_outputs/enum_variant_marked_non_exhaustive.output.ron similarity index 68% rename from test_outputs/variant_marked_non_exhaustive.output.ron rename to test_outputs/enum_variant_marked_non_exhaustive.output.ron index 302a31dc..fbd58f68 100644 --- a/test_outputs/variant_marked_non_exhaustive.output.ron +++ b/test_outputs/enum_variant_marked_non_exhaustive.output.ron @@ -12,6 +12,19 @@ "visibility_limit": String("public"), }, ], + "./test_crates/enum_tuple_variant_changed_kind/": [ + { + "name": String("TestBecomeNonExhaustive"), + "path": List([ + String("enum_tuple_variant_changed_kind"), + String("TestBecomeNonExhaustive"), + ]), + "span_begin_line": Uint64(23), + "span_filename": String("src/lib.rs"), + "variant_name": String("WillBecomeStructLike"), + "visibility_limit": String("public"), + }, + ], "./test_crates/enum_tuple_variant_field_added/": [ { "name": String("PublicEnum"), @@ -25,6 +38,19 @@ "visibility_limit": String("public"), }, ], + "./test_crates/enum_unit_variant_changed_kind/": [ + { + "name": String("TestBecomeNonExhaustive"), + "path": List([ + String("enum_unit_variant_changed_kind"), + String("TestBecomeNonExhaustive"), + ]), + "span_begin_line": Uint64(23), + "span_filename": String("src/lib.rs"), + "variant_name": String("WillBecomeStructLike"), + "visibility_limit": String("public"), + }, + ], "./test_crates/non_exhaustive/": [ { "name": String("MyEnum"), diff --git a/test_outputs/function_missing.output.ron b/test_outputs/function_missing.output.ron index b2f81ed2..dd4ceff3 100644 --- a/test_outputs/function_missing.output.ron +++ b/test_outputs/function_missing.output.ron @@ -1,4 +1,26 @@ { + "./test_crates/feature_flags_validation/": [ + { + "name": String("foo_becomes_gated"), + "path": List([ + String("feature_flags_validation"), + String("foo_becomes_gated"), + ]), + "span_begin_line": Uint64(4), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "name": String("bar_becomes_gated"), + "path": List([ + String("feature_flags_validation"), + String("bar_becomes_gated"), + ]), + "span_begin_line": Uint64(5), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], "./test_crates/features_simple/": [ { "name": String("feature_dependent_function"), diff --git a/test_outputs/integration_snapshots__bugreport.snap b/test_outputs/integration_snapshots__bugreport.snap new file mode 100644 index 00000000..fcc76018 --- /dev/null +++ b/test_outputs/integration_snapshots__bugreport.snap @@ -0,0 +1,58 @@ +--- +source: tests/integration_snapshots.rs +info: + program: cargo-semver-checks + args: + - semver-checks + - "--bugreport" + env: + CARGO_TERM_COLOR: never +--- +success: true +exit_code: 0 +----- stdout ----- +System information: +------------------- +#### Software version + +cargo-semver-checks [VERSION] ([HASH]) + +#### Operating system + +[OS] + +#### Command-line + +```bash +[EXECUTABLE_PATH] semver-checks --bugreport +``` + +#### cargo version + +``` +> cargo -V +cargo [CARGO_VERSION] ([CARGO_VERSION_DETAILS]) +``` + +#### Compile time information + +- Profile: [PROFILE] +- Target triple: [TARGET_TRIPLE] +- Family: [FAMILY] +- OS: [OS] +- Architecture: [ARCH] +- Pointer width: [WIDTH] +- Endian: [ENDIAN] +- CPU features: [FEATURES] +- Host: [HOST_TRIPLE] + + +Cargo build configuration: +-------------------------- +[CARGO_BUILD_TOML] +Please file an issue on GitHub reporting your bug. +Consider adding the diagnostic information above, either manually or automatically through the link below: + +https://github.com/obi1kenobi/cargo-semver-checks/issues/new?[INFO_URLENCODED] + +----- stderr ----- diff --git a/test_outputs/integration_snapshots__unstable_options_without_flag.snap b/test_outputs/integration_snapshots__unstable_options_without_flag.snap new file mode 100644 index 00000000..e32e4894 --- /dev/null +++ b/test_outputs/integration_snapshots__unstable_options_without_flag.snap @@ -0,0 +1,15 @@ +--- +source: tests/integration_snapshots.rs +info: + program: cargo-semver-checks + args: + - semver-checks + - "--witness-hints" +--- +success: false +exit_code: 1 +----- stdout ----- + +----- stderr ----- +error: the following options are not supported without `-Z unstable-options`: + - `--witness-hints` diff --git a/test_outputs/integration_snapshots__z_help.snap b/test_outputs/integration_snapshots__z_help.snap new file mode 100644 index 00000000..6b934a0f --- /dev/null +++ b/test_outputs/integration_snapshots__z_help.snap @@ -0,0 +1,22 @@ +--- +source: tests/integration_snapshots.rs +info: + program: cargo-semver-checks + args: + - semver-checks + - "-Z" + - help +--- +success: true +exit_code: 0 +----- stdout ----- +Unstable feature flags: +-Z name Description +help Print a list of the current unstable feature flags +unstable-options Enables the use of unstable CLI flags. + Run `cargo semver-checks -Z help` to list them +Unstable options: + --witness-hints + Enable printing witness hints, examples of potentially-broken downstream code + +----- stderr ----- diff --git a/test_outputs/snapshot_tests/cargo_semver_checks__snapshot_tests__workspace_baseline_compile_error-output.snap b/test_outputs/snapshot_tests/cargo_semver_checks__snapshot_tests__workspace_baseline_compile_error-output.snap index f832410e..aed6ff30 100644 --- a/test_outputs/snapshot_tests/cargo_semver_checks__snapshot_tests__workspace_baseline_compile_error-output.snap +++ b/test_outputs/snapshot_tests/cargo_semver_checks__snapshot_tests__workspace_baseline_compile_error-output.snap @@ -11,7 +11,7 @@ aborting due to failure to build rustdoc for crate error v0.1.0 Description: A publicly-visible function cannot be imported by its prior path. A `pub use` may have been removed, or the function itself may have been renamed or removed entirely. ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove - impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.34.0/src/lints/function_missing.ron + impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/[VERSION]/src/lints/function_missing.ron Failed in: function no_error::my_fn, previously in file [ROOT]/test_crates/manifest_tests/workspace_baseline_compile_error/old/no-error/src/lib.rs:3 diff --git a/test_outputs/snapshot_tests/cargo_semver_checks__snapshot_tests__workspace_publish_false_explicit-output.snap b/test_outputs/snapshot_tests/cargo_semver_checks__snapshot_tests__workspace_publish_false_explicit-output.snap index e28cdd34..018804e2 100644 --- a/test_outputs/snapshot_tests/cargo_semver_checks__snapshot_tests__workspace_publish_false_explicit-output.snap +++ b/test_outputs/snapshot_tests/cargo_semver_checks__snapshot_tests__workspace_publish_false_explicit-output.snap @@ -10,7 +10,7 @@ success: false Description: A publicly-visible function cannot be imported by its prior path. A `pub use` may have been removed, or the function itself may have been renamed or removed entirely. ref: https://doc.rust-lang.org/cargo/reference/semver.html#item-remove - impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/v0.34.0/src/lints/function_missing.ron + impl: https://github.com/obi1kenobi/cargo-semver-checks/tree/[VERSION]/src/lints/function_missing.ron Failed in: function a::should_not_run, previously in file [ROOT]/test_crates/manifest_tests/workspace_all_publish_false/old/a/src/lib.rs:4 diff --git a/test_outputs/trait_method_added.output.ron b/test_outputs/trait_method_added.output.ron new file mode 100644 index 00000000..c1589c82 --- /dev/null +++ b/test_outputs/trait_method_added.output.ron @@ -0,0 +1,360 @@ +{ + "./test_crates/method_moved_to_trait_must_use_added/": [ + { + "method_name": String("MethodToMovedDeclaredMustUseMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("enum_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(49), + "span_filename": String("src/enum_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MethodToMovedDeclaredMustUseMessageMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("enum_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(52), + "span_filename": String("src/enum_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMethodToMovedDeclaredMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("enum_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(54), + "span_filename": String("src/enum_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMethodToMovedDeclaredMustUseMessageMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("enum_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(57), + "span_filename": String("src/enum_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMessageMethodToMovedDeclaredMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("enum_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(59), + "span_filename": String("src/enum_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMessageMethodToMovedDeclaredMustUseMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("enum_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(62), + "span_filename": String("src/enum_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMessageMethodToMovedDeclaredMustUseMessageMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("enum_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(65), + "span_filename": String("src/enum_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MethodToMovedImplDeclaredMustUseMethod"), + "name": String("TraitWithMovedImplMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("enum_method_moved_to_trait_must_use_added"), + String("TraitWithMovedImplMustUseMethods"), + ]), + "span_begin_line": Uint64(135), + "span_filename": String("src/enum_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MethodToMovedDeclaredMustUseMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("struct_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(45), + "span_filename": String("src/struct_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MethodToMovedDeclaredMustUseMessageMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("struct_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(48), + "span_filename": String("src/struct_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMethodToMovedDeclaredMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("struct_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(50), + "span_filename": String("src/struct_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMethodToMovedDeclaredMustUseMessageMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("struct_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(53), + "span_filename": String("src/struct_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMessageMethodToMovedDeclaredMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("struct_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(55), + "span_filename": String("src/struct_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMessageMethodToMovedDeclaredMustUseMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("struct_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(58), + "span_filename": String("src/struct_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMessageMethodToMovedDeclaredMustUseMessageMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("struct_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(61), + "span_filename": String("src/struct_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MethodToMovedImplDeclaredMustUseMethod"), + "name": String("TraitWithMovedImplMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("struct_method_moved_to_trait_must_use_added"), + String("TraitWithMovedImplMustUseMethods"), + ]), + "span_begin_line": Uint64(127), + "span_filename": String("src/struct_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MethodToMovedDeclaredMustUseMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("union_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(49), + "span_filename": String("src/union_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MethodToMovedDeclaredMustUseMessageMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("union_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(52), + "span_filename": String("src/union_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMethodToMovedDeclaredMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("union_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(54), + "span_filename": String("src/union_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMethodToMovedDeclaredMustUseMessageMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("union_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(57), + "span_filename": String("src/union_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMessageMethodToMovedDeclaredMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("union_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(59), + "span_filename": String("src/union_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMessageMethodToMovedDeclaredMustUseMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("union_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(62), + "span_filename": String("src/union_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MustUseMessageMethodToMovedDeclaredMustUseMessageMethod"), + "name": String("TraitWithMovedDeclaredMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("union_method_moved_to_trait_must_use_added"), + String("TraitWithMovedDeclaredMustUseMethods"), + ]), + "span_begin_line": Uint64(65), + "span_filename": String("src/union_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("MethodToMovedImplDeclaredMustUseMethod"), + "name": String("TraitWithMovedImplMustUseMethods"), + "path": List([ + String("method_moved_to_trait_must_use_added"), + String("union_method_moved_to_trait_must_use_added"), + String("TraitWithMovedImplMustUseMethods"), + ]), + "span_begin_line": Uint64(135), + "span_filename": String("src/union_method_moved_to_trait_must_use_added.rs"), + "visibility_limit": String("public"), + }, + ], + "./test_crates/trait_method_added/": [ + { + "method_name": String("one_method"), + "name": String("WillGainMethodWithoutDefault"), + "path": List([ + String("trait_method_added"), + String("WillGainMethodWithoutDefault"), + ]), + "span_begin_line": Uint64(7), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("two_method"), + "name": String("WillGainAnotherMethodWithoutDefault"), + "path": List([ + String("trait_method_added"), + String("WillGainAnotherMethodWithoutDefault"), + ]), + "span_begin_line": Uint64(12), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("one_method"), + "name": String("WillGainMultipleMethodsWithoutDefault"), + "path": List([ + String("trait_method_added"), + String("WillGainMultipleMethodsWithoutDefault"), + ]), + "span_begin_line": Uint64(16), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("two_method"), + "name": String("WillGainMultipleMethodsWithoutDefault"), + "path": List([ + String("trait_method_added"), + String("WillGainMultipleMethodsWithoutDefault"), + ]), + "span_begin_line": Uint64(17), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("one_method"), + "name": String("WillGainMethodWithoutDefaultAndSeal"), + "path": List([ + String("trait_method_added"), + String("WillGainMethodWithoutDefaultAndSeal"), + ]), + "span_begin_line": Uint64(21), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + { + "method_name": String("one_method"), + "name": String("WIllGainDocHiddenMethodWithoutDefault"), + "path": List([ + String("trait_method_added"), + String("WIllGainDocHiddenMethodWithoutDefault"), + ]), + "span_begin_line": Uint64(26), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], +} diff --git a/test_outputs/trait_default_impl_removed.output.ron b/test_outputs/trait_method_default_impl_removed.output.ron similarity index 86% rename from test_outputs/trait_default_impl_removed.output.ron rename to test_outputs/trait_method_default_impl_removed.output.ron index d2b3f484..888cf96f 100644 --- a/test_outputs/trait_default_impl_removed.output.ron +++ b/test_outputs/trait_method_default_impl_removed.output.ron @@ -1,10 +1,10 @@ { - "./test_crates/trait_default_impl_removed/": [ + "./test_crates/trait_method_default_impl_removed/": [ { "method_name": String("method_default_impl_removed"), "name": String("TraitA"), "path": List([ - String("trait_default_impl_removed"), + String("trait_method_default_impl_removed"), String("TraitA"), ]), "span_begin_line": Uint64(8), @@ -15,7 +15,7 @@ "method_name": String("method_default_impl_removed_and_becomes_non_obj_safe"), "name": String("TraitD"), "path": List([ - String("trait_default_impl_removed"), + String("trait_method_default_impl_removed"), String("TraitD"), ]), "span_begin_line": Uint64(22), @@ -26,7 +26,7 @@ "method_name": String("method_default_impl_removed_and_becomes_sealed"), "name": String("TraitE"), "path": List([ - String("trait_default_impl_removed"), + String("trait_method_default_impl_removed"), String("TraitE"), ]), "span_begin_line": Uint64(27), @@ -37,7 +37,7 @@ "method_name": String("method_partially_sealed_has_default_impl_removed"), "name": String("TraitF"), "path": List([ - String("trait_default_impl_removed"), + String("trait_method_default_impl_removed"), String("TraitF"), ]), "span_begin_line": Uint64(32), diff --git a/test_outputs/trait_method_missing.output.ron b/test_outputs/trait_method_missing.output.ron index b084be8a..84a4be7f 100644 --- a/test_outputs/trait_method_missing.output.ron +++ b/test_outputs/trait_method_missing.output.ron @@ -1,10 +1,10 @@ { - "./test_crates/trait_default_impl_removed/": [ + "./test_crates/trait_method_default_impl_removed/": [ { "method_name": String("method_becomes_removed"), "name": String("TraitB"), "path": List([ - String("trait_default_impl_removed"), + String("trait_method_default_impl_removed"), String("TraitB"), ]), "span_begin_line": Uint64(14), diff --git a/test_outputs/trait_newly_sealed.output.ron b/test_outputs/trait_newly_sealed.output.ron index 1363a9d1..42cd1032 100644 --- a/test_outputs/trait_newly_sealed.output.ron +++ b/test_outputs/trait_newly_sealed.output.ron @@ -11,6 +11,18 @@ "visibility_limit": String("public"), }, ], + "./test_crates/trait_method_added/": [ + { + "name": String("WillGainMethodWithoutDefaultAndSeal"), + "path": List([ + String("trait_method_added"), + String("WillGainMethodWithoutDefaultAndSeal"), + ]), + "span_begin_line": Uint64(20), + "span_filename": String("src/lib.rs"), + "visibility_limit": String("public"), + }, + ], "./test_crates/trait_newly_sealed/": [ { "name": String("TraitBecomesSealed"), @@ -45,11 +57,11 @@ "visibility_limit": String("public"), }, ], - "./test_crates/trait_default_impl_removed/": [ + "./test_crates/trait_method_default_impl_removed/": [ { "name": String("TraitE"), "path": List([ - String("trait_default_impl_removed"), + String("trait_method_default_impl_removed"), String("TraitE"), ]), "span_begin_line": Uint64(26), @@ -59,7 +71,7 @@ { "name": String("TraitF"), "path": List([ - String("trait_default_impl_removed"), + String("trait_method_default_impl_removed"), String("TraitF"), ]), "span_begin_line": Uint64(31), @@ -68,4 +80,3 @@ }, ], } - diff --git a/test_outputs/trait_no_longer_object_safe.output.ron b/test_outputs/trait_no_longer_object_safe.output.ron index f69d496f..b78ecb79 100644 --- a/test_outputs/trait_no_longer_object_safe.output.ron +++ b/test_outputs/trait_no_longer_object_safe.output.ron @@ -21,11 +21,11 @@ "visibility_limit": String("public"), }, ], - "./test_crates/trait_default_impl_removed/": [ + "./test_crates/trait_method_default_impl_removed/": [ { "name": String("TraitD"), "path": List([ - String("trait_default_impl_removed"), + String("trait_method_default_impl_removed"), String("TraitD"), ]), "span_begin_line": Uint64(21), diff --git a/tests/feature_config.rs b/tests/feature_config.rs index 32c0d0e4..1c32651a 100644 --- a/tests/feature_config.rs +++ b/tests/feature_config.rs @@ -70,6 +70,52 @@ fn simple_default_features() { }); } +#[test] +fn simple_validation_feature_flags() { + CargoSemverChecks::new( + "test_crates/feature_flags_validation/new/", + "test_crates/feature_flags_validation/old/Cargo.toml", + ) + .add_arg("--only-explicit-features") + .add_arg("--baseline-features") + .add_arg("std,alloc") + .add_arg("--current-features") + .add_arg("foo,bar") + // without --features flag still works, but this is about flag validation + .add_arg("--features") + .add_arg("unstable,nightly") + .run_all() + .into_iter() + .for_each(|a| { + a.success(); + }); + // We repeat the same test, but specify each flag separately, + // to ensure that both ways can be parsed + CargoSemverChecks::new( + "test_crates/feature_flags_validation/new/", + "test_crates/feature_flags_validation/old/Cargo.toml", + ) + .add_arg("--only-explicit-features") + .add_arg("--baseline-features") + .add_arg("std") + .add_arg("--baseline-features") + .add_arg("alloc") + .add_arg("--current-features") + .add_arg("foo") + .add_arg("--current-features") + .add_arg("bar") + // without --features flag still works, but this is about flag validation + .add_arg("--features") + .add_arg("unstable") + .add_arg("--features") + .add_arg("nightly") + .run_all() + .into_iter() + .for_each(|a| { + a.success(); + }); +} + #[test] fn simple_heuristic_features() { CargoSemverChecks::new( diff --git a/tests/integration_snapshots.rs b/tests/integration_snapshots.rs new file mode 100644 index 00000000..f6584f19 --- /dev/null +++ b/tests/integration_snapshots.rs @@ -0,0 +1,131 @@ +//! Integration snapshot tests for testing `cargo-semver-checks` binary behavior. +//! +//! These tests have greater compile time penalties than `src/snapshot_tests.rs`. +//! Prefer those unless your test needs to access behavior in the `main` function of +//! `cargo-semver-checks`. +//! +//! See the module-level doc comment on `src/snapshot_tests.rs` for information on how +//! to create and update snapshot tests. +//! +//! To write an integration test, make a `#[test]` function and call [`assert_integration_test`] +//! to configure and run an integration test on the `cargo-semver-checks` binary. + +use std::path::{Path, PathBuf}; + +use assert_cmd::cargo::CommandCargoExt; +use insta_cmd::Command; + +/// Create a snapshot of the output of a `cargo semver-checks` invocation, using [`insta_cmd`]. +/// Add arguments and mutate the command using the passed closure (you do not need to include the +/// `semver-checks` subcommand`. This also lets you modify the [`insta::Settings`], e.g., to add +/// filters. +/// +/// The snapshot will be the `test_crates/snapshot_tests/integration_snapshots__{test_name}.snap` +/// file, so make sure to check this file into version control when adding/updating a test. +/// `test_name` will often be the name of the `#[test]` function that calls this function. +fn assert_integration_test( + test_name: &str, + modify: impl FnOnce(&mut Command, &mut insta::Settings), +) { + let mut settings = insta::Settings::clone_current(); + let mut cmd = + Command::cargo_bin("cargo-semver-checks").expect("failed to get cargo-semver-checks"); + + // never use color for more readable snapshots + cmd.env("CARGO_TERM_COLOR", "never"); + // disable backtrace printing for reproducibility + cmd.env("RUST_BACKTRACE", "0"); + + cmd.arg("semver-checks"); + settings.set_snapshot_path("../test_outputs/"); + + modify(&mut cmd, &mut settings); + + settings.bind(|| insta_cmd::assert_cmd_snapshot!(test_name, cmd)); +} + +/// Snapshots the behavior of the `--bugreport` argument. +#[test] +fn bugreport() { + assert_integration_test("bugreport", |cmd, settings| { + cmd.arg("--bugreport"); + // much of this is not reproducible on different machines, so we have + // to heavily filter it. + settings.add_filter( + r"cargo-semver-checks \d+\.\d+\.\d+(-[\w\.-]+)? \(\w+(-modified)?\)", + "cargo-semver-checks [VERSION] ([HASH])", + ); + settings.add_filter( + r"#### Operating system\n\n[^\n]+", + "#### Operating system\n\n[OS]", + ); + settings.add_filter( + ®ex::escape(executable_path().to_str().expect("non-UTF-8 path")), + "[EXECUTABLE_PATH]", + ); + + settings.add_filter( + r"cargo \d+\.\d+\.\d+(-[\w\.-]+)? \(.+\)", + "cargo [CARGO_VERSION] ([CARGO_VERSION_DETAILS])", + ); + + settings.add_filter(r"Profile: \w+", "Profile: [PROFILE]"); + settings.add_filter(r"Target triple: [\w-]+", "Target triple: [TARGET_TRIPLE]"); + settings.add_filter(r"Family: \w+", "Family: [FAMILY]"); + settings.add_filter(r"OS: \w+", "OS: [OS]"); + settings.add_filter(r"Architecture: \w+", "Architecture: [ARCH]"); + settings.add_filter(r"Pointer width: \d+", "Pointer width: [WIDTH]"); + settings.add_filter(r"Endian: \w+", "Endian: [ENDIAN]"); + settings.add_filter(r"CPU features: [\w,-]+", "CPU features: [FEATURES]"); + settings.add_filter(r"Host: [\w-]+", "Host: [HOST_TRIPLE]"); + + // this should be serialized TOML + settings.add_filter( + "Cargo build configuration:\\n\ + --------------------------\\n\ + [\\s\\S]*\\n\ + Please file an issue", + "Cargo build configuration:\n\ + --------------------------\n\ + [CARGO_BUILD_TOML]\n\ + Please file an issue", + ); + + settings.add_filter( + r"https://github\.com/obi1kenobi/cargo-semver-checks/issues/new\?\S+", + "https://github.com/obi1kenobi/cargo-semver-checks/issues/new?[INFO_URLENCODED]", + ); + }); +} + +// TODO: this test will break when the `--witness-hints` is stabilized. It will need to +// be replaced with a different unstable option. See the module-level doc comment for +// how to update this test. +/// Tests the behavior of unstable options being passed without `-Z unstable-options`. +#[test] +fn unstable_options_without_flag() { + assert_integration_test("unstable_options_without_flag", |cmd, _| { + cmd.arg("--witness-hints"); + }); +} + +/// Snapshots the behavior of `-Z help`. This snapshot will need to be updated when any +/// unstable options or feature flags are added, removed, or stabilized. See the module-level +/// doc comment for how to update this test. +#[test] +fn z_help() { + assert_integration_test("z_help", |cmd, _| { + cmd.args(["-Z", "help"]); + }) +} + +/// Helper function to get a canonicalized version of the cargo executable bin. +fn executable_path() -> PathBuf { + Path::new( + Command::cargo_bin("cargo-semver-checks") + .expect("expected cargo-semver-checks") + .get_program(), + ) + .canonicalize() + .expect("error canonicalizing") +}