diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml new file mode 100644 index 0000000..a5971b8 --- /dev/null +++ b/.github/workflows/audit.yml @@ -0,0 +1,30 @@ +name: audit check + +concurrency: + group: ${{github.workflow}}-${{github.ref}} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + +on: + push: + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + pull_request: + paths: + - '**/Cargo.toml' + - '**/Cargo.lock' + +jobs: + audit: + timeout-minutes: 10 + name: 'Rust audit check' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: Swatinem/rust-cache@v2 + - uses: rustsec/audit-check@v1.4.1 + with: + token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0be5282..d1ed846 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -8,28 +8,36 @@ env: CARGO_TERM_COLOR: always jobs: - crates-io: - name: crates-io - runs-on: ubuntu-latest - environment: prod - steps: - - uses: actions/checkout@v4 - - run: cargo publish --token ${CRATES_API_TOKEN} - env: - CRATES_API_TOKEN: ${{ secrets.CRATES_API_TOKEN }} + crates-io: + timeout-minutes: 10 + name: crates-io + runs-on: ubuntu-latest + environment: prod + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - name: Verify package + run: cargo package --locked + - name: Publish + run: cargo publish --token ${CRATES_API_TOKEN} + env: + CRATES_API_TOKEN: ${{ secrets.CRATES_API_TOKEN }} - github-release: - name: github-release - runs-on: ubuntu-latest - environment: prod - steps: - - name: Checkout code - uses: actions/checkout@master - - name: Create Release - id: create_release - uses: softprops/action-gh-release@v2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - name: ${{ github.ref_name }} - generate_release_notes: true + github-release: + timeout-minutes: 10 + name: github-release + needs: [crates-io] + runs-on: ubuntu-latest + environment: prod + steps: + - name: Checkout code + uses: actions/checkout@master + - name: Create Release + id: create_release + uses: softprops/action-gh-release@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: ${{ github.ref_name }} + generate_release_notes: true diff --git a/.github/workflows/rust.yml b/.github/workflows/tests.yml similarity index 57% rename from .github/workflows/rust.yml rename to .github/workflows/tests.yml index a933bfe..75076a8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/tests.yml @@ -14,12 +14,19 @@ env: CARGO_TERM_COLOR: always jobs: + +###################################################################### +# General checks/fmt and docs +###################################################################### + fmt: name: Format runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Cache cargo output + uses: Swatinem/rust-cache@v2 - name: Fmt check run: cargo fmt --check @@ -29,6 +36,8 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Cache cargo output + uses: Swatinem/rust-cache@v2 - name: Check run: cargo check @@ -38,15 +47,40 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Cache cargo output + uses: Swatinem/rust-cache@v2 - name: Run clippy - run: cargo clippy --no-deps --all-targets + run: cargo clippy --no-deps --all-targets -- -D warnings - tests: - name: Unit and Integration Tests + docs: + name: Docs runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Cache cargo output + uses: Swatinem/rust-cache@v2 + - name: Build docs + run: cargo doc --no-deps --all-features + env: + RUSTFLAGS: "-D warnings" + +###################################################################### +# Unit/integration/coverage tests, multi OS +###################################################################### + + tests: + name: Unit and Integration Tests on ${{ matrix.os }} + timeout-minutes: 10 + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + fail-fast: false # continue running other OS even if one fails steps: - uses: actions/checkout@v4 + - name: Cache cargo output + uses: Swatinem/rust-cache@v2 - name: Build run: cargo build - name: Run tests @@ -54,6 +88,7 @@ jobs: coverage: name: Coverage + timeout-minutes: 10 runs-on: ubuntu-latest permissions: pull-requests: write @@ -90,4 +125,4 @@ jobs: ``` ${{ steps.codecov.outputs.new_cov_comment }} - ``` + ``` \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0592392..37ee804 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /target .DS_Store +coverage.info +lcov.info diff --git a/Cargo.lock b/Cargo.lock index bdd302a..bccbf4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,12 +1,12 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "anstream" -version = "0.6.15" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" dependencies = [ "anstyle", "anstyle-parse", @@ -19,43 +19,44 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.1.1" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.52.0", + "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.4" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell", + "windows-sys", ] [[package]] name = "anyhow" -version = "1.0.92" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" [[package]] name = "askama" @@ -118,9 +119,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.6.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" [[package]] name = "cfg-if" @@ -130,9 +131,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.20" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "769b0145982b4b48713e01ec42d61614425f27b7058bda7180a3a41f30104796" dependencies = [ "clap_builder", "clap_derive", @@ -140,9 +141,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7" dependencies = [ "anstream", "anstyle", @@ -152,9 +153,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.18" +version = "4.5.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +checksum = "54b755194d6389280185988721fffba69495eed5ee9feeee9a599b53db80318c" dependencies = [ "heck", "proc-macro2", @@ -164,31 +165,31 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "colorchoice" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] name = "fastrand" -version = "2.1.1" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fs_extra" @@ -196,6 +197,95 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi", + "windows-targets", +] + [[package]] name = "heck" version = "0.5.0" @@ -219,21 +309,37 @@ checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "libc" -version = "0.2.161" +version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" +checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "lock_api" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "medusa-gen" @@ -243,6 +349,7 @@ dependencies = [ "askama", "clap", "fs_extra", + "serial_test", "tempfile", ] @@ -299,63 +406,168 @@ version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "proc-macro2" -version = "1.0.88" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + [[package]] name = "rustix" -version = "0.38.38" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys", +] + +[[package]] +name = "scc" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28e1c91382686d21b5ac7959341fcb9780fa7c03773646995a87c950fa7be640" +dependencies = [ + "sdd", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdd" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478f121bb72bbf63c52c93011ea1791dca40140dfe13f8336c4c5ac952c33aa9" + [[package]] name = "serde" -version = "1.0.210" +version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.217" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serial_test" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "1b258109f244e1d6891bf1053a55d63a5cd4f8f4c30cf9a1280989f80e7a1fa9" +dependencies = [ + "futures", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d69265a08751de7844521fd15003ae0a888e035773ba05695c5c759a6f89eef" dependencies = [ "proc-macro2", "quote", "syn", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + [[package]] name = "strsim" version = "0.11.1" @@ -364,9 +576,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.82" +version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" +checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", @@ -375,28 +587,29 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.13.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91" dependencies = [ "cfg-if", "fastrand", + "getrandom", "once_cell", "rustix", - "windows-sys 0.59.0", + "windows-sys", ] [[package]] name = "unicase" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.13" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "utf8parse" @@ -405,12 +618,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] -name = "windows-sys" -version = "0.52.0" +name = "wasi" +version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" dependencies = [ - "windows-targets", + "wit-bindgen-rt", ] [[package]] @@ -485,3 +698,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags", +] diff --git a/Cargo.toml b/Cargo.toml index afedacb..39e9ef6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,10 @@ readme = "readme.MD" keywords = ["medusa", "solidity", "fuzzing", "template"] categories = ["command-line-utilities"] +[lib] +name = "medusa_gen" +path = "src/lib.rs" + [[bin]] name = "medusa-gen" path = "src/main.rs" @@ -21,3 +25,6 @@ askama = "0.12.1" clap = { version = "4.5.20", features = ["cargo", "derive"] } fs_extra = "1.3.0" tempfile = "3.13.0" + +[dev-dependencies] +serial_test = "3.2.0" diff --git a/readme.MD b/readme.MD index bc1812e..6bc2bb4 100644 --- a/readme.MD +++ b/readme.MD @@ -2,6 +2,10 @@ This is a tool to generate a set of contracts for a Medusa testing campaign, following Wonderland usage. +Made with ♥ by Wonderland (https://defi.sucks) + +## Description + The following contracts are generated, according to this structure (I know, we call the child "parent" for...reason): - fuzz/ - FuzzTest.sol @@ -28,7 +32,7 @@ The inheritance tree is as follows, FuzzTest is the entry point: This is an early alpha version, only available by building from sources: ```bash -cargo install --git https://github.com/defiSucks/medusa-gen.rs +cargo install medusa-gen ``` ## Usage diff --git a/src/cli.rs b/src/cli.rs index e7b2182..c16b38c 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -26,7 +26,6 @@ Version: {version} \n\ For more information, visit: https://github.com/defi-wonderland/medusa-gen-rs\n", ))] - pub struct Args { /// Solidity version #[arg(short, long, default_value = "0.8.23")] diff --git a/src/gen.rs b/src/lib.rs similarity index 51% rename from src/gen.rs rename to src/lib.rs index 1504b83..e6921cb 100644 --- a/src/gen.rs +++ b/src/lib.rs @@ -1,5 +1,9 @@ -use crate::types::{Contract, ContractBuilder}; -use crate::{Args, ContractType}; +pub mod cli; +mod types; + +use crate::cli::Args; +use crate::types::{Contract, ContractBuilder, ContractType}; + use anyhow::{Context, Result}; use fs_extra::dir::{copy, CopyOptions}; use std::fmt::Write; @@ -29,7 +33,7 @@ fn parse_parents(parents: &[Contract]) -> String { } /// create a vec of contracts of a given type -fn create_contracts(contract_type: ContractType, count: u8, path: &Path) -> Result> { +fn create_contracts(contract_type: &ContractType, count: u8, path: &Path) -> Result> { let mut contracts = Vec::new(); // directories @@ -43,7 +47,7 @@ fn create_contracts(contract_type: ContractType, count: u8, path: &Path) -> Resu for i in 0..count { let contract = ContractBuilder::new() - .with_type(&contract_type) + .with_type(contract_type) .with_name(format!("{}{}", contract_type.name(), (b'A' + i) as char)) .build(); @@ -74,7 +78,7 @@ fn generate_parents( }; // Use the helper function to generate the contracts - create_contracts(contract_type, count, path) + create_contracts(&contract_type, count, path) } /// Move the content of a temp folder to the fuzz test folder @@ -178,6 +182,7 @@ pub fn generate_test_suite(args: &Args) -> Result<()> { #[cfg(test)] mod tests { use super::*; + use serial_test::serial; #[test] fn test_parse_child_imports() { @@ -267,157 +272,221 @@ mod tests { assert_eq!(parse_parents(parents.as_ref()), ""); } - // #[test] - // fn test_generate_family_with_handler() -> Result<()> { - // let tmpdir = std::env::temp_dir(); - // env::set_current_dir(&tmpdir)?; - - // let args = Args { - // overwrite: true, - // solc: "0.8.23".to_string(), - // nb_handlers: 2, - // nb_properties: 2, - // }; - - // let result = generate_family(&args, ContractType::Handler); - - // assert!(result.is_ok()); - - // assert!(Path::new("handlers").is_dir()); - // assert!(Path::new("handlers/HandlerA.t.sol").is_file()); - // assert!(Path::new("handlers/HandlerB.t.sol").is_file()); - // assert!(!Path::new("handlers/HandlerC.t.sol").is_file()); - // assert!(Path::new("handlers/HandlerParent.t.sol").is_file()); - - // Ok(()) - // } - - // #[test] - // fn test_generate_family_with_property() -> Result<()> { - // let tmpdir = std::env::temp_dir(); - // env::set_current_dir(&tmpdir)?; - - // let args = Args { - // overwrite: true, - // solc: "0.8.23".to_string(), - // nb_handlers: 2, - // nb_properties: 2, - // }; - - // let result = generate_family(&args, ContractType::Property); - - // assert!(result.is_ok()); - - // assert!(Path::new("properties").is_dir()); - // assert!(Path::new("properties/PropertyA.t.sol").is_file()); - // assert!(Path::new("properties/PropertyB.t.sol").is_file()); - // assert!(!Path::new("properties/PropertyC.t.sol").is_file()); - // assert!(Path::new("properties/PropertyParent.t.sol").is_file()); - - // Ok(()) - // } - - // #[test] - // fn test_generate_family_with_setup_fail() { - // let args = Args { - // overwrite: true, - // solc: "0.8.23".to_string(), - // nb_handlers: 2, - // nb_properties: 2, - // }; - - // let result = generate_family(&args, ContractType::Setup); - // let error = result.as_ref().unwrap_err(); - - // assert_eq!(format!("{}", error), "Invalid contract type in gen family"); - // } - - // #[test] - // fn test_generate_family_with_entry_point_fail() { - // let args = Args { - // overwrite: true, - // solc: "0.8.23".to_string(), - // nb_handlers: 2, - // nb_properties: 2, - // }; - - // let result = generate_family(&args, ContractType::EntryPoint); - // let error = result.as_ref().unwrap_err(); - - // assert_eq!(format!("{}", error), "Invalid contract type in gen family"); - // } - - // #[test] - // fn test_generate_contract_with_setup() -> Result<()> { - // let tmpdir = std::env::temp_dir(); - // env::set_current_dir(&tmpdir)?; - - // let args = Args { - // overwrite: true, - // solc: "0.8.23".to_string(), - // nb_handlers: 2, - // nb_properties: 2, - // }; - - // let result = generate_contract( - // &args, - // ContractType::Setup, - // &ContractType::Setup.name(), - // &tmpdir, - // ); - - // assert!(result.is_ok()); - - // assert_eq!( - // result.unwrap(), - // Contract { - // licence: "MIT".to_string(), - // solc: "0.8.23".to_string(), - // imports: "".to_string(), - // name: "Setup".to_string(), - // parents: "".to_string(), - // } - // ); - - // assert!(Path::new("Setup.t.sol").is_file()); - // Ok(()) - // } - - // #[test] - // fn test_generate_contract_with_entry_point() -> Result<()> { - // let tmpdir = std::env::temp_dir(); - // env::set_current_dir(&tmpdir)?; - - // let args = Args { - // overwrite: true, - // solc: "0.8.23".to_string(), - // nb_handlers: 2, - // nb_properties: 2, - // }; - - // let result = generate_contract( - // &args, - // ContractType::EntryPoint, - // &ContractType::EntryPoint.name(), - // &tmpdir, - // ); - - // assert!(result.is_ok()); - - // assert_eq!( - // result.unwrap(), - // Contract { - // licence: "MIT".to_string(), - // solc: "0.8.23".to_string(), - // imports: "import {PropertiesParent} from './properties/PropertiesParent.t.sol';" - // .to_string(), - // name: "FuzzTest".to_string(), - // parents: "PropertiesParent".to_string(), - // } - // ); - - // assert!(Path::new("FuzzTest.t.sol").is_file()); - - // Ok(()) - // } + #[test] + fn test_create_contracts() -> Result<()> { + let temp_dir = TempDir::new().context("Failed to create temp dir")?; + let contract_type = ContractType::Handler; + let count = 2; + + let contracts = create_contracts( + &contract_type, + count, + &temp_dir.path().join(contract_type.directory_name()), + )?; + + // Check that the correct number of contracts was created + assert_eq!(contracts.len(), 2); + + // Check that the contracts have the expected names + assert_eq!(contracts[0].name, "HandlersA"); + assert_eq!(contracts[1].name, "HandlersB"); + + // Verify the files were created in the correct directory + let handler_dir = temp_dir.path().join("handlers"); + assert!(handler_dir.exists()); + assert!(handler_dir.is_dir()); + + // Check that the contract files exist + assert!(handler_dir.join("HandlersA.t.sol").exists()); + assert!(handler_dir.join("HandlersB.t.sol").exists()); + assert!(!handler_dir.join("HandlersC.t.sol").exists()); + + Ok(()) + } + + #[test] + fn test_create_contracts_empty() -> Result<()> { + let temp_dir = TempDir::new().context("Failed to create temp dir")?; + let contract_type = ContractType::Handler; + let count = 0; + + let contracts = create_contracts( + &contract_type, + count, + &temp_dir.path().join(contract_type.directory_name()), + )?; + + // Check that no contracts were created + assert!(contracts.is_empty()); + + // Verify the directory was still created + let handler_dir = temp_dir.path().join("handlers"); + assert!(handler_dir.exists()); + assert!(handler_dir.is_dir()); + + Ok(()) + } + + #[test] + fn test_generate_parents() -> Result<()> { + let temp_dir = TempDir::new().context("Failed to create temp dir")?; + let args = Args { + overwrite: true, + solc: "0.8.23".to_string(), + nb_handlers: 2, + nb_properties: 1, + }; + + // Test Handler parents + let handler_parents = generate_parents( + ContractType::Handler, + &args, + &temp_dir.path().join(ContractType::Handler.directory_name()), + )?; + + assert_eq!(handler_parents.len(), 2); + assert_eq!(handler_parents[0].name, "HandlersA"); + assert_eq!(handler_parents[1].name, "HandlersB"); + + // Test Property parents + let property_parents = generate_parents( + ContractType::Property, + &args, + &temp_dir + .path() + .join(ContractType::Property.directory_name()), + )?; + + assert_eq!(property_parents.len(), 1); + assert_eq!(property_parents[0].name, "PropertiesA"); + + Ok(()) + } + + #[test] + fn test_generate_parents_invalid_type() { + let temp_dir = TempDir::new().unwrap(); + let args = Args { + overwrite: true, + solc: "0.8.23".to_string(), + nb_handlers: 2, + nb_properties: 1, + }; + + let result = generate_parents(ContractType::Setup, &args, temp_dir.path()); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Invalid contract type in generate_parents" + ); + } + + // All the move_temp_contents are in serial to avoid having race conditions + #[test] + #[serial] + fn test_move_temp_contents() -> Result<()> { + let temp_dir = TempDir::new().context("Failed to create temp dir")?; + + // Create a test file in temp directory + let test_file = temp_dir.path().join("test.txt"); + std::fs::write(&test_file, "test content")?; + + let result = move_temp_contents(&temp_dir, true); + assert!(result.is_ok()); + + let dest_file = Path::new("./test/invariants/fuzz/test.txt"); + assert!(dest_file.exists()); + assert_eq!(std::fs::read_to_string(dest_file)?, "test content"); + + std::fs::remove_dir_all("./test/invariants/fuzz")?; + Ok(()) + } + + #[test] + #[serial] + fn test_move_temp_contents_no_overwrite() -> Result<()> { + let temp_dir = TempDir::new().context("Failed to create temp dir")?; + + std::fs::create_dir_all("./test/invariants/fuzz")?; + + let result = move_temp_contents(&temp_dir, false); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Fuzz test folder already exists, did you mean --overwrite ?" + ); + + std::fs::remove_dir_all("./test/invariants/fuzz")?; + Ok(()) + } + + #[test] + #[serial] + fn test_move_temp_contents_new_directory() -> Result<()> { + let temp_dir = TempDir::new().context("Failed to create temp dir")?; + + // source directory with test file + let source_dir = temp_dir.path().join("source"); + std::fs::create_dir(&source_dir)?; + let test_file = source_dir.join("test.txt"); + std::fs::write(&test_file, "test content")?; + + // TempDir for source that will be moved + let source_temp = + TempDir::new_in(&source_dir).context("Failed to create source temp dir")?; + std::fs::write(source_temp.path().join("test.txt"), "test content")?; + + // Set current directory to temp_dir + let original_dir = std::env::current_dir()?; + std::env::set_current_dir(&temp_dir)?; + + // Test moving to non-existent directory + let result = move_temp_contents(&source_temp, false); + if let Err(ref e) = result { + println!("Error: {:#}", e); + } + assert!(result.is_ok()); + + let fuzz_dir = Path::new("./test/invariants/fuzz"); + assert!(fuzz_dir.exists()); + assert!(fuzz_dir.is_dir()); + let dest_file = fuzz_dir.join("test.txt"); + assert!(dest_file.exists()); + assert_eq!(std::fs::read_to_string(dest_file)?, "test content"); + + std::env::set_current_dir(original_dir)?; + Ok(()) + } + + #[test] + #[serial] + fn test_generate_test_suite() -> Result<()> { + let temp_dir = TempDir::new().context("Failed to create temp dir")?; + let original_dir = std::env::current_dir()?; + std::env::set_current_dir(&temp_dir)?; + + let args = Args { + overwrite: true, + solc: "0.8.23".to_string(), + nb_handlers: 2, + nb_properties: 1, + }; + + let result = generate_test_suite(&args); + assert!(result.is_ok()); + + let fuzz_dir = Path::new("test/invariants/fuzz"); + assert!(fuzz_dir.join("handlers/HandlersA.t.sol").exists()); + assert!(fuzz_dir.join("handlers/HandlersB.t.sol").exists()); + assert!(fuzz_dir.join("handlers/HandlersParent.t.sol").exists()); + assert!(fuzz_dir.join("properties/PropertiesA.t.sol").exists()); + assert!(fuzz_dir.join("properties/PropertiesParent.t.sol").exists()); + assert!(fuzz_dir.join("Setup.t.sol").exists()); + assert!(fuzz_dir.join("FuzzTest.t.sol").exists()); + + std::env::set_current_dir(original_dir)?; + Ok(()) + } } diff --git a/src/main.rs b/src/main.rs index 6602347..04949a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,11 @@ -mod cli; -mod gen; -mod types; - use anyhow::Result; use clap::Parser; - -use crate::cli::Args; -use crate::gen::generate_test_suite; -use crate::types::ContractType; +use medusa_gen::cli::Args; fn main() -> Result<()> { let args = Args::parse(); - generate_test_suite(&args)?; + medusa_gen::generate_test_suite(&args)?; Ok(()) }