diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index bdd0240..64f108c 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -19,39 +19,26 @@ jobs: steps: - name: Check out source files - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 1 - name: Update Toolchain - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - toolchain: stable - components: clippy + components: clippy, rustfmt - name: Build - uses: actions-rs/cargo@v1 - with: - command: build - args: --release --all-features + run: cargo build --release --all-features - name: Rustfmt - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --check + run: cargo fmt --check - name: Cargo Clippy - uses: actions-rs/cargo@v1 - with: - command: clippy - args: --all-targets --all-features -- -D warnings + run: cargo clippy --all-targets --all-features -- -D warnings - name: Cargo Test - uses: actions-rs/cargo@v1 - with: - command: test - args: --profile release + run: cargo test --profile release - name: Basic Testing continue-on-error: true @@ -70,7 +57,7 @@ jobs: ${{ github.workspace }}\target\release\dura.exe kill - name: Upload Binary - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: dura-${{ matrix.binary-suffix }}_${{ github.sha }} path: ${{ github.workspace }}/target/release/dura diff --git a/Cargo.lock b/Cargo.lock index 702832d..e27e66a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,15 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] [[package]] name = "adler" @@ -8,6 +17,12 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ansi_term" version = "0.12.1" @@ -29,6 +44,21 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide 0.8.9", + "object", + "rustc-demangle", + "windows-link", +] + [[package]] name = "base64" version = "0.13.1" @@ -104,6 +134,12 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "crc32fast" version = "1.3.2" @@ -124,14 +160,30 @@ dependencies = [ ] [[package]] -name = "crossbeam-utils" -version = "0.8.12" +name = "crossbeam-deque" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edbafec5fa1f196ca66527c1b12c2ec4745ca14b50f1ad8f9f6f720b55d11fac" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ - "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", ] +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + [[package]] name = "dashmap" version = "5.4.0" @@ -142,7 +194,7 @@ dependencies = [ "hashbrown", "lock_api", "once_cell", - "parking_lot_core 0.9.4", + "parking_lot_core", ] [[package]] @@ -179,6 +231,7 @@ dependencies = [ "serde_json", "serial_test", "sudo", + "sysinfo", "tempfile", "tokio", "toml", @@ -187,6 +240,12 @@ dependencies = [ "walkdir", ] +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "errno" version = "0.2.8" @@ -224,7 +283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.5.4", ] [[package]] @@ -322,9 +381,15 @@ checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" dependencies = [ "cfg-if", "libc", - "wasi", + "wasi 0.10.0+wasi-snapshot-preview1", ] +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + [[package]] name = "git2" version = "0.15.0" @@ -405,7 +470,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -417,7 +482,7 @@ dependencies = [ "hermit-abi 0.2.6", "io-lifetimes", "rustix", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -443,9 +508,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.137" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libgit2-sys" @@ -529,9 +594,9 @@ checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" -version = "2.4.1" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "minimal-lexical" @@ -549,25 +614,23 @@ dependencies = [ ] [[package]] -name = "mio" -version = "0.7.14" +name = "miniz_oxide" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "libc", - "log", - "miow", - "ntapi", - "winapi", + "adler2", ] [[package]] -name = "miow" -version = "0.3.7" +name = "mio" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ - "winapi", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.48.0", ] [[package]] @@ -582,9 +645,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.3.6" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] @@ -618,6 +681,15 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + [[package]] name = "once_cell" version = "1.16.0" @@ -649,17 +721,6 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" -[[package]] -name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.5", -] - [[package]] name = "parking_lot" version = "0.12.1" @@ -667,21 +728,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.4", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] @@ -694,7 +741,7 @@ dependencies = [ "libc", "redox_syscall", "smallvec", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -730,7 +777,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.85", "version_check", ] @@ -747,22 +794,42 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.36" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.14" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47aa80447ce4daf1717500037052af176af5d38cc3e571d9ec1c7353fc10c87d" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.2.10" @@ -815,6 +882,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + [[package]] name = "rustix" version = "0.36.3" @@ -826,7 +899,7 @@ dependencies = [ "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.42.0", ] [[package]] @@ -867,7 +940,7 @@ checksum = "ed201699328568d8d08208fdd080e3ff594e6c422e438b6705905da01005d537" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.85", ] [[package]] @@ -891,7 +964,7 @@ dependencies = [ "futures", "lazy_static", "log", - "parking_lot 0.12.1", + "parking_lot", "serial_test_derive", ] @@ -904,7 +977,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.85", ] [[package]] @@ -940,6 +1013,16 @@ version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" +[[package]] +name = "socket2" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "strsim" version = "0.10.0" @@ -967,6 +1050,31 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sysinfo" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01" +dependencies = [ + "core-foundation-sys", + "libc", + "memchr", + "ntapi", + "rayon", + "windows", +] + [[package]] name = "tempfile" version = "3.3.0" @@ -1006,7 +1114,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" dependencies = [ "libc", - "wasi", + "wasi 0.10.0+wasi-snapshot-preview1", "winapi", ] @@ -1027,32 +1135,33 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.15.0" +version = "1.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbbf1c778ec206785635ce8ad57fe52b3009ae9e0c9f574a728f3049d3e55838" +checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" dependencies = [ + "autocfg", + "backtrace", "bytes", "libc", - "memchr", "mio", "num_cpus", - "once_cell", - "parking_lot 0.11.2", + "parking_lot", "pin-project-lite", "signal-hook-registry", + "socket2", "tokio-macros", - "winapi", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "1.7.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.117", ] [[package]] @@ -1084,7 +1193,7 @@ checksum = "f4f480b8f81512e825f337ad51e94c1eb5d3bbdf2b363dcd01e2b19a9ffe3f8e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.85", ] [[package]] @@ -1131,6 +1240,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + [[package]] name = "unicode-normalization" version = "0.1.19" @@ -1187,6 +1302,12 @@ version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "winapi" version = "0.3.9" @@ -1218,19 +1339,118 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143" +dependencies = [ + "windows-core", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.42.0", + "windows_aarch64_msvc 0.42.0", + "windows_i686_gnu 0.42.0", + "windows_i686_msvc 0.42.0", + "windows_x86_64_gnu 0.42.0", + "windows_x86_64_gnullvm 0.42.0", + "windows_x86_64_msvc 0.42.0", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -1239,38 +1459,128 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index 6134435..a7d36e3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ tracing = { version = "0.1.5"} tracing-subscriber = { version = "0.3", features = ["env-filter", "registry"] } walkdir = "2.3.2" sudo = "0.6.0" +sysinfo = "0.33" [dev-dependencies] tempfile = "3.2.0" diff --git a/src/config.rs b/src/config.rs index 635704c..cec5779 100644 --- a/src/config.rs +++ b/src/config.rs @@ -159,7 +159,7 @@ impl Config { } } - pub fn git_repos(&self) -> GitRepoIter { + pub fn git_repos(&self) -> GitRepoIter<'_> { GitRepoIter::new(self) } } diff --git a/src/database.rs b/src/database.rs index 89ef182..a367a1c 100644 --- a/src/database.rs +++ b/src/database.rs @@ -15,6 +15,14 @@ impl RuntimeLock { Self { pid: None } } + /// Check if the stored PID corresponds to a running process. + pub fn is_alive(&self) -> bool { + match self.pid { + None => false, + Some(pid) => is_process_alive(pid), + } + } + pub fn default_path() -> PathBuf { Self::get_dura_cache_home().join("runtime.db") } @@ -79,3 +87,10 @@ impl RuntimeLock { fs::write(path, json).unwrap() } } + +fn is_process_alive(pid: u32) -> bool { + let pid = sysinfo::Pid::from_u32(pid); + let mut sys = sysinfo::System::new(); + sys.refresh_processes(sysinfo::ProcessesToUpdate::Some(&[pid]), true); + sys.process(pid).is_some() +} diff --git a/src/log.rs b/src/log.rs index 601573d..2099059 100644 --- a/src/log.rs +++ b/src/log.rs @@ -43,6 +43,7 @@ impl Operation { } #[derive(Debug, Serialize, Deserialize)] +#[allow(dead_code)] struct Stats { dir_stats: Histo, loop_stats: Histo, diff --git a/src/main.rs b/src/main.rs index 8a1ab50..340c7df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,8 @@ use std::process; use clap::builder::IntoResettable; use clap::{ - arg, crate_authors, crate_description, crate_name, crate_version, value_parser, Arg, Command, + arg, crate_authors, crate_description, crate_name, crate_version, value_parser, Arg, ArgAction, + Command, }; use dura::config::{Config, WatchConfig}; use dura::database::RuntimeLock; @@ -86,7 +87,7 @@ async fn main() { .required(false) .action(clap::builder::ArgAction::Set) .value_parser(value_parser!(String)) - .default_value(&"255".to_string()) + .default_value("255".to_string()) .num_args(0..=1) .help("Determines the depth to recurse into when scanning directories") ) @@ -104,6 +105,17 @@ async fn main() { .long_flag("kill") .about("Stop the running worker (should only be a single worker).") ) + .subcommand( + Command::new("status") + .long_flag("status") + .about("Show whether the dura daemon is running and list watched repositories.") + .arg( + Arg::new("json") + .long("json") + .help("Output in JSON format") + .action(ArgAction::SetTrue) + ) + ) .subcommand( Command::new("metrics") .short_flag('M') @@ -204,6 +216,10 @@ async fn main() { Some(("kill", _)) => { kill(); } + Some(("status", arg_matches)) => { + let json = arg_matches.get_flag("json"); + status(json); + } Some(("metrics", arg_matches)) => { let mut input: Box = match arg_matches.get_one::("input") { Some(input) => Box::new( @@ -248,7 +264,7 @@ fn unwatch_dir(path: &std::path::Path) { config.save(); } -#[cfg(all(unix))] +#[cfg(unix)] fn check_if_user() -> bool { sudo::check() != sudo::RunningAs::Root } @@ -269,3 +285,63 @@ fn kill() { runtime_lock.pid = None; runtime_lock.save(); } + +fn status(json: bool) { + let runtime_lock = RuntimeLock::load(); + let is_running = runtime_lock.is_alive(); + let config = Config::load(); + let config_path = Config::default_path(); + let cache_path = RuntimeLock::default_path() + .parent() + .map(|p| p.to_path_buf()) + .unwrap_or_else(RuntimeLock::default_path); + + if json { + let repos: Vec = config + .repos + .keys() + .map(|path| { + serde_json::json!({ + "path": path, + }) + }) + .collect(); + + let output = serde_json::json!({ + "daemon": { + "running": is_running, + "pid": if is_running { runtime_lock.pid } else { None }, + }, + "repositories": repos, + "config_path": config_path.to_string_lossy(), + "cache_path": cache_path.to_string_lossy(), + }); + + println!("{}", serde_json::to_string_pretty(&output).unwrap()); + } else { + if is_running { + eprintln!("dura serve is running (PID {})", runtime_lock.pid.unwrap()); + } else { + eprintln!("dura serve is not running"); + } + + eprintln!(); + eprintln!("Config: {}", config_path.display()); + eprintln!("Cache: {}", cache_path.display()); + + if !config.repos.is_empty() { + eprintln!(); + eprintln!("Watching {} repositories:", config.repos.len()); + for path in config.repos.keys() { + eprintln!(" {path}"); + } + } else if is_running { + eprintln!(); + eprintln!("No repositories are being watched. Run `dura watch` in a git repository."); + } + } + + if !is_running { + process::exit(1); + } +} diff --git a/src/metrics.rs b/src/metrics.rs index 09b6c35..8a21bea 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -99,7 +99,7 @@ fn scrape_git( .and_then(|c| c.as_str()) .and_then(|c| Oid::from_str(c).ok()) .and_then(|c| repo.find_commit(c).ok()); - let parent_commit = commit_opt.as_ref().and_then(|c| c.parents().last()); + let parent_commit = commit_opt.as_ref().and_then(|c| c.parents().next_back()); if let (Some(commit), Some(parent)) = (commit_opt, parent_commit) { let diff = repo.diff_tree_to_tree(Some(&parent.tree()?), Some(&commit.tree()?), None)?; diff --git a/src/poll_guard.rs b/src/poll_guard.rs index 1ce4947..6cc754a 100644 --- a/src/poll_guard.rs +++ b/src/poll_guard.rs @@ -45,7 +45,6 @@ impl PollGuard { for entry in WalkDir::new(dir) { if let Ok(modified) = get_file_time(entry) { if compare_times(modified, watermark).unwrap_or(false) { - dbg!(modified, watermark); return true; } } diff --git a/src/snapshots.rs b/src/snapshots.rs index f35f19d..e902e07 100644 --- a/src/snapshots.rs +++ b/src/snapshots.rs @@ -1,4 +1,6 @@ -use git2::{BranchType, DiffOptions, Error, IndexAddOption, Repository, Signature}; +use git2::{ + BranchType, Diff, DiffOptions, Error, ErrorCode, IndexAddOption, Repository, Signature, +}; use serde::{Deserialize, Serialize}; use std::fmt; use std::path::Path; @@ -28,22 +30,38 @@ pub fn is_repo(path: &Path) -> bool { pub fn capture(path: &Path) -> Result, Error> { let repo = Repository::open(path)?; - let head = repo.head()?.peel_to_commit()?; - let message = "dura auto-backup"; - + let head = match repo.head() { + Ok(reference) => Some(reference.peel_to_commit()?), + Err(e) if e.code() == ErrorCode::UnbornBranch => None, + Err(e) => return Err(e), + }; // status check if repo.statuses(None)?.is_empty() { return Ok(None); } - let branch_name = format!("dura/{}", head.id()); + let branch_name = match &head { + Some(commit) => format!("dura/{}", commit.id()), + None => "dura/unborn".to_string(), + }; + let branch_commit = match repo.find_branch(&branch_name, BranchType::Local) { Ok(mut branch) => { match branch.get().peel_to_commit() { - Ok(commit) if commit.id() != head.id() => Some(commit), + Ok(commit) => { + // For normal repos: if the branch commit equals head, dura hasn't + // made any backup yet — clean up and start fresh. + // For unborn repos: any existing commit is a valid prior backup. + let dominated_by_head = head.as_ref().is_some_and(|h| commit.id() == h.id()); + if dominated_by_head { + branch.delete()?; + None + } else { + Some(commit) + } + } _ => { - // Dura branch exist but no commit is made by dura - // So we clean this branch + // Dura branch exists but can't resolve to commit — clean up branch.delete()?; None } @@ -51,14 +69,21 @@ pub fn capture(path: &Path) -> Result, Error> { } Err(_) => None, }; - let parent_commit = branch_commit.as_ref().unwrap_or(&head); + + // Parent is either an existing dura branch commit or the head commit. + // For unborn repos with no prior dura backup, there is no parent. + let parent = branch_commit.as_ref().or(head.as_ref()); // tree let mut index = repo.index()?; index.add_all(["*"].iter(), IndexAddOption::DEFAULT, None)?; + let old_tree = match parent { + Some(commit) => Some(commit.tree()?), + None => None, + }; let dirty_diff = repo.diff_tree_to_index( - Some(&parent_commit.tree()?), + old_tree.as_ref(), Some(&index), Some(DiffOptions::new().include_untracked(true)), )?; @@ -66,29 +91,97 @@ pub fn capture(path: &Path) -> Result, Error> { return Ok(None); } + // Build informative commit message with file names and change stats + let message = build_commit_message(&dirty_diff); + let tree_oid = index.write_tree()?; let tree = repo.find_tree(tree_oid)?; + + // Create dura branch if it doesn't exist. + // For unborn repos we skip this — repo.commit() with update_ref will create the ref. if repo.find_branch(&branch_name, BranchType::Local).is_err() { - repo.branch(branch_name.as_str(), &head, false)?; + if let Some(head_commit) = &head { + repo.branch(branch_name.as_str(), head_commit, false)?; + } } let committer = Signature::now(&get_git_author(&repo), &get_git_email(&repo))?; + let parents: Vec<&git2::Commit> = parent.into_iter().collect(); let oid = repo.commit( Some(&format!("refs/heads/{}", &branch_name)), &committer, &committer, - message, + &message, &tree, - &[parent_commit], + &parents, )?; + let base_hash = head + .as_ref() + .map_or("unborn".to_string(), |h| h.id().to_string()); + Ok(Some(CaptureStatus { dura_branch: branch_name, commit_hash: oid.to_string(), - base_hash: head.id().to_string(), + base_hash, })) } +/// Build an informative commit message from diff stats. +/// +/// Format: `dura: 3 files (+45/-12) model.py, config.rs, utils.rs` +/// +/// The first line stays under 72 chars for git log readability. +/// File names are base names only (no directories) to save space. +fn build_commit_message(diff: &Diff) -> String { + let stats = match diff.stats() { + Ok(s) => s, + Err(_) => return "dura auto-backup".to_string(), + }; + + let files_changed = stats.files_changed(); + let insertions = stats.insertions(); + let deletions = stats.deletions(); + + // Collect base file names from deltas + let file_names: Vec = diff + .deltas() + .filter_map(|delta| { + delta + .new_file() + .path() + .and_then(|p| p.file_name()) + .map(|n| n.to_string_lossy().into_owned()) + }) + .collect(); + + // Build first line: "dura: 3 files (+45/-12) model.py, config.rs" + let header = format!( + "dura: {} file{} (+{}/-{})", + files_changed, + if files_changed == 1 { "" } else { "s" }, + insertions, + deletions + ); + + if file_names.is_empty() { + return header; + } + + // Append file names, truncating to keep first line under 72 chars + let mut first_line = format!("{} {}", header, file_names[0]); + for name in &file_names[1..] { + let candidate = format!("{}, {}", first_line, name); + if candidate.len() > 72 { + first_line = format!("{}...", first_line); + break; + } + first_line = candidate; + } + + first_line +} + fn get_git_author(repo: &Repository) -> String { let dura_cfg = Config::load(); if let Some(value) = dura_cfg.commit_author { diff --git a/tests/snapshots_test.rs b/tests/snapshots_test.rs index d411831..5fae6ca 100644 --- a/tests/snapshots_test.rs +++ b/tests/snapshots_test.rs @@ -146,3 +146,97 @@ fn test_commit_signature_exclude_git_config() { .unwrap(); assert_eq!(commit_email, "dura@github.io"); } + +/// Commit messages should include file names and change stats +#[test] +fn commit_message_includes_file_info() { + let tmp = tempfile::tempdir().unwrap(); + let mut repo = repo_and_file!(tmp, "model.py"); + repo.change_file("model.py"); + let status = snapshots::capture(repo.dir.as_path()).unwrap().unwrap(); + + let message = repo + .git(&["show", "-s", "--format=format:%s", &status.commit_hash]) + .unwrap(); + assert!(message.starts_with("dura: 1 file"), "got: {}", message); + assert!(message.contains("model.py"), "got: {}", message); +} + +/// Commit messages with multiple files show stats and file names +#[test] +fn commit_message_multiple_files() { + let tmp = tempfile::tempdir().unwrap(); + let mut repo = repo_and_file!(tmp, "foo.txt"); + repo.write_file("bar.txt"); + repo.commit_all(); + + repo.change_file("foo.txt"); + repo.change_file("bar.txt"); + let status = snapshots::capture(repo.dir.as_path()).unwrap().unwrap(); + + let message = repo + .git(&["show", "-s", "--format=format:%s", &status.commit_hash]) + .unwrap(); + assert!(message.starts_with("dura: 2 files"), "got: {}", message); + assert!(message.contains("foo.txt"), "got: {}", message); + assert!(message.contains("bar.txt"), "got: {}", message); +} + +/// Repos with no commits (unborn branch) should be captured without error +#[test] +fn unborn_branch_first_capture() { + let tmp = tempfile::tempdir().unwrap(); + let repo = util::git_repo::GitRepo::new(tmp.path().to_path_buf()); + repo.init(); + // Don't commit — this is the unborn branch case + repo.write_file("foo.txt"); + + let status = snapshots::capture(repo.dir.as_path()).unwrap().unwrap(); + + assert_eq!(status.dura_branch, "dura/unborn"); + assert_eq!(status.base_hash, "unborn"); +} + +/// Subsequent captures on an unborn branch reuse the prior dura commit as parent +#[test] +fn unborn_branch_subsequent_capture() { + let tmp = tempfile::tempdir().unwrap(); + let mut repo = util::git_repo::GitRepo::new(tmp.path().to_path_buf()); + repo.init(); + repo.write_file("foo.txt"); + + // First capture + let status1 = snapshots::capture(repo.dir.as_path()).unwrap().unwrap(); + assert_eq!(status1.dura_branch, "dura/unborn"); + + // Change the file and capture again + repo.change_file("foo.txt"); + let status2 = snapshots::capture(repo.dir.as_path()).unwrap().unwrap(); + + assert_eq!(status2.dura_branch, "dura/unborn"); + assert_ne!(status2.commit_hash, status1.commit_hash); +} + +/// After user makes their first commit, dura switches from unborn to normal branch +#[test] +fn unborn_branch_transitions_to_normal() { + let tmp = tempfile::tempdir().unwrap(); + let mut repo = util::git_repo::GitRepo::new(tmp.path().to_path_buf()); + repo.init(); + repo.write_file("foo.txt"); + + // Capture on unborn + let status1 = snapshots::capture(repo.dir.as_path()).unwrap().unwrap(); + assert_eq!(status1.dura_branch, "dura/unborn"); + + // User makes their first commit + repo.commit_all(); + // Change a file + repo.change_file("foo.txt"); + + // Now dura should use the normal branch naming + let status2 = snapshots::capture(repo.dir.as_path()).unwrap().unwrap(); + assert!(status2.dura_branch.starts_with("dura/")); + assert_ne!(status2.dura_branch, "dura/unborn"); + assert_ne!(status2.base_hash, "unborn"); +} diff --git a/tests/status_test.rs b/tests/status_test.rs new file mode 100644 index 0000000..6b8a436 --- /dev/null +++ b/tests/status_test.rs @@ -0,0 +1,194 @@ +mod util; + +use crate::util::dura::Dura; +use crate::util::git_repo::GitRepo; +use dura::database::RuntimeLock; + +const START_TIMEOUT: u64 = 8; + +#[test] +fn status_when_daemon_not_running() { + let dura = Dura::new(); + let output = dura.run_output(&["status"]); + + assert_eq!(output.status.code(), Some(1)); + let stderr = String::from_utf8(output.stderr).unwrap(); + let stdout = String::from_utf8(output.stdout).unwrap(); + assert!(stdout.is_empty(), "Human mode should not write to stdout"); + assert!( + stderr.contains("not running"), + "Expected 'not running' on stderr, got: {stderr}" + ); + assert!( + stderr.contains("Config:"), + "Expected 'Config:' path on stderr, got: {stderr}" + ); + assert!( + stderr.contains("Cache:"), + "Expected 'Cache:' path on stderr, got: {stderr}" + ); +} + +#[test] +fn status_when_daemon_running_with_repos() { + let tmp = tempfile::tempdir().unwrap(); + let repo = GitRepo::new(tmp.path().to_path_buf()); + repo.init(); + + let mut dura = Dura::new(); + dura.run_in_dir(&["watch"], tmp.path()); + dura.start_async(&["serve"], true); + dura.primary + .as_ref() + .map(|d| d.read_line(START_TIMEOUT).unwrap()); + + let output = dura.run_output(&["status"]); + + assert_eq!(output.status.code(), Some(0)); + let stdout = String::from_utf8(output.stdout).unwrap(); + let stderr = String::from_utf8(output.stderr).unwrap(); + assert!(stdout.is_empty(), "Human mode should not write to stdout"); + + let pid = dura.pid(true).unwrap(); + assert!( + stderr.contains(&format!("PID {pid}")), + "Expected PID on stderr, got: {stderr}" + ); + assert!( + stderr.contains("Config:"), + "Expected 'Config:' path on stderr, got: {stderr}" + ); + assert!( + stderr.contains("Cache:"), + "Expected 'Cache:' path on stderr, got: {stderr}" + ); + + let canonical = tmp.path().canonicalize().unwrap(); + let canonical_str = canonical.to_str().unwrap(); + assert!( + stderr.contains(canonical_str), + "Expected repo path '{canonical_str}' on stderr, got: {stderr}" + ); +} + +#[test] +fn status_stale_pid() { + let dura = Dura::new(); + let mut runtime_lock = RuntimeLock::empty(); + runtime_lock.pid = Some(99999); + dura.save_runtime_lock(&runtime_lock); + + let output = dura.run_output(&["status"]); + + assert_eq!(output.status.code(), Some(1)); + let stderr = String::from_utf8(output.stderr).unwrap(); + let stdout = String::from_utf8(output.stdout).unwrap(); + assert!(stdout.is_empty(), "Human mode should not write to stdout"); + assert!( + stderr.contains("not running"), + "Expected 'not running' on stderr, got: {stderr}" + ); + assert!( + stderr.contains("Config:"), + "Expected 'Config:' path on stderr, got: {stderr}" + ); +} + +#[test] +fn status_daemon_running_no_repos() { + let mut dura = Dura::new(); + dura.start_async(&["serve"], true); + dura.primary + .as_ref() + .map(|d| d.read_line(START_TIMEOUT).unwrap()); + + let output = dura.run_output(&["status"]); + + assert_eq!(output.status.code(), Some(0)); + let stdout = String::from_utf8(output.stdout).unwrap(); + let stderr = String::from_utf8(output.stderr).unwrap(); + assert!(stdout.is_empty(), "Human mode should not write to stdout"); + assert!( + stderr.contains("No repositories"), + "Expected 'No repositories' on stderr, got: {stderr}" + ); + assert!( + stderr.contains("Config:"), + "Expected 'Config:' path on stderr, got: {stderr}" + ); +} + +#[test] +fn status_json_when_not_running() { + let dura = Dura::new(); + let output = dura.run_output(&["status", "--json"]); + + assert_eq!(output.status.code(), Some(1)); + let stdout = String::from_utf8(output.stdout).unwrap(); + let json: serde_json::Value = + serde_json::from_str(&stdout).expect("Expected valid JSON on stdout"); + + assert_eq!(json["daemon"]["running"], false); + assert_eq!(json["daemon"]["pid"], serde_json::Value::Null); + assert!(json["repositories"].is_array()); + assert!( + json["config_path"].is_string(), + "Expected config_path in JSON output" + ); + assert!( + json["cache_path"].is_string(), + "Expected cache_path in JSON output" + ); +} + +#[test] +fn status_json_when_running_with_repos() { + let tmp = tempfile::tempdir().unwrap(); + let repo = GitRepo::new(tmp.path().to_path_buf()); + repo.init(); + + let mut dura = Dura::new(); + dura.run_in_dir(&["watch"], tmp.path()); + dura.start_async(&["serve"], true); + dura.primary + .as_ref() + .map(|d| d.read_line(START_TIMEOUT).unwrap()); + + let output = dura.run_output(&["status", "--json"]); + + assert_eq!(output.status.code(), Some(0)); + let stdout = String::from_utf8(output.stdout).unwrap(); + let json: serde_json::Value = + serde_json::from_str(&stdout).expect("Expected valid JSON on stdout"); + + assert_eq!(json["daemon"]["running"], true); + assert!(json["daemon"]["pid"].is_number()); + + let repos = json["repositories"].as_array().unwrap(); + assert!(!repos.is_empty()); + + let canonical = tmp.path().canonicalize().unwrap(); + let canonical_str = canonical.to_str().unwrap(); + let has_repo = repos + .iter() + .any(|r| r["path"].as_str() == Some(canonical_str)); + assert!( + has_repo, + "Expected repo path '{canonical_str}' in JSON repositories, got: {repos:?}" + ); + + let first_repo = &repos[0]; + assert!( + first_repo.as_object().unwrap().len() == 1, + "Expected repo object to have only 'path' key, got: {first_repo:?}" + ); + + assert!( + json["config_path"].is_string(), + "Expected config_path in JSON output" + ); + assert!( + json["cache_path"].is_string(), + "Expected cache_path in JSON output" + ); +} diff --git a/tests/util/dura.rs b/tests/util/dura.rs index 2575777..507f6ab 100644 --- a/tests/util/dura.rs +++ b/tests/util/dura.rs @@ -98,6 +98,17 @@ impl Dura { } } + pub fn run_output(&self, args: &[&str]) -> std::process::Output { + println!("$ dura {}", args.join(" ")); + let exe = env!("CARGO_BIN_EXE_dura").to_string(); + Command::new(exe) + .args(args) + .env("DURA_CONFIG_HOME", self.config_dir.path()) + .env("DURA_CACHE_HOME", self.cache_dir.path()) + .output() + .expect("Failed to execute dura") + } + pub fn pid(&self, is_primary: bool) -> Option { if is_primary { self.primary.as_ref().map(|d| d.child.id())