diff --git a/.env.example b/.env.example index 4c3da3c..abc9179 100644 --- a/.env.example +++ b/.env.example @@ -1,6 +1,5 @@ -CACHE_DIR=/tmp/artifactview -MAX_ARTIFACT_SIZE=100000000 -MAX_AGE_H=12 +NO_HTTPS=1 # If you only want to access public repositories, # create a fine-grained token with Public Repositories (read-only) access -GITHUB_TOKEN=github_pat_123456 +# GITHUB_TOKEN=github_pat_123456 +SITE_ALIASES=gh=>github.com;cb=>codeberg.org diff --git a/.forgejo/workflows/artifact.yaml b/.forgejo/workflows/artifact.yaml new file mode 100644 index 0000000..0470fa2 --- /dev/null +++ b/.forgejo/workflows/artifact.yaml @@ -0,0 +1,43 @@ +name: Test artifact +on: + push: + branches: + - main + paths: + - ".forgejo/workflows/artifact.yaml" + +jobs: + artifact: + runs-on: cimaster-latest + steps: + - name: ๐Ÿ‘๏ธ Checkout repository + uses: actions/checkout@v4 + - name: Create test artifacts + run: | + # A1 + mkdir -p artifacts/a1/mdbook + cd artifacts + git clone https://github.com/rust-lang/mdBook + cd mdBook/test_book + mdbook build -d ../../a1/mdbook + cd ../.. + + cp -r ../tests/testfiles/junit ../tests/testfiles/sites/style.css ../tests/testfiles/sites/404.html ../tests/testfiles/sites/example.rs ../README.md a1 + + # A2 + git clone https://github.com/SveltePress/sveltepress + cd sveltepress/packages/docs-site + pnpm i + pnpm build + mv dist ../../../a2 + - name: Upload A1 (Example) + uses: https://code.forgejo.org/forgejo/upload-artifact@v4 + with: + name: Example + path: artifacts/a1 + + - name: Upload A2 (SveltePress) + uses: https://code.forgejo.org/forgejo/upload-artifact@v4 + with: + name: SveltePress + path: artifacts/a2 diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml index 9ca8bff..1da8c3b 100644 --- a/.forgejo/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -1,6 +1,8 @@ name: CI on: push: + branches: + - "main" pull_request: jobs: @@ -16,53 +18,21 @@ jobs: - name: ๐Ÿ“Ž Clippy run: cargo clippy --all -- -D warnings - name: ๐Ÿงช Test - run: cargo test - - release: - runs-on: cimaster-latest - needs: test - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - steps: - - name: ๐Ÿ‘๏ธ Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 0 # important to fetch tag logs - - - name: โš’๏ธ Build application run: | - PKG_CONFIG_SYSROOT_DIR=/usr/x86_64-linux-gnu cargo build --release --target x86_64-unknown-linux-gnu - PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu cargo build --release --target aarch64-unknown-linux-gnu - - name: ๐Ÿ‹ Build docker image - uses: https://code.thetadev.de/ThetaDev/action-kaniko@v1 + ./tests/testfiles/sites/make_zip.sh + cargo nextest run --config-file ~/.config/nextest.toml --profile ci --workspace + - name: ๐Ÿ’Œ Upload test report + if: always() + uses: https://code.forgejo.org/forgejo/upload-artifact@v4 with: - image: thetadev256/artifactview - username: thetadev256 - password: ${{ secrets.DOCKER_TOKEN }} - tag: ${{ github.ref_name }} - tag_with_latest: ${{ startsWith(github.ref, 'refs/tags/v') }} - platforms: "linux/amd64,linux/arm64" - - - name: Prepare release - if: ${{ startsWith(github.ref, 'refs/tags/v') }} + name: test + path: target/nextest/ci/junit.xml + - name: ๐Ÿ”— Artifactview PR comment + if: ${{ always() && github.event_name == 'pull_request' }} run: | - mkdir dist - tar -cJf dist/artifactview-x86_64-${{ github.ref_name }}.tar.xz -C target/x86_64-unknown-linux-gnu/release artifactview - tar -cJf dist/artifactview-aarch64-${{ github.ref_name }}.tar.xz -C target/aarch64-unknown-linux-gnu/release artifactview + if [[ "$GITEA_ACTIONS" == "true" ]]; then RUN_NUMBER="$GITHUB_RUN_NUMBER"; else RUN_NUMBER="$GITHUB_RUN_ID"; fi + echo "Run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$RUN_NUMBER" + echo "Pull: ${{ github.event.number }}" - { - echo 'CHANGELOG<> "$GITHUB_ENV" - - - name: ๐ŸŽ‰ Publish release - if: ${{ startsWith(github.ref, 'refs/tags/v') }} - uses: https://gitea.com/actions/release-action@main - with: - title: "artifactview ${{ github.ref_name }}" - body: "${{ env.CHANGELOG }}" - files: dist/* - - - name: ๐Ÿš€ Deploy to server - run: | - curl -s -H "Authorization: Bearer ${{ secrets.THETADEV_DE_WATCHTOWER_TOKEN }}" https://watchtower.thetadev.de/v1/update + curl -SsL --fail-with-body -w "\n" -X POST https://av.thetadev.de/.well-known/api/prComment -H "Content-Type: application/json" \ + --data '{"url": "'"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$RUN_NUMBER"'", "pr": ${{ github.event.number }}, "artifact_titles": {"test":"๐Ÿงช Test report"}, "artifact_paths": {"test":"/junit.xml?viewer=1"}}' diff --git a/.forgejo/workflows/docker-readme.yaml b/.forgejo/workflows/docker-readme.yaml new file mode 100644 index 0000000..05e4984 --- /dev/null +++ b/.forgejo/workflows/docker-readme.yaml @@ -0,0 +1,23 @@ +name: DockerHub README +on: + push: + branches: + - main + paths: + - "README.md" + - ".forgejo/workflows/docker-readme.yaml" + +jobs: + update: + runs-on: cimaster-latest + steps: + - name: ๐Ÿ‘๏ธ Checkout repository + uses: actions/checkout@v4 + + - name: Docker Hub Description + uses: https://github.com/peter-evans/dockerhub-description@v4 + with: + username: thetadev256 + password: ${{ secrets.DOCKERHUB_PASSWORD }} + repository: thetadev256/artifactview + enable-url-completion: true diff --git a/.forgejo/workflows/release.yaml b/.forgejo/workflows/release.yaml new file mode 100644 index 0000000..ec2484d --- /dev/null +++ b/.forgejo/workflows/release.yaml @@ -0,0 +1,75 @@ +name: Release +on: + push: + tags: + - "v*" + +jobs: + release: + runs-on: cimaster-latest + steps: + - name: ๐Ÿ‘๏ธ Checkout repository + uses: actions/checkout@v4 + + - name: โš’๏ธ Build application + run: | + PKG_CONFIG_SYSROOT_DIR=/usr/x86_64-linux-gnu cargo build --release --target x86_64-unknown-linux-gnu + PKG_CONFIG_SYSROOT_DIR=/usr/aarch64-linux-gnu cargo build --release --target aarch64-unknown-linux-gnu + + ln -s ./x86_64-unknown-linux-gnu target/amd64 + ln -s ./aarch64-unknown-linux-gnu target/arm64 + + - name: Setup Docker Buildx + uses: https://github.com/docker/setup-buildx-action@v3 + + - name: Login to Container Registry + uses: https://github.com/docker/login-action@v3 + with: + username: thetadev256 + password: ${{ secrets.DOCKER_TOKEN }} + + - name: Docker meta + id: meta + uses: https://github.com/docker/metadata-action@v5 + with: + images: | + thetadev256/artifactview + tags: | + type=semver,pattern=v{{version}} + type=semver,pattern=v{{major}}.{{minor}} + type=semver,pattern=v{{major}} + + - name: ๐Ÿ‹ Build docker image + uses: https://github.com/docker/build-push-action@v6 + with: + push: true + tags: ${{ steps.meta.outputs.tags }} + annotations: ${{ steps.meta.outputs.annotations }} + platforms: linux/amd64,linux/arm64 + context: "." + file: Dockerfile + + - name: Prepare release + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + run: | + mkdir dist + tar -cJf dist/artifactview-x86_64-${{ github.ref_name }}.tar.xz -C target/x86_64-unknown-linux-gnu/release artifactview + tar -cJf dist/artifactview-aarch64-${{ github.ref_name }}.tar.xz -C target/aarch64-unknown-linux-gnu/release artifactview + + { + echo 'CHANGELOG<> "$GITHUB_ENV" + + - name: ๐ŸŽ‰ Publish release + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + uses: https://gitea.com/actions/release-action@main + with: + title: "artifactview ${{ github.ref_name }}" + body: "${{ env.CHANGELOG }}" + files: dist/* + + - name: ๐Ÿš€ Deploy to server + run: | + curl -SsL --fail-with-body -H "Authorization: Bearer ${{ secrets.THETADEV_DE_WATCHTOWER_TOKEN }}" https://watchtower.thetadev.de/v1/update diff --git a/.forgejo/workflows/renovate.yaml.bak b/.forgejo/workflows/renovate.yaml.bak new file mode 100644 index 0000000..3bada59 --- /dev/null +++ b/.forgejo/workflows/renovate.yaml.bak @@ -0,0 +1,63 @@ +name: renovate + +on: + push: + branches: ["main"] + paths: + - ".forgejo/workflows/renovate.yaml" + - "renovate.json" + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +env: + RENOVATE_REPOSITORIES: ${{ github.repository }} + +jobs: + renovate: + runs-on: docker + container: + image: renovate/renovate:39 + + steps: + - name: Load renovate repo cache + uses: actions/cache/restore@v4 + with: + path: | + .tmp/cache/renovate/repository + .tmp/cache/renovate/renovate-cache-sqlite + .tmp/osv + key: repo-cache-${{ github.run_id }} + restore-keys: | + repo-cache- + + - name: Run renovate + run: renovate + env: + LOG_LEVEL: info + RENOVATE_BASE_DIR: ${{ github.workspace }}/.tmp + RENOVATE_ENDPOINT: ${{ github.server_url }} + RENOVATE_PLATFORM: gitea + RENOVATE_REPOSITORY_CACHE: 'enabled' + RENOVATE_TOKEN: ${{ secrets.FORGEJO_CI_BOT_TOKEN }} + GITHUB_COM_TOKEN: ${{ secrets.GH_PUBLIC_TOKEN }} + RENOVATE_GIT_AUTHOR: 'Renovate Bot ' + + RENOVATE_X_SQLITE_PACKAGE_CACHE: true + + GIT_AUTHOR_NAME: 'Renovate Bot' + GIT_AUTHOR_EMAIL: 'forgejo-renovate-action@forgejo.org' + GIT_COMMITTER_NAME: 'Renovate Bot' + GIT_COMMITTER_EMAIL: 'forgejo-renovate-action@forgejo.org' + + OSV_OFFLINE_ROOT_DIR: ${{ github.workspace }}/.tmp/osv + + - name: Save renovate repo cache + if: always() && env.RENOVATE_DRY_RUN != 'full' + uses: actions/cache/save@v4 + with: + path: | + .tmp/cache/renovate/repository + .tmp/cache/renovate/renovate-cache-sqlite + .tmp/osv + key: repo-cache-${{ github.run_id }} diff --git a/.gitignore b/.gitignore index aebe389..4644368 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /target /dist /.env +*.snap.new +/tests/testfiles/sites_data diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c77c173..0aca880 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,3 +10,11 @@ repos: - id: cargo-fmt - id: cargo-clippy args: ["--all", "--tests", "--", "-D", "warnings"] + + - repo: local + hooks: + - id: compress-res + name: Compress resources + language: system + entry: zopfli + files: "^resources/.+.css$" diff --git a/CHANGELOG.md b/CHANGELOG.md index 940ddbe..56b521c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,334 @@ All notable changes to this project will be documented in this file. +## [v0.4.9](https://codeberg.org/ThetaDev/artifactview/compare/v0.4.8..v0.4.9) - 2025-07-20 + +### ๐Ÿ› Bug Fixes + +- TARGETARCH argument Buildkit workflow - ([c911af0](https://codeberg.org/ThetaDev/artifactview/commit/c911af085eb4e434f9976a9573d19e8c05985d3c)) + +### โš™๏ธ Miscellaneous Tasks + +- *(deps)* Update rust crate axum-test to v17.2.0 (#171) - ([dfa1ab5](https://codeberg.org/ThetaDev/artifactview/commit/dfa1ab54e8fd72113ed25d0a4a7f368db27fb2d0)) +- *(deps)* Update rust crate serde_json to v1.0.138 (#173) - ([de2e2fc](https://codeberg.org/ThetaDev/artifactview/commit/de2e2fc7b174b2cbe8440c3a17d8002d6edbba73)) +- *(deps)* Pin dependencies (#174) - ([aeebe74](https://codeberg.org/ThetaDev/artifactview/commit/aeebe74ddd07e1d8323f495a5065b8a9f06c2bc9)) +- *(deps)* Update rust crate env_logger to v0.11.6 (#175) - ([a970d8c](https://codeberg.org/ThetaDev/artifactview/commit/a970d8cea779690e150ddbcc416d26a2efe4a606)) +- *(deps)* Update rust crate zip to v2.2.2 (#181) - ([97852ce](https://codeberg.org/ThetaDev/artifactview/commit/97852ced9557b354642a703117b2a853e8f0c626)) +- Update dependencies - ([1367ed9](https://codeberg.org/ThetaDev/artifactview/commit/1367ed9ff1dd3e2ca142fab14c1846a2a6b46423)) +- Update release workflow to use Buildkit - ([e13bfa2](https://codeberg.org/ThetaDev/artifactview/commit/e13bfa2765b2bef0370efb01ba828cdf9a0da7b3)) +- Fix clippy lints - ([43e15ca](https://codeberg.org/ThetaDev/artifactview/commit/43e15ca849be79905af0c517507bc3bdd0e995bb)) + + +## [v0.4.8](https://codeberg.org/ThetaDev/artifactview/compare/v0.4.7..v0.4.8) - 2025-01-26 + +### ๐Ÿ› Bug Fixes + +- Lifetime-related lints - ([e20f6fb](https://codeberg.org/ThetaDev/artifactview/commit/e20f6fb92e86751222d2c5143ee384cdbea1159d)) + +### โš™๏ธ Miscellaneous Tasks + +- *(deps)* Update rust crate async-compression to v0.4.15 (#85) - ([4f4716c](https://codeberg.org/ThetaDev/artifactview/commit/4f4716cdd86c317ede2b381d375ef8e736aee240)) +- *(deps)* Update rust crate async-compression to v0.4.16 (#86) - ([9592da3](https://codeberg.org/ThetaDev/artifactview/commit/9592da3d6e2c2223174fcc459e94f29bf5067ead)) +- *(deps)* Update rust crate serde_json to v1.0.129 (#87) - ([2e46d37](https://codeberg.org/ThetaDev/artifactview/commit/2e46d3795089ef7b2739db4d216f4db99f792071)) +- *(deps)* Update rust crate serde_json to v1.0.131 (#88) - ([992f995](https://codeberg.org/ThetaDev/artifactview/commit/992f9954414af550fce90c9c7424ab0da2296875)) +- *(deps)* Update rust crate governor to v0.6.4 (#89) - ([f1d9897](https://codeberg.org/ThetaDev/artifactview/commit/f1d9897e832b5cdb99fd81edcb38d27bd6b445f8)) +- *(deps)* Update rust crate serde_json to v1.0.132 (#90) - ([600d18d](https://codeberg.org/ThetaDev/artifactview/commit/600d18d05b356c641a32b93b2407a1f23e2370c4)) +- *(deps)* Update rust crate async-compression to v0.4.17 (#91) - ([0b3c032](https://codeberg.org/ThetaDev/artifactview/commit/0b3c0325a50cd456cb9b62f97e916f0760785a3c)) +- *(deps)* Update rust crate governor to 0.7.0 (#92) - ([cbd304c](https://codeberg.org/ThetaDev/artifactview/commit/cbd304c84195983596156a95c26d65a89e93df8a)) +- *(deps)* Update rust crate thiserror to v1.0.65 (#94) - ([2df196a](https://codeberg.org/ThetaDev/artifactview/commit/2df196a2e666e8186d9ff66e885123c8a48c743c)) +- *(deps)* Update rust crate serde to v1.0.213 (#93) - ([e5b9105](https://codeberg.org/ThetaDev/artifactview/commit/e5b9105da1fb0c63584cc409b5dd98c1fe045f9b)) +- *(deps)* Update rust crate tokio to v1.41.0 (#95) - ([1dc4fe2](https://codeberg.org/ThetaDev/artifactview/commit/1dc4fe225c01f237f6a698fced7eff72cfb8ee85)) +- *(deps)* Update rust crate pin-project to v1.1.7 (#96) - ([c82bccc](https://codeberg.org/ThetaDev/artifactview/commit/c82bccca9098281fa42ded5d5527eadb3cbcce88)) +- *(deps)* Update rust crate regex to v1.11.1 (#97) - ([613815a](https://codeberg.org/ThetaDev/artifactview/commit/613815aa1ebc01643e117ca9fafcb37bfe7d29d7)) +- *(deps)* Update rust crate axum-test to v16.3.0 (#98) - ([b2070ec](https://codeberg.org/ThetaDev/artifactview/commit/b2070ec460e45eeb9997885bd7cf54913f9f4183)) +- *(deps)* Update rust crate insta to v1.41.0 (#99) - ([5aec8d6](https://codeberg.org/ThetaDev/artifactview/commit/5aec8d677f089ff5092fe78655170d52e544baeb)) +- *(deps)* Update rust crate quick-xml to 0.37.0 (#100) - ([72e20d4](https://codeberg.org/ThetaDev/artifactview/commit/72e20d413e80b86d49ea0b275ce6aece99d75314)) +- *(deps)* Update rust crate reqwest to v0.12.9 (#101) - ([d45e8e6](https://codeberg.org/ThetaDev/artifactview/commit/d45e8e63c9ce4067a05839643581b9f42e4048ec)) +- *(deps)* Update rust crate serde to v1.0.214 (#102) - ([2a1ebd7](https://codeberg.org/ThetaDev/artifactview/commit/2a1ebd7b854ca82cceb768202e4d9ee984007311)) +- *(deps)* Update rust crate futures-lite to v2.4.0 (#103) - ([3bda063](https://codeberg.org/ThetaDev/artifactview/commit/3bda06357809e68c0804aa425e625893af1bbde2)) +- *(deps)* Update rust crate insta to v1.41.1 (#104) - ([a406bff](https://codeberg.org/ThetaDev/artifactview/commit/a406bffabeeb2627cd5ef74f3520add6eb0a8d6c)) +- *(deps)* Update rust crate thiserror to v1.0.66 (#105) - ([8f89fc9](https://codeberg.org/ThetaDev/artifactview/commit/8f89fc9953370c4325c63762e4451d37a1a4a64a)) +- *(deps)* Update rust crate scraper to 0.21.0 (#106) - ([1f00bbf](https://codeberg.org/ThetaDev/artifactview/commit/1f00bbfac53521cf0f76cf6fc4bf23e7a5e10562)) +- *(deps)* Update rust crate thiserror to v1.0.67 (#107) - ([39a76ea](https://codeberg.org/ThetaDev/artifactview/commit/39a76eaa334d3f57e30e3ba95eb781dfa7aee1ee)) +- *(deps)* Update rust crate thiserror to v1.0.68 (#108) - ([a48af07](https://codeberg.org/ThetaDev/artifactview/commit/a48af07d936a77977d602649fe579941b2cd2630)) +- *(deps)* Update rust crate url to v2.5.3 (#109) - ([44cc0c1](https://codeberg.org/ThetaDev/artifactview/commit/44cc0c10103eb8fc10ddcc17e559479fadcbe4f1)) +- *(deps)* Update rust crate tokio to v1.41.1 (#111) - ([dd809ce](https://codeberg.org/ThetaDev/artifactview/commit/dd809ce3f322fe8dafadeb802be7fcf905aa2f02)) +- *(deps)* Update rust crate thiserror to v2 (#110) - ([8cb636c](https://codeberg.org/ThetaDev/artifactview/commit/8cb636ccc9c93800e6e98522de5c38bb05e76fdb)) +- *(deps)* Update rust crate futures-lite to v2.5.0 (#112) - ([c05eb56](https://codeberg.org/ThetaDev/artifactview/commit/c05eb562a9d10945ee534ef50208aeb004023c51)) +- *(deps)* Update rust crate thiserror to v2.0.3 (#113) - ([a695cef](https://codeberg.org/ThetaDev/artifactview/commit/a695cef57d5c73492d806958290bb37bd9613125)) +- *(deps)* Update rust crate serde to v1.0.215 (#114) - ([3497592](https://codeberg.org/ThetaDev/artifactview/commit/34975924b1e1c3d5367346a1e0274a435496091f)) +- *(deps)* Update rust crate flate2 to v1.0.35 (#115) - ([0b9498c](https://codeberg.org/ThetaDev/artifactview/commit/0b9498c541c0188fd7c82a99b2f778251b831df3)) +- *(deps)* Update rust crate axum to v0.7.8 (#116) - ([79623d9](https://codeberg.org/ThetaDev/artifactview/commit/79623d9bc2d40e43374c4a2934d6bbac1235bae7)) +- *(deps)* Update rust crate axum-extra to v0.9.5 (#117) - ([b35cfe3](https://codeberg.org/ThetaDev/artifactview/commit/b35cfe3f4fe402943ee3238b671c18449c5437ba)) +- *(deps)* Update rust crate axum-test to v16.4.0 (#118) - ([e370001](https://codeberg.org/ThetaDev/artifactview/commit/e37000143e752b4bc496797de2410be33d3adf2b)) +- *(deps)* Update rust crate axum to v0.7.9 (#119) - ([cab58d2](https://codeberg.org/ThetaDev/artifactview/commit/cab58d284e6b9f2e6b730d9b3f6d648d0955832d)) +- *(deps)* Update rust crate quick-xml to v0.37.1 (#121) - ([dffcd16](https://codeberg.org/ThetaDev/artifactview/commit/dffcd16a60b456e9ed547b2a01342df3585e607f)) +- *(deps)* Update rust crate axum-extra to v0.9.6 (#120) - ([4cf0084](https://codeberg.org/ThetaDev/artifactview/commit/4cf0084e24969d15149468de42cf6ed15e1169f5)) +- *(deps)* Update rust crate serde_json to v1.0.133 (#122) - ([5231609](https://codeberg.org/ThetaDev/artifactview/commit/52316093cdad4ceca274e4c65035842e0413892e)) +- *(deps)* Update rust crate tower-http to v0.6.2 (#123) - ([8158497](https://codeberg.org/ThetaDev/artifactview/commit/8158497a73367fb2280d48350e3afa868c006d65)) +- *(deps)* Update rust crate zip to v2.2.1 (#124) - ([81c8521](https://codeberg.org/ThetaDev/artifactview/commit/81c852126ca45172bd00f75a5007263fefb5967b)) +- *(deps)* Update rust crate url to v2.5.4 (#125) - ([c99dfa8](https://codeberg.org/ThetaDev/artifactview/commit/c99dfa809175e84261245b2680a964b95d81b4e3)) +- *(deps)* Update rust crate comrak to 0.30.0 (#126) - ([389dd6f](https://codeberg.org/ThetaDev/artifactview/commit/389dd6f536b9044a8b9675c3ad34fa218028d154)) +- *(deps)* Update rust crate async-compression to v0.4.18 (#127) - ([05f20f4](https://codeberg.org/ThetaDev/artifactview/commit/05f20f44ac4910e600f318d656d376d52fd6b131)) +- *(deps)* Update rust crate comrak to 0.31.0 (#128) - ([5fd14aa](https://codeberg.org/ThetaDev/artifactview/commit/5fd14aada8f310fdeb0b2dc33a15de0b195ebc81)) +- *(deps)* Update rust crate tracing to v0.1.41 (#129) - ([ab3479f](https://codeberg.org/ThetaDev/artifactview/commit/ab3479f0d1b3a30413df654cc9f2e7a52081b542)) +- *(deps)* Update rust crate tracing-subscriber to v0.3.19 (#130) - ([1f9847b](https://codeberg.org/ThetaDev/artifactview/commit/1f9847b3edd73c1e905feb88087ee4325364ddda)) +- *(deps)* Update rust crate thiserror to v2.0.4 (#131) - ([e9d1226](https://codeberg.org/ThetaDev/artifactview/commit/e9d122639022d3b5b68b4626e00442a81acf85f5)) +- *(deps)* Update rust crate time to v0.3.37 (#132) - ([6fc7263](https://codeberg.org/ThetaDev/artifactview/commit/6fc7263f5996abefdb71fd5ecae277ec707cbdd9)) +- *(deps)* Update rust crate http to v1.2.0 (#133) - ([5f517ae](https://codeberg.org/ThetaDev/artifactview/commit/5f517ae6c784e4fbc99ca69650d85621e90f776c)) +- *(deps)* Update rust crate tokio to v1.42.0 (#134) - ([b15c4b0](https://codeberg.org/ThetaDev/artifactview/commit/b15c4b009a268cd4227846f166131aad5628f87d)) +- *(deps)* Update rust crate tokio-util to v0.7.13 (#135) - ([f43f06c](https://codeberg.org/ThetaDev/artifactview/commit/f43f06c33462c15a8a752493f14c86d9dbde0e8d)) +- *(deps)* Update rust crate thiserror to v2.0.5 (#136) - ([94c589c](https://codeberg.org/ThetaDev/artifactview/commit/94c589c20936c8e56be3147fd01e51d8b25e617f)) +- *(deps)* Update rust crate thiserror to v2.0.6 (#137) - ([5e83ab5](https://codeberg.org/ThetaDev/artifactview/commit/5e83ab510614db696a7245ad5027c717b3493cab)) +- *(deps)* Update rust crate chrono to v0.4.39 (#138) - ([8e9c5aa](https://codeberg.org/ThetaDev/artifactview/commit/8e9c5aad48378c2fa1a1d3d370b96de2fca5dd86)) +- *(deps)* Update rust crate governor to 0.8.0 (#139) - ([b24136e](https://codeberg.org/ThetaDev/artifactview/commit/b24136ec597d2b4c579176a6a7845878549478bb)) +- *(deps)* Update rust crate scraper to 0.22.0 (#140) - ([f48c570](https://codeberg.org/ThetaDev/artifactview/commit/f48c57021505091b22c36bd49bd4527e652b8d78)) +- *(deps)* Update rust crate serde to v1.0.216 (#141) - ([7d9827f](https://codeberg.org/ThetaDev/artifactview/commit/7d9827f9fc52aa88f19efa371e36877723328248)) +- *(deps)* Update rust crate thiserror to v2.0.7 (#142) - ([1e26d04](https://codeberg.org/ThetaDev/artifactview/commit/1e26d04b068f9c2e0a65a3dbcc43f66d118a6043)) +- *(deps)* Update rust crate axum-test to v16.4.1 (#143) - ([3244de4](https://codeberg.org/ThetaDev/artifactview/commit/3244de48fc2691898599ab20f2baa888db9f3c82)) +- *(deps)* Update rust crate zip to v2.2.2 (#144) - ([98ba21e](https://codeberg.org/ThetaDev/artifactview/commit/98ba21e7979bfbdd06fb46da8e45f97f71dc2c99)) +- *(deps)* Update rust crate proptest to v1.6.0 (#145) - ([d28f979](https://codeberg.org/ThetaDev/artifactview/commit/d28f9790b825b2af749e3fcd07718faa91c2a942)) +- *(deps)* Update rust crate comrak to 0.32.0 (#146) - ([f6b0e06](https://codeberg.org/ThetaDev/artifactview/commit/f6b0e06dc1a39311c4473e0f4c3c5742a352e00f)) +- *(deps)* Update rust crate thiserror to v2.0.8 (#147) - ([e87b71c](https://codeberg.org/ThetaDev/artifactview/commit/e87b71cc0df91f0ee71a88a3bd0127d4f8c74eea)) +- *(deps)* Update rust crate env_logger to v0.11.6 (#148) - ([8e295a9](https://codeberg.org/ThetaDev/artifactview/commit/8e295a96decd532fdd854ee8950a68d496c1617d)) +- *(deps)* Update rust crate serde_json to v1.0.134 (#149) - ([c90116c](https://codeberg.org/ThetaDev/artifactview/commit/c90116c9bac801e57062a462bd7a08a5fd6389b2)) +- *(deps)* Update rust crate thiserror to v2.0.9 (#150) - ([2a2a8e0](https://codeberg.org/ThetaDev/artifactview/commit/2a2a8e0b310b4ddcf15f20b1189bf768499f6b75)) +- *(deps)* Update rust crate serde to v1.0.217 (#151) - ([abe8f92](https://codeberg.org/ThetaDev/artifactview/commit/abe8f92ab8cdee926ca1ad4faf280427a9439e0e)) +- *(deps)* Update rust crate quick-xml to v0.37.2 (#152) - ([d778789](https://codeberg.org/ThetaDev/artifactview/commit/d7787899593d60243c1efa6dd036fc44e9c51868)) +- *(deps)* Update rust crate axum-extra to 0.10.0 (#154) - ([55621fb](https://codeberg.org/ThetaDev/artifactview/commit/55621fbbea51aed2234e43fa4b5ed61524ca1805)) +- *(deps)* Update rust crate rstest to 0.24.0 (#155) - ([b3cc2b5](https://codeberg.org/ThetaDev/artifactview/commit/b3cc2b53dc032d2821748895c983dde49c56eb65)) +- *(deps)* Update rust crate reqwest to v0.12.12 (#156) - ([cd73f48](https://codeberg.org/ThetaDev/artifactview/commit/cd73f4828f67d31dee356cb98a1ac45354e0e728)) +- *(deps)* Update rust crate comrak to 0.33.0 (#158) - ([1ce03ca](https://codeberg.org/ThetaDev/artifactview/commit/1ce03ca19f4032d5e88c8efbe83b98c45d98247d)) +- *(deps)* Update rust crate insta to v1.42.0 (#159) - ([5b138fa](https://codeberg.org/ThetaDev/artifactview/commit/5b138fae112d47a08b0940d61bd403047a8567fa)) +- *(deps)* Update rust crate pin-project to v1.1.8 (#161) - ([13ee5cc](https://codeberg.org/ThetaDev/artifactview/commit/13ee5cc2456180bfdc47a2d9e3a0dfe9a2dacd7d)) +- *(deps)* Update rust crate serde_json to v1.0.135 (#162) - ([8417ea3](https://codeberg.org/ThetaDev/artifactview/commit/8417ea34a0808d2ba25194021bbe38538ce52ddf)) +- *(deps)* Update rust crate thiserror to v2.0.10 (#163) - ([c2ee6cd](https://codeberg.org/ThetaDev/artifactview/commit/c2ee6cd84933e7cb2167b4cd34ec47f926105e59)) +- *(deps)* Update rust crate tokio to v1.43.0 (#164) - ([db790e0](https://codeberg.org/ThetaDev/artifactview/commit/db790e0811e9a67a63dc4708ca928efbcff1eb49)) +- *(deps)* Update rust crate thiserror to v2.0.11 (#165) - ([db0a4fd](https://codeberg.org/ThetaDev/artifactview/commit/db0a4fd5d48842ff48a375d241139ad91796422b)) +- *(deps)* Update rust crate futures-lite to v2.6.0 (#166) - ([c9a6d67](https://codeberg.org/ThetaDev/artifactview/commit/c9a6d6786f763825874520776653b311bc7bd5d8)) +- *(deps)* Update rust crate serde_json to v1.0.137 (#167) - ([558ce7d](https://codeberg.org/ThetaDev/artifactview/commit/558ce7daa8fbedac507f2d6e01961896eb8daac0)) +- *(deps)* Update rust crate comrak to 0.34.0 (#168) - ([a88977a](https://codeberg.org/ThetaDev/artifactview/commit/a88977af942d275422670984ddbefa53f8d61e13)) +- *(deps)* Update rust crate comrak to 0.35.0 (#169) - ([4042ded](https://codeberg.org/ThetaDev/artifactview/commit/4042ded5aee3763c293ffd264cb2428eb4266845)) +- *(deps)* Update rust crate insta to v1.42.1 (#170) - ([0c49fe7](https://codeberg.org/ThetaDev/artifactview/commit/0c49fe751a833ca684bfc39d19e2f1eb7ea269f5)) +- *(deps)* Update rust crate axum to 0.8.0 (#157) - ([2c2893d](https://codeberg.org/ThetaDev/artifactview/commit/2c2893da218737572e3943e2b72f7cec4ca6798f)) + + +## [v0.4.7](https://codeberg.org/ThetaDev/artifactview/compare/v0.4.6..v0.4.7) - 2024-10-12 + +### ๐Ÿ› Bug Fixes + +- *(deps)* Update rust crate serde_json to v1.0.121 (#29) - ([df805bf](https://codeberg.org/ThetaDev/artifactview/commit/df805bfe8394dd148ded1d4d3af901eb97593885)) +- *(deps)* Update rust crate serde_json to v1.0.122 (#32) - ([db67487](https://codeberg.org/ThetaDev/artifactview/commit/db67487abdfc15fe55854fcb233e0bb876b603b3)) +- *(deps)* Update rust crate regex to v1.10.6 (#33) - ([7c2a976](https://codeberg.org/ThetaDev/artifactview/commit/7c2a97666d98d4959affbb8ece93c4ba162a760d)) +- *(deps)* Update rust crate serde-env to 0.2.0 (#37) - ([6b7d107](https://codeberg.org/ThetaDev/artifactview/commit/6b7d107387ff3e52e62e4ed19c64e63f8048c478)) +- *(deps)* Update rust crate serde to v1.0.205 (#38) - ([f9698b5](https://codeberg.org/ThetaDev/artifactview/commit/f9698b5a7f9c7f3748d4d7aa38f7dc4c0f5f2029)) +- *(deps)* Update rust crate serde to v1.0.206 (#39) - ([ed86f30](https://codeberg.org/ThetaDev/artifactview/commit/ed86f30cf4a736eeb4a3d471e81b8e7f7344b53b)) +- *(deps)* Update rust crate serde_json to v1.0.124 (#40) - ([cc6a495](https://codeberg.org/ThetaDev/artifactview/commit/cc6a4959983205ae2f40d81c9a40c8514165c0bb)) +- *(deps)* Update rust crate serde to v1.0.207 (#41) - ([0c2b39a](https://codeberg.org/ThetaDev/artifactview/commit/0c2b39a68a1adb567a1582f0c1b9e024fda9ed53)) +- *(deps)* Update rust crate serde to v1.0.208 (#43) - ([8073e90](https://codeberg.org/ThetaDev/artifactview/commit/8073e90f685d80565db81e23769841c16c2af261)) +- *(deps)* Update rust crate serde_json to v1.0.125 (#44) - ([4b3639a](https://codeberg.org/ThetaDev/artifactview/commit/4b3639aea7beed4ebc421fdfe26823be164d5c1c)) +- *(deps)* Update rust crate comrak to 0.27.0 (#46) - ([3cef317](https://codeberg.org/ThetaDev/artifactview/commit/3cef3175767170824f604fcccdc912bf09745bf9)) +- *(deps)* Update rust crate comrak to 0.28.0 (#47) - ([a88a3c6](https://codeberg.org/ThetaDev/artifactview/commit/a88a3c6103e776a4d10b3f7e6e9a37a2c672cfba)) +- *(deps)* Update rust crate quick_cache to v0.6.6 (#50) - ([73959c0](https://codeberg.org/ThetaDev/artifactview/commit/73959c00f2c54b682c3db8640ca12319ce4ee37d)) +- *(deps)* Update rust crate reqwest to v0.12.7 (#51) - ([22d5626](https://codeberg.org/ThetaDev/artifactview/commit/22d5626bf025783a127cd99faa0052778e0253b1)) +- *(deps)* Update rust crate serde to v1.0.210 (#52) - ([f8c9d6f](https://codeberg.org/ThetaDev/artifactview/commit/f8c9d6f7cb475f4642f5e4f11108c4d053cc8c7e)) +- *(deps)* Update rust crate serde_json to v1.0.128 (#57) - ([a48e23b](https://codeberg.org/ThetaDev/artifactview/commit/a48e23beceefc1b4c51910dc7114ab62abfd189c)) +- *(deps)* Update rust crate quick_cache to v0.6.9 (#59) - ([4eb2b22](https://codeberg.org/ThetaDev/artifactview/commit/4eb2b22a8f1c2b1b28f72303d364708d04790eca)) +- *(deps)* Update rust crate tower-http to 0.6.0 (#61) - ([1d03f5b](https://codeberg.org/ThetaDev/artifactview/commit/1d03f5b4b09596a68893126d8b177226b62fb38a)) +- *(deps)* Update rust crate axum to v0.7.6 (#62) - ([61f65e5](https://codeberg.org/ThetaDev/artifactview/commit/61f65e54db431b3e94d21188920ae88233c44d3b)) +- *(deps)* Update rust crate quick-xml to v0.36.2 (#64) - ([de4459f](https://codeberg.org/ThetaDev/artifactview/commit/de4459f646444a949c390394524f284a1944a0da)) +- *(deps)* Update rust crate axum-extra to v0.9.4 (#63) - ([6619ef6](https://codeberg.org/ThetaDev/artifactview/commit/6619ef60e44832dd8839bcaf82d43707965b772a)) +- *(deps)* Update rust crate thiserror to v1.0.64 (#66) - ([923f97f](https://codeberg.org/ThetaDev/artifactview/commit/923f97f8e9d0855ff97685496221a180018ae686)) +- *(deps)* Update rust crate tower-http to v0.6.1 (#68) - ([ca0734d](https://codeberg.org/ThetaDev/artifactview/commit/ca0734d47072d555ba07b2f512975b9379305a58)) +- *(deps)* Update rust crate secrecy to 0.10.0 (#60) - ([72d0cde](https://codeberg.org/ThetaDev/artifactview/commit/72d0cde37075220cc6a938840ad30781ecdcbaa7)) + +### โš™๏ธ Miscellaneous Tasks + +- *(deps)* Update rust crate zip to v2.1.6 (#31) - ([7e0aaa8](https://codeberg.org/ThetaDev/artifactview/commit/7e0aaa8362005b56526ed5a6114f473893a5cf46)) +- *(deps)* Update rust crate flate2 to v1.0.31 (#34) - ([01e6a9c](https://codeberg.org/ThetaDev/artifactview/commit/01e6a9c8ad1f7c7dd5307a02a4a2b3c381aeacd6)) +- *(deps)* Update rust crate rstest to 0.22.0 (#35) - ([b9d0a29](https://codeberg.org/ThetaDev/artifactview/commit/b9d0a29741138a4dd7b758417b003c9bddc35f3e)) +- *(deps)* Update rust crate scraper to 0.20.0 (#36) - ([ca174a3](https://codeberg.org/ThetaDev/artifactview/commit/ca174a3aa21d0466930c2aa3291c2ecbed2ac31f)) +- *(deps)* Update rust crate axum-test to v15.3.1 (#42) - ([24171c9](https://codeberg.org/ThetaDev/artifactview/commit/24171c9800aa52270c203e5a5fc40f520b6ac74c)) +- *(deps)* Update rust crate tokio to v1.39.3 (#45) - ([63978d7](https://codeberg.org/ThetaDev/artifactview/commit/63978d79f9c3e276e59d7f3a558cec5f8f88e17e)) +- *(deps)* Update rust crate flate2 to v1.0.33 (#48) - ([980e596](https://codeberg.org/ThetaDev/artifactview/commit/980e5968eaa909c9d7a72d78156d3465c4599abe)) +- *(deps)* Update rust crate tokio-util to v0.7.12 (#49) - ([78179fd](https://codeberg.org/ThetaDev/artifactview/commit/78179fd73791c47cd3a60a37e704472109b50c15)) +- *(deps)* Update rust crate insta to v1.40.0 (#54) - ([03597d1](https://codeberg.org/ThetaDev/artifactview/commit/03597d10e58eca8a56e4708971cac1750e7707f4)) +- *(deps)* Update rust crate tokio to v1.40.0 (#55) - ([97b9610](https://codeberg.org/ThetaDev/artifactview/commit/97b9610f308c8ad0a083f6522c8b661c4b2c1e4f)) +- *(deps)* Update rust crate zip to v2.2.0 (#56) - ([3f719ac](https://codeberg.org/ThetaDev/artifactview/commit/3f719ac939612722b82b8bad2744b4570ff40df7)) +- *(deps)* Update rust crate once_cell to v1.20.0 (#58) - ([f8a95c8](https://codeberg.org/ThetaDev/artifactview/commit/f8a95c82e4a627d10906330327e94cd829d6f4c8)) +- *(deps)* Update rust crate axum-test to v15.7.1 (#53) - ([7a92941](https://codeberg.org/ThetaDev/artifactview/commit/7a92941452b0e2f59e0ceda49a378f0ef43784cb)) +- *(deps)* Update rust crate axum-test to v15.7.3 (#65) - ([82ca6dd](https://codeberg.org/ThetaDev/artifactview/commit/82ca6dd6bff9d8cb87a349cc8edb46f42a9721fc)) +- *(deps)* Update rust crate axum-test to v16 (#69) - ([dbcee49](https://codeberg.org/ThetaDev/artifactview/commit/dbcee4945c0be57534bdd2d7d98d85767e0cf92c)) +- *(deps)* Update rust crate flate2 to v1.0.34 (#70) - ([6f3544e](https://codeberg.org/ThetaDev/artifactview/commit/6f3544e3d8051697053cad5a751df6a1b4bc658b)) +- *(deps)* Update rust crate axum to v0.7.7 (#71) - ([01c494c](https://codeberg.org/ThetaDev/artifactview/commit/01c494c2773dc67f922beed7e84aa2ff59fb575f)) +- *(deps)* Update rust crate axum-test to v16.0.1 (#72) - ([2367512](https://codeberg.org/ThetaDev/artifactview/commit/23675124bdb15e01f037a6380f97f4d2921a34eb)) +- *(deps)* Update rust crate once_cell to v1.20.1 (#73) - ([484f113](https://codeberg.org/ThetaDev/artifactview/commit/484f1136469f5459b1d464944621e9db450d2f6a)) +- *(deps)* Update rust crate axum-test to v16.1.0 (#74) - ([2ed0cdc](https://codeberg.org/ThetaDev/artifactview/commit/2ed0cdc4a372c0a40de2ddc70a14b658a3619eec)) +- *(deps)* Update rust crate regex to v1.11.0 (#75) - ([e436f77](https://codeberg.org/ThetaDev/artifactview/commit/e436f77c3e4969d83c42d45201c15f0375d90ad0)) +- *(deps)* Update rust crate rstest to 0.23.0 (#76) - ([7c684eb](https://codeberg.org/ThetaDev/artifactview/commit/7c684eb5657eda84668bf3a1aadf38f1e4ac51db)) +- *(deps)* Update rust crate reqwest to v0.12.8 (#77) - ([a3f028f](https://codeberg.org/ThetaDev/artifactview/commit/a3f028f2ad11d3e599bf20d1f92679bf8b8dafc4)) +- *(deps)* Update rust crate async-compression to v0.4.13 (#78) - ([7cefbd4](https://codeberg.org/ThetaDev/artifactview/commit/7cefbd4a67e5636f046177f35fce14fff6300cb4)) +- *(deps)* Update rust crate once_cell to v1.20.2 (#79) - ([8309901](https://codeberg.org/ThetaDev/artifactview/commit/8309901a8c658cdf948889df57b731147d77c949)) +- *(deps)* Update rust crate pin-project to v1.1.6 (#80) - ([6ca7088](https://codeberg.org/ThetaDev/artifactview/commit/6ca7088b9c7ecd001df52f9cb35e86301c231bbb)) +- *(deps)* Update rust crate axum-test to v16.2.0 (#81) - ([e63baec](https://codeberg.org/ThetaDev/artifactview/commit/e63baec2490e069953f63d158f9af212f154055b)) +- *(deps)* Update rust crate secrecy to v0.10.3 (#82) - ([a2dc40f](https://codeberg.org/ThetaDev/artifactview/commit/a2dc40f4443cb3c3d1c7cf2b9e7178777c4b73e7)) +- *(deps)* Update rust crate async-compression to v0.4.14 (#83) - ([51f098f](https://codeberg.org/ThetaDev/artifactview/commit/51f098f4ada63ddc550774a60a81eb3d55697b44)) +- *(deps)* Update rust crate comrak to 0.29.0 (#84) - ([5da4074](https://codeberg.org/ThetaDev/artifactview/commit/5da4074bb9e87c9d17ed70669b6e47a3d57b1e8f)) + + +## [v0.4.6](https://codeberg.org/ThetaDev/artifactview/compare/v0.4.5..v0.4.6) - 2024-07-28 + +### ๐Ÿ› Bug Fixes + +- *(deps)* Update rust crate serde_json to v1.0.120 (#14) - ([06f9c27](https://codeberg.org/ThetaDev/artifactview/commit/06f9c278a857a272580ee1c4f8e58078556accda)) +- *(deps)* Update rust crate quick_cache to 0.6.0 (#15) - ([2e06266](https://codeberg.org/ThetaDev/artifactview/commit/2e0626667e5fc344df9870cd8c924b9dd60886bb)) +- *(deps)* Update rust crate serde to v1.0.204 (#16) - ([1321386](https://codeberg.org/ThetaDev/artifactview/commit/13213861ba0ea30504caa50da2a99af567876e5c)) +- *(deps)* Update rust crate quick-xml to 0.36.0 (#19) - ([5f94794](https://codeberg.org/ThetaDev/artifactview/commit/5f94794d24f300762da2ab162f4336508b516eda)) +- *(deps)* Update rust crate async-compression to v0.4.12 (#22) - ([88c635c](https://codeberg.org/ThetaDev/artifactview/commit/88c635cbad535eb902ea54a314e8511965a792b4)) +- *(deps)* Update rust crate quick-xml to v0.36.1 (#23) - ([c5c9f85](https://codeberg.org/ThetaDev/artifactview/commit/c5c9f85e4baec58dcc2001ac3a7f005c7f501557)) +- *(deps)* Update rust crate quick_cache to v0.6.2 (#24) - ([a5d4973](https://codeberg.org/ThetaDev/artifactview/commit/a5d49733fd84ba37e8c258f191bf79f4affb86f9)) +- *(deps)* Update rust crate thiserror to v1.0.63 (#25) - ([b67b173](https://codeberg.org/ThetaDev/artifactview/commit/b67b1730b1e9c06b4ff99774c37c71391f48f93a)) +- *(deps)* Update rust crate comrak to 0.26.0 (#27) - ([642930d](https://codeberg.org/ThetaDev/artifactview/commit/642930d397efa106267bd2aff8c413ab4173a5c6)) + +### โš™๏ธ Miscellaneous Tasks + +- *(deps)* Update rust crate axum-test to v15.3.0 (#17) - ([a88f1ba](https://codeberg.org/ThetaDev/artifactview/commit/a88f1ba91c054fdb267f0edef10aacf14a909694)) +- *(deps)* Lock file maintenance (#18) - ([f41a922](https://codeberg.org/ThetaDev/artifactview/commit/f41a92243c91086d5d774410b1452303fad64ccf)) +- *(deps)* Update rust crate env_logger to v0.11.5 (#20) - ([c9db056](https://codeberg.org/ThetaDev/artifactview/commit/c9db0567916e500017034d6a99eb48a25a1671e0)) +- *(deps)* Update rust crate scraper to v0.19.1 (#21) - ([1a5c056](https://codeberg.org/ThetaDev/artifactview/commit/1a5c056204b488e36ef95145b05a674c661a2154)) +- *(deps)* Update rust crate tokio to v1.39.1 (#26) - ([057a365](https://codeberg.org/ThetaDev/artifactview/commit/057a365a0ecafe00fa84e53c736272f5db26f0f1)) +- *(deps)* Update rust crate tokio to v1.39.2 (#28) - ([3ae7f88](https://codeberg.org/ThetaDev/artifactview/commit/3ae7f8813e19818098a6b67d0c6770db8a79defe)) +- *(deps)* Update rust crate zip to v2 (#9) - ([a7160fa](https://codeberg.org/ThetaDev/artifactview/commit/a7160fadde6c95c6c03c90c5d2301738c8e559c2)) + + +## [v0.4.5](https://codeberg.org/ThetaDev/artifactview/compare/v0.4.4..v0.4.5) - 2024-07-01 + +### ๐Ÿš€ Features + +- Update PR comment format - ([197eeea](https://codeberg.org/ThetaDev/artifactview/commit/197eeea75baa8ba44d27ec46c5f552028052869b)) + +### ๐Ÿ› Bug Fixes + +- *(deps)* Update rust crate serde_json to v1.0.118 (#5) - ([fc3b5a1](https://codeberg.org/ThetaDev/artifactview/commit/fc3b5a1530985012ff8364a8fa676626e7544eaf)) +- *(deps)* Update rust crate quick-xml to 0.34.0 (#8) - ([595a9d0](https://codeberg.org/ThetaDev/artifactview/commit/595a9d0f4115faf5056653406b6d05bf671dc2b3)) +- *(deps)* Update rust crate mime_guess to v2.0.5 (#10) - ([94191f8](https://codeberg.org/ThetaDev/artifactview/commit/94191f878d774bef165cca850fcdf00fde16d662)) +- *(deps)* Update rust crate quick-xml to 0.35.0 (#11) - ([7ebe881](https://codeberg.org/ThetaDev/artifactview/commit/7ebe8815462f3e704a79af038b9b1850ed4677ed)) +- *(deps)* Update rust crate serde_json to v1.0.119 (#12) - ([2525022](https://codeberg.org/ThetaDev/artifactview/commit/2525022df76b3c16951983c14a55fa9617114a8e)) +- Swap crc and size column (#3) - ([7d2c686](https://codeberg.org/ThetaDev/artifactview/commit/7d2c68630ec6e75061c050a4c8b035edb472d150)) + +### ๐Ÿ“š Documentation + +- Make example CI step compatible with GitHub+Forgejo - ([39f0019](https://codeberg.org/ThetaDev/artifactview/commit/39f0019455cc23f1b8c39b77d2aaa5af278731a9)) +- Update README - ([40ae3a7](https://codeberg.org/ThetaDev/artifactview/commit/40ae3a7f557c63a0bb2abcd595218c8ec1095fe7)) + +### โš™๏ธ Miscellaneous Tasks + +- *(deps)* Update rust crate proptest to v1.5.0 (#6) - ([797fc0c](https://codeberg.org/ThetaDev/artifactview/commit/797fc0c04c2a51811a24cfc431496e6e5dbf0bea)) +- *(deps)* Update rust crate rstest to 0.21.0 (#7) - ([cec3aa3](https://codeberg.org/ThetaDev/artifactview/commit/cec3aa3fc02e6a871d9c221f61c3f2d8828f9f63)) +- *(deps)* Lock file maintenance (#13) - ([9767167](https://codeberg.org/ThetaDev/artifactview/commit/9767167661e22775614cea7b888a19ee16c17d65)) + + +## [v0.4.4](https://codeberg.org/ThetaDev/artifactview/compare/v0.4.3..v0.4.4) - 2024-06-22 + +### ๐Ÿ› Bug Fixes + +- Use forge aliases for PR comment links - ([3690b02](https://codeberg.org/ThetaDev/artifactview/commit/3690b0244cf47d0d73511f5f69f5d12abe0f1837)) + + +## [v0.4.3](https://codeberg.org/ThetaDev/artifactview/compare/v0.4.2..v0.4.3) - 2024-06-22 + +### ๐Ÿ› Bug Fixes + +- 404 error on GitHub comment creation - ([d8c3ab4](https://codeberg.org/ThetaDev/artifactview/commit/d8c3ab4f36727f118b31683db87d287d9945ee14)) + + +## [v0.4.2](https://codeberg.org/ThetaDev/artifactview/compare/v0.4.1..v0.4.2) - 2024-06-22 + +### ๐Ÿ› Bug Fixes + +- PR comment emoji prefix detection - ([1e36edf](https://codeberg.org/ThetaDev/artifactview/commit/1e36edf49978e8ba24a85d4663ff3ebaf9642a29)) + + +## [v0.4.1](https://codeberg.org/ThetaDev/artifactview/compare/v0.4.0..v0.4.1) - 2024-06-22 + +### ๐Ÿš€ Features + +- Improved PR comment format, add `artifact_paths` parameter - ([6ae7520](https://codeberg.org/ThetaDev/artifactview/commit/6ae7520111469b04764115ccb5eeb3b756a4572e)) + +### ๐Ÿ› Bug Fixes + +- Return correct status code on API errors - ([f94cdcb](https://codeberg.org/ThetaDev/artifactview/commit/f94cdcbd1fec5474145e845a8673470a174bf6f5)) + + +## [v0.4.0](https://codeberg.org/ThetaDev/artifactview/compare/v0.3.1..v0.4.0) - 2024-06-22 + +### ๐Ÿš€ Features + +- Create PR comments - ([d0cdbf5](https://codeberg.org/ThetaDev/artifactview/commit/d0cdbf55a3a278ce21cab11885170e9f4a6d4094)) +- Add url parameter to /artifacts API - ([23b8101](https://codeberg.org/ThetaDev/artifactview/commit/23b81014266728e3db1cb200a5cf46a212baed72)) + + +## [v0.3.1](https://codeberg.org/ThetaDev/artifactview/compare/v0.3.0..v0.3.1) - 2024-06-19 + +### ๐Ÿ› Bug Fixes + +- Add instance domain to userscript description - ([311d3ae](https://codeberg.org/ThetaDev/artifactview/commit/311d3aedb968d1103569300d9c7f117edd96ae45)) +- Do not show fallback pages for favicon - ([f1f0af6](https://codeberg.org/ThetaDev/artifactview/commit/f1f0af62647074e2cce6eb52fb188e014b248ace)) + +### โš™๏ธ Miscellaneous Tasks + +- Update quick-xml to v0.32.0 - ([604d650](https://codeberg.org/ThetaDev/artifactview/commit/604d650d49180b80784948e895508aa7e0048211)) +- Update dependencies - ([68ad001](https://codeberg.org/ThetaDev/artifactview/commit/68ad00170fc8b0a881bbdeb36e50374e335b3f7f)) + + +## [v0.3.0](https://codeberg.org/ThetaDev/artifactview/compare/v0.2.0..v0.3.0) - 2024-06-18 + +### ๐Ÿš€ Features + +- Add userscript - ([299f54f](https://codeberg.org/ThetaDev/artifactview/commit/299f54fd58b567884f76e1c36c7c1648d43fbc75)) + +### ๐Ÿ› Bug Fixes + +- Make icon visible on light background - ([7021980](https://codeberg.org/ThetaDev/artifactview/commit/70219805d0029bb5b1e0bd08f77f444949d5b9f6)) +- Redirect user to directory path when requesting index page - ([a8e173c](https://codeberg.org/ThetaDev/artifactview/commit/a8e173c8a921ab29f4e70b7abd5167fb87c6f609)) + + +## [v0.2.0](https://codeberg.org/ThetaDev/artifactview/compare/v0.1.0..v0.2.0) - 2024-06-14 + +### ๐Ÿš€ Features + +- Add port config option - ([eca80aa](https://codeberg.org/ThetaDev/artifactview/commit/eca80aaa8e7ff9d3b36991f9d80d8846441a5536)) +- Add viewer - ([47f3ea1](https://codeberg.org/ThetaDev/artifactview/commit/47f3ea126784c3add59ef5feea94f11f8d4413b2)) +- Add viewer selection - ([4ebeb4b](https://codeberg.org/ThetaDev/artifactview/commit/4ebeb4b873d3b935a2eaa4008c0fc30dfd87c0f7)) +- Add markdown viewer - ([608a9f6](https://codeberg.org/ThetaDev/artifactview/commit/608a9f68f4e756bf65c0084fbf654c8e40d4762c)) +- Add junit-parser crate - ([06c09b2](https://codeberg.org/ThetaDev/artifactview/commit/06c09b2831d4ba9fe4095776b3c591cd64c095a9)) +- Add junit-parser - ([ec65b6d](https://codeberg.org/ThetaDev/artifactview/commit/ec65b6d03f13d88dbf874b72b086eec369d94475)) +- Add JUnit viewer template - ([806a2dd](https://codeberg.org/ThetaDev/artifactview/commit/806a2dda9a5e7ce33e0496b31934a2cf11eeb2ae)) +- Add compressed stylesheets - ([134bdaa](https://codeberg.org/ThetaDev/artifactview/commit/134bdaa34bd750a64a8b7b33d5d4383de6f51188)) + +### ๐Ÿ› Bug Fixes + +- Make url input field required - ([5a54f0a](https://codeberg.org/ThetaDev/artifactview/commit/5a54f0a7a6828b29ea67269c1474e9ebbafdca2a)) +- Fix markdown styling - ([93758e3](https://codeberg.org/ThetaDev/artifactview/commit/93758e3ab2f86e2edf8d7f04b8786e74e2ec67d6)) +- Handling zip files containing dir entries - ([8405af8](https://codeberg.org/ThetaDev/artifactview/commit/8405af8fb339418dc0b17768e8ef3bc1f22d46f5)) +- Increment stylesheet path - ([d9f9d6e](https://codeberg.org/ThetaDev/artifactview/commit/d9f9d6edcaef4086ce51596305909a1d63e132f7)) +- Improve path header - ([194499b](https://codeberg.org/ThetaDev/artifactview/commit/194499b2768325e27dc77e0f3a71c410667a8851)) +- Clippy error, async_zip tests - ([5f22997](https://codeberg.org/ThetaDev/artifactview/commit/5f229977d84f50911a42fd66f07f567727098968)) +- Dont cache empty artifact lists - ([09a5e56](https://codeberg.org/ThetaDev/artifactview/commit/09a5e5639e0ed24f437c0ed81e464530a6b7ee99)) + +### ๐Ÿšœ Refactor + +- [**breaking**] Don't use URL queries for artifact selection - ([72c0e3a](https://codeberg.org/ThetaDev/artifactview/commit/72c0e3af69e101c3690696a065b573624e19626d)) + +### ๐Ÿงช Testing + +- Add integration tests - ([4aaff46](https://codeberg.org/ThetaDev/artifactview/commit/4aaff462bb602180832cae3fe902161d2f8043a9)) + +### โš™๏ธ Miscellaneous Tasks + +- Update repo URL to Codeberg - ([6a0171c](https://codeberg.org/ThetaDev/artifactview/commit/6a0171c62a7cf1023c8efe203e202a6bccdc04e5)) + + ## [v0.1.0](https://codeberg.org/ThetaDev/artifactview/commits/tag/v0.1.0) - 2024-05-31 Initial release diff --git a/Cargo.lock b/Cargo.lock index 323af5f..b3f604c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,21 +1,21 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aes" @@ -30,12 +30,12 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom", + "getrandom 0.3.3", "once_cell", "version_check", "zerocopy", @@ -50,6 +50,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -71,15 +77,15 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccaf7e9dfbb6ab22c82e473cd1a8a7bd313c19a5b7e40970f3d89ef5a5c9e81e" dependencies = [ - "unicode-width", + "unicode-width 0.1.14", "yansi-term", ] [[package]] name = "anstream" -version = "0.6.14" +version = "0.6.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" dependencies = [ "anstyle", "anstyle-parse", @@ -92,43 +98,53 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" [[package]] name = "anstyle-parse" -version = "0.2.4" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.3" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a64c907d4e79225ac72e2a354c9ce84d50ebb4586dee56c82b3ee73004f537f5" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.3" +version = "3.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" dependencies = [ "anstyle", - "windows-sys 0.52.0", + "once_cell_polyfill", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] [[package]] name = "array-init" @@ -141,20 +157,24 @@ dependencies = [ [[package]] name = "artifactview" -version = "0.1.0" +version = "0.4.9" dependencies = [ "async_zip", "axum", "axum-extra", + "axum-test", + "comrak", "dotenvy", "envy", "flate2", "futures-lite", "governor", "headers", - "hex", "http", + "httpdate", "humansize", + "insta", + "junit-parser", "mime", "mime_guess", "once_cell", @@ -163,15 +183,21 @@ dependencies = [ "pin-project", "proptest", "quick_cache", - "rand", + "rand 0.9.1", "regex", "reqwest", "rstest", + "scraper", + "secrecy", "serde", "serde-env", "serde-hex", "serde_json", - "thiserror", + "serde_urlencoded", + "syntect", + "temp_testdir", + "thiserror 2.0.12", + "time", "tokio", "tokio-util", "tower-http", @@ -183,10 +209,20 @@ dependencies = [ ] [[package]] -name = "async-compression" -version = "0.4.11" +name = "assert-json-diff" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "async-compression" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06575e6a9673580f52661c92107baabffbf41e2141373441cbcdc47cb733003c" dependencies = [ "bzip2", "deflate64", @@ -196,19 +232,8 @@ dependencies = [ "memchr", "pin-project-lite", "xz2", - "zstd 0.13.1", - "zstd-safe 7.1.0", -] - -[[package]] -name = "async-trait" -version = "0.1.80" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.66", + "zstd", + "zstd-safe", ] [[package]] @@ -221,7 +246,7 @@ dependencies = [ "env_logger", "futures-lite", "pin-project", - "thiserror", + "thiserror 2.0.12", "tokio", "tokio-util", "zip", @@ -234,20 +259,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "autocfg" -version = "1.3.0" +name = "auto-future" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" -version = "0.7.5" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ - "async-trait", "axum-core", "bytes", + "form_urlencoded", "futures-util", "http", "http-body", @@ -265,7 +296,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.1", + "sync_wrapper", "tokio", "tower", "tower-layer", @@ -275,20 +306,19 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ - "async-trait", "bytes", - "futures-util", + "futures-core", "http", "http-body", "http-body-util", "mime", "pin-project-lite", "rustversion", - "sync_wrapper 0.1.2", + "sync_wrapper", "tower-layer", "tower-service", "tracing", @@ -296,9 +326,9 @@ dependencies = [ [[package]] name = "axum-extra" -version = "0.9.3" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733" +checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d" dependencies = [ "axum", "axum-core", @@ -310,34 +340,58 @@ dependencies = [ "http-body-util", "mime", "pin-project-lite", + "rustversion", "serde", "tower", "tower-layer", "tower-service", - "tracing", +] + +[[package]] +name = "axum-test" +version = "17.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb1dfb84bd48bad8e4aa1acb82ed24c2bb5e855b659959b4e03b4dca118fcac" +dependencies = [ + "anyhow", + "assert-json-diff", + "auto-future", + "axum", + "bytes", + "bytesize", + "cookie", + "http", + "http-body-util", + "hyper", + "hyper-util", + "mime", + "pretty_assertions", + "reserve-port", + "rust-multipart-rfc7578_2", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec 1.15.1", + "tokio", + "tower", + "url", ] [[package]] name = "backtrace" -version = "0.3.72" +version = "0.3.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" +checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] -[[package]] -name = "base64" -version = "0.21.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" - [[package]] name = "base64" version = "0.22.1" @@ -345,31 +399,40 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "base64ct" -version = "1.6.0" +name = "bincode" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.5.0" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "block-buffer" @@ -381,10 +444,16 @@ dependencies = [ ] [[package]] -name = "bumpalo" -version = "3.16.0" +name = "buf-min" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "22d5698cf6842742ed64805705798f8b351fff53fa546fd45c52184bee58dc90" + +[[package]] +name = "bumpalo" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byteorder" @@ -394,58 +463,79 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.6.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "bytesize" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c8f83209414aacf0eeae3cf730b18d6981697fba62f200fcfb92b9f082acba" [[package]] name = "bzip2" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" dependencies = [ "bzip2-sys", - "libc", ] [[package]] name = "bzip2-sys" -version = "0.1.11+1.0.8" +version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", - "libc", "pkg-config", ] [[package]] -name = "cc" -version = "1.0.98" +name = "caseless" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" +checksum = "8b6fd507454086c8edfd769ca6ada439193cdb209c7681712ef6275cccbfe5d8" +dependencies = [ + "unicode-normalization", +] + +[[package]] +name = "cc" +version = "1.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deec109607ca693028562ed836a5f1c4b8bd77755c4e132fc5ce11b0b6211ae7" dependencies = [ "jobserver", "libc", - "once_cell", + "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" dependencies = [ "android-tzdata", "iana-time-zone", + "js-sys", "num-traits", - "windows-targets 0.52.5", + "wasm-bindgen", + "windows-link", ] [[package]] @@ -460,15 +550,41 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.1" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "comrak" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32c3278f396e5707769a68bc0943e9b8f84a172836b173827810918279621747" +dependencies = [ + "caseless", + "entities", + "memchr", + "slug", + "typed-arena", + "unicode_categories", +] + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "windows-sys 0.59.0", +] [[package]] name = "constant_time_eq" -version = "0.1.5" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" [[package]] name = "convert_case" @@ -476,6 +592,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -487,34 +613,59 @@ dependencies = [ ] [[package]] -name = "core-foundation-sys" -version = "0.8.6" +name = "core-foundation" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] -name = "crc32fast" -version = "1.4.2" +name = "crc" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crypto-common" @@ -527,13 +678,37 @@ dependencies = [ ] [[package]] -name = "dashmap" -version = "5.5.3" +name = "cssparser" +version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +checksum = "b7c66d1cd8ed61bf80b38432613a7a2f09401ab8d0501110655f8b341484a3e3" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec 1.15.1", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.104", +] + +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" dependencies = [ "cfg-if", - "hashbrown", + "crossbeam-utils", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", @@ -541,32 +716,56 @@ dependencies = [ [[package]] name = "deflate64" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83ace6c86376be0b6cdcf3fb41882e81d94b31587573d1cfa9d01cd06bba210d" +checksum = "da692b8d1080ea3045efaab14434d40468c3d8657e42abddfffca87b428f4c1b" [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", + "serde", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version", - "syn 1.0.109", + "syn 2.0.104", ] +[[package]] +name = "deunicode" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abd57806937c9cc163efc8ea3910e00a62e2aeb0b8119f1793a978088f8f6b04" + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -578,6 +777,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -586,15 +796,42 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dtoa" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" +checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "ego-tree" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8" + +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "entities" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca" [[package]] name = "env_filter" -version = "0.1.0" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", @@ -602,14 +839,14 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.3" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] @@ -622,31 +859,31 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] name = "fastrand" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "flate2" -version = "1.0.30" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "4a3d7db9596fecd151c5f638c0ee5d5bd487b6e0ea232e5dc96d5250f6f94b1d" dependencies = [ "crc32fast", "miniz_oxide", @@ -658,6 +895,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + [[package]] name = "foreign-types" version = "0.3.2" @@ -683,58 +926,41 @@ dependencies = [ ] [[package]] -name = "futures" -version = "0.3.30" +name = "futf" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", + "mac", + "new_debug_unreachable", ] [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", - "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-lite" -version = "2.3.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" dependencies = [ "fastrand", "futures-core", @@ -745,26 +971,26 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.104", ] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-timer" @@ -774,11 +1000,10 @@ checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -790,6 +1015,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -801,53 +1035,81 @@ dependencies = [ ] [[package]] -name = "getrandom" -version = "0.2.15" +name = "getopts" +version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1" +dependencies = [ + "unicode-width 0.2.1", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", ] [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "governor" -version = "0.6.3" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" +checksum = "3cbe789d04bf14543f03c4b60cd494148aa79438c8440ae7d81a7778147745c3" dependencies = [ "cfg-if", "dashmap", - "futures", + "futures-sink", "futures-timer", - "no-std-compat", + "futures-util", + "getrandom 0.3.3", + "hashbrown 0.15.4", "nonzero_ext", "parking_lot", "portable-atomic", "quanta", - "rand", - "smallvec 1.13.2", + "rand 0.9.1", + "smallvec 1.15.1", "spinning_top", + "web-time", ] [[package]] name = "h2" -version = "0.4.5" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "17da50a276f1e01e0ba6c029e47b7100754904ee8a278f886546e98575380785" dependencies = [ "atomic-waker", "bytes", @@ -869,12 +1131,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] -name = "headers" -version = "0.4.0" +name = "hashbrown" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" dependencies = [ - "base64 0.21.7", + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "headers" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +dependencies = [ + "base64", "bytes", "headers-core", "http", @@ -892,18 +1165,6 @@ dependencies = [ "http", ] -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hex" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" - [[package]] name = "hmac" version = "0.12.1" @@ -914,10 +1175,22 @@ dependencies = [ ] [[package]] -name = "http" -version = "1.1.0" +name = "html5ever" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", @@ -926,9 +1199,9 @@ dependencies = [ [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", @@ -936,9 +1209,9 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", @@ -949,9 +1222,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -968,17 +1241,11 @@ dependencies = [ "libm", ] -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" -version = "1.3.1" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", @@ -990,26 +1257,27 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "smallvec 1.13.2", + "smallvec 1.15.1", "tokio", "want", ] [[package]] name = "hyper-rustls" -version = "0.26.0" +version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "futures-util", "http", "hyper", "hyper-util", "rustls", + "rustls-native-certs", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", + "webpki-roots", ] [[package]] @@ -1030,34 +1298,39 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.5" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +checksum = "7f66d5bd4c6f02bf0542fad85d626775bab9258cf795a4256dcaf3161114d1df" dependencies = [ + "base64", "bytes", "futures-channel", + "futures-core", "futures-util", "http", "http-body", "hyper", + "ipnet", + "libc", + "percent-encoding", "pin-project-lite", "socket2", "tokio", - "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", "windows-core", ] @@ -1072,99 +1345,274 @@ dependencies = [ ] [[package]] -name = "idna" -version = "0.5.0" +name = "icu_collections" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "200072f5d0e3614556f94a9930d5dc3e0662a652823904c3a75dc3b0af7fee47" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436880e8e18df4d7bbc06d58432329d6458cc84531f7ac5f024e93deadb37979" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec 1.15.1", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" + +[[package]] +name = "icu_properties" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "potential_utf", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" + +[[package]] +name = "icu_provider" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c80da27b5f4187909049ee2d72f276f0d9f99a42c306bd0131ecfe04d8e5af" +dependencies = [ + "displaydoc", + "icu_locale_core", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec 1.15.1", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", ] [[package]] name = "indexmap" -version = "2.2.6" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.4", ] [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] [[package]] -name = "ipnet" -version = "2.9.0" +name = "insta" +version = "1.43.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "154934ea70c58054b556dd430b99a98c2a7ff5309ac9891597e339b5c28f4371" +dependencies = [ + "console", + "once_cell", + "serde", + "similar", +] + +[[package]] +name = "io-uring" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" +dependencies = [ + "bitflags 2.9.1", + "cfg-if", + "libc", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc5ebe9c3a1a7a5127f920a418f7585e9e758e911d0466ed004f393b0e380b2" +dependencies = [ + "memchr", + "serde", +] [[package]] name = "is_terminal_polyfill" -version = "1.70.0" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "jiff" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", +] + +[[package]] +name = "jiff-static" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.3", "libc", ] [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] +[[package]] +name = "junit-parser" +version = "0.1.0" +dependencies = [ + "insta", + "once_cell", + "path_macro", + "quick-xml", + "serde", + "thiserror 2.0.12", + "time", +] + [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libm" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "litemap" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241eaef5fd12c88705a01fc1066c48c4b36e0dd4377dcdc7ec3942cea7a69956" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", @@ -1172,9 +1620,25 @@ dependencies = [ [[package]] name = "log" -version = "0.4.21" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lzma-rs" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e" +dependencies = [ + "byteorder", + "crc", +] [[package]] name = "lzma-sys" @@ -1188,10 +1652,41 @@ dependencies = [ ] [[package]] -name = "matchit" -version = "0.7.3" +name = "mac" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "maybe-uninit" @@ -1201,9 +1696,9 @@ checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "mime" @@ -1213,9 +1708,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -1223,29 +1718,29 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.7.3" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.11" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c" dependencies = [ "libc", - "wasi", - "windows-sys 0.48.0", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.59.0", ] [[package]] name = "native-tls" -version = "0.2.12" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" dependencies = [ "libc", "log", @@ -1253,16 +1748,16 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] [[package]] -name = "no-std-compat" -version = "0.4.1" +name = "new_debug_unreachable" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nodrop" @@ -1299,41 +1794,58 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", - "libm", -] - -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", ] [[package]] name = "object" -version = "0.35.0" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + +[[package]] +name = "onig" +version = "6.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "336b9c63443aceef14bea841b899035ae3abe89b7c486aaf4c5bd8aafedac3f0" +dependencies = [ + "bitflags 2.9.1", + "libc", + "once_cell", + "onig_sys", +] + +[[package]] +name = "onig_sys" +version = "69.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f86c6eef3d6df15f23bcfb6af487cbd2fed4e5581d58d5bf1f5f8b7f6727dc" +dependencies = [ + "cc", + "pkg-config", +] [[package]] name = "openssl" -version = "0.10.64" +version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" dependencies = [ - "bitflags", + "bitflags 2.9.1", "cfg-if", "foreign-types", "libc", @@ -1350,20 +1862,20 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.104", ] [[package]] name = "openssl-probe" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.102" +version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", @@ -1379,15 +1891,15 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", @@ -1395,26 +1907,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", - "smallvec 1.13.2", - "windows-targets 0.52.5", -] - -[[package]] -name = "password-hash" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" -dependencies = [ - "base64ct", - "rand_core", - "subtle", + "smallvec 1.15.1", + "windows-targets 0.52.6", ] [[package]] @@ -1425,14 +1926,12 @@ checksum = "a6e819bbd49d5939f682638fa54826bf1650abddcd65d000923de8ad63cc7d15" [[package]] name = "pbkdf2" -version = "0.11.0" +version = "0.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest", "hmac", - "password-hash", - "sha2", ] [[package]] @@ -1442,30 +1941,82 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] -name = "pin-project" -version = "1.1.5" +name = "phf" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.104", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -1475,15 +2026,33 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "portable-atomic" -version = "1.6.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" +checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7c30837279ca13e7c867e9e40053bc68740f988cb07f7ca6df43cc734b585" +dependencies = [ + "zerovec", +] [[package]] name = "powerfmt" @@ -1493,9 +2062,28 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] [[package]] name = "prettyplease" @@ -1507,36 +2095,27 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "proc-macro-crate" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" -dependencies = [ - "toml_edit", -] - [[package]] name = "proc-macro2" -version = "1.0.84" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.4.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" dependencies = [ "bit-set", "bit-vec", - "bitflags", + "bitflags 2.9.1", "lazy_static", "num-traits", - "rand", + "rand 0.9.1", "rand_chacha", "rand_xorshift", "regex-syntax", @@ -1547,15 +2126,15 @@ dependencies = [ [[package]] name = "quanta" -version = "0.12.3" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" dependencies = [ "crossbeam-utils", "libc", "once_cell", "raw-cpuid", - "wasi", + "wasi 0.11.1+wasi-snapshot-preview1", "web-sys", "winapi", ] @@ -1567,45 +2146,123 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] -name = "quick_cache" -version = "0.5.1" +name = "quick-xml" +version = "0.37.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "347e1a588d1de074eeb3c00eadff93db4db65aeb62aee852b1efd0949fe65b6c" +checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +dependencies = [ + "memchr", +] + +[[package]] +name = "quick_cache" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b450dad8382b1b95061d5ca1eb792081fb082adf48c678791fe917509596d5f" dependencies = [ "ahash", "equivalent", - "hashbrown", + "hashbrown 0.15.4", "parking_lot", ] [[package]] -name = "quote" -version = "1.0.36" +name = "quinn" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "626214629cda6781b6dc1d316ba307189c85ba657213ce642d9c77670f8202c8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.12", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49df843a9161c85bb8aae55f101bc0bac8bcafd637a620d9122fd7e0b2f7422e" +dependencies = [ + "bytes", + "getrandom 0.3.3", + "lru-slab", + "rand 0.9.1", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.12", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcebb1209ee276352ef14ff8732e24cc2b02bbac986cd74a4c81bcb2f9881970" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.59.0", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ - "libc", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" +dependencies = [ "rand_chacha", - "rand_core", + "rand_core 0.9.3", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.9.3", ] [[package]] @@ -1613,42 +2270,48 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ - "getrandom", + "getrandom 0.3.3", ] [[package]] name = "rand_xorshift" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" dependencies = [ - "rand_core", + "rand_core 0.9.3", ] [[package]] name = "raw-cpuid" -version = "11.0.2" +version = "11.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e29830cbb1290e404f24c73af91c5d8d631ce7e128691e9477556b540cd01ecd" +checksum = "c6df7ab838ed27997ba19a4664507e6f82b41fe6e20be42929332156e5e85146" dependencies = [ - "bitflags", + "bitflags 2.9.1", ] [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "de3a5d9f0aba1dbcec1cc47f0ff94a4b778fe55bca98a6dfa92e4e094e57b1c4" dependencies = [ - "bitflags", + "bitflags 2.9.1", ] [[package]] name = "regex" -version = "1.10.4" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1658,9 +2321,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -1669,9 +2332,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "relative-path" @@ -1681,11 +2344,11 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.4" +version = "0.12.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531" dependencies = [ - "base64 0.22.1", + "base64", "bytes", "futures-core", "futures-util", @@ -1696,26 +2359,25 @@ dependencies = [ "hyper-rustls", "hyper-tls", "hyper-util", - "ipnet", "js-sys", "log", - "mime", "native-tls", - "once_cell", "percent-encoding", "pin-project-lite", + "quinn", "rustls", "rustls-native-certs", - "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper 0.1.2", + "sync_wrapper", "tokio", "tokio-native-tls", "tokio-rustls", "tokio-util", + "tower", + "tower-http", "tower-service", "url", "wasm-bindgen", @@ -1723,29 +2385,36 @@ dependencies = [ "wasm-streams", "web-sys", "webpki-roots", - "winreg", +] + +[[package]] +name = "reserve-port" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21918d6644020c6f6ef1993242989bf6d4952d2e025617744f184c02df51c356" +dependencies = [ + "thiserror 2.0.12", ] [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.16", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rstest" -version = "0.20.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27059f51958c5f8496a6f79511e7c0ac396dd815dc8894e9b6e2efb5779cf6f0" +checksum = "6fc39292f8613e913f7df8fa892b8944ceb47c247b78e1b1ae2f09e019be789d" dependencies = [ "rstest_macros", "rustc_version", @@ -1753,57 +2422,77 @@ dependencies = [ [[package]] name = "rstest_macros" -version = "0.20.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6132d64df104c0b3ea7a6ad7766a43f587bd773a4a9cf4cd59296d426afaf3a" +checksum = "1f168d99749d307be9de54d23fd226628d99768225ef08f6ffb52e0182a27746" dependencies = [ "cfg-if", "glob", - "proc-macro-crate", "proc-macro2", "quote", "regex", "relative-path", "rustc_version", - "syn 2.0.66", + "syn 2.0.104", "unicode-ident", ] [[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "rust-multipart-rfc7578_2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "c839d037155ebc06a571e305af66ff9fd9063a6e662447051737e1ac75beea41" +dependencies = [ + "bytes", + "futures-core", + "futures-util", + "http", + "mime", + "rand 0.9.1", + "thiserror 2.0.12", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "989e6739f80c4ad5b13e0fd7fe89531180375b18520cc8c82080e4dc4035b84f" + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" [[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", ] [[package]] name = "rustix" -version = "0.38.34" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" dependencies = [ - "bitflags", + "bitflags 2.9.1", "errno", "libc", "linux-raw-sys", - "windows-sys 0.52.0", + "windows-sys 0.60.2", ] [[package]] name = "rustls" -version = "0.22.4" +version = "0.23.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "2491382039b29b9b11ff08b76ff6c97cf287671dbb74f0be44bda389fffe9bd1" dependencies = [ - "log", + "once_cell", "ring", "rustls-pki-types", "rustls-webpki", @@ -1813,38 +2502,31 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.7.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f1fb85efa936c42c6d5fc28d2629bb51e4b2f4b8a5211e297d599cc5a093792" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" dependencies = [ "openssl-probe", - "rustls-pemfile", "rustls-pki-types", "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" -dependencies = [ - "base64 0.22.1", - "rustls-pki-types", + "security-framework 3.2.0", ] [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "229a4a4c221013e7e1f1a043678c5cc39fe5171437c88fb47151a21e6f5b5c79" +dependencies = [ + "web-time", + "zeroize", +] [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.103.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "0a17884ae0c1b773f1ccd2bd4a8c72f16da897310a98b0e84bf349ad5ead92fc" dependencies = [ "ring", "rustls-pki-types", @@ -1853,9 +2535,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "rusty-fork" @@ -1871,17 +2553,26 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1891,13 +2582,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] -name = "security-framework" -version = "2.11.0" +name = "scraper" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +checksum = "527e65d9d888567588db4c12da1087598d0f6f8b346cc2c5abc91f05fc2dffe2" dependencies = [ - "bitflags", - "core-foundation", + "cssparser", + "ego-tree", + "getopts", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + +[[package]] +name = "secrecy" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a" +dependencies = [ + "serde", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags 2.9.1", + "core-foundation 0.10.1", "core-foundation-sys", "libc", "security-framework-sys", @@ -1905,37 +2634,55 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.11.0" +version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", ] [[package]] -name = "semver" -version = "1.0.23" +name = "selectors" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8" +dependencies = [ + "bitflags 2.9.1", + "cssparser", + "derive_more", + "fxhash", + "log", + "new_debug_unreachable", + "phf", + "phf_codegen", + "precomputed-hash", + "servo_arc", + "smallvec 1.15.1", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" -version = "1.0.203" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde-env" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c68119a0846249fd6f4b38561b4b4727dbc4fd9fea074f1253bca7d50440ce58" +checksum = "d13536c0c431652192b75c7d5afa83dedae98f91d7e687ff30a009e9d15284fb" dependencies = [ "anyhow", - "log", "serde", ] @@ -1952,31 +2699,32 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.104", ] [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "serde_path_to_error" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" dependencies = [ "itoa", "serde", @@ -1994,6 +2742,15 @@ dependencies = [ "serde", ] +[[package]] +name = "servo_arc" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "204ea332803bd95a0b60388590d59cf6468ec9becf626e2451f1d26a1d972de4" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "sha1" version = "0.10.6" @@ -2005,17 +2762,6 @@ dependencies = [ "digest", ] -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - [[package]] name = "sharded-slab" version = "0.1.7" @@ -2026,21 +2772,52 @@ dependencies = [ ] [[package]] -name = "signal-hook-registry" -version = "1.4.2" +name = "shlex" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" dependencies = [ "libc", ] [[package]] -name = "slab" -version = "0.4.9" +name = "simd-adler32" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "siphasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" + +[[package]] +name = "slab" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04dc19736151f35336d325007ac991178d504a119863a2fcb3758cdb5e52c50d" + +[[package]] +name = "slug" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" dependencies = [ - "autocfg", + "deunicode", + "wasm-bindgen", ] [[package]] @@ -2054,26 +2831,20 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.7" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "spinning_top" version = "0.3.0" @@ -2084,10 +2855,41 @@ dependencies = [ ] [[package]] -name = "subtle" -version = "2.5.0" +name = "stable_deref_trait" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -2102,9 +2904,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.66" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -2113,82 +2915,169 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.2" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] -name = "sync_wrapper" -version = "1.0.1" +name = "synstructure" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "syntect" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874dcfa363995604333cf947ae9f751ca3af4522c60886774c4963943b4746b1" +dependencies = [ + "bincode", + "bitflags 1.3.2", + "flate2", + "fnv", + "once_cell", + "onig", + "regex-syntax", + "serde", + "serde_derive", + "serde_json", + "thiserror 1.0.69", + "walkdir", +] + +[[package]] +name = "temp_testdir" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "921f1e9c427802414907a48b21a6504ff6b3a15a1a3cf37e699590949ad9befc" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1" dependencies = [ - "cfg-if", "fastrand", + "getrandom 0.3.3", + "once_cell", "rustix", - "windows-sys 0.52.0", + "windows-sys 0.59.0", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", ] [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl 2.0.12", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.104", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] name = "time" -version = "0.3.36" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde", "time-core", + "time-macros", ] [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +dependencies = [ + "displaydoc", + "zerovec", +] [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -2201,32 +3090,33 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", + "io-uring", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", + "slab", "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.104", ] [[package]] @@ -2241,20 +3131,19 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.25.0" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", - "rustls-pki-types", "tokio", ] [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" dependencies = [ "bytes", "futures-core", @@ -2273,33 +3162,16 @@ dependencies = [ "serde", ] -[[package]] -name = "toml_datetime" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" - -[[package]] -name = "toml_edit" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" -dependencies = [ - "indexmap", - "toml_datetime", - "winnow", -] - [[package]] name = "tower" -version = "0.4.13" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", - "pin-project", "pin-project-lite", + "sync_wrapper", "tokio", "tower-layer", "tower-service", @@ -2308,16 +3180,18 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.5.2" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" dependencies = [ - "bitflags", + "bitflags 2.9.1", "bytes", + "futures-util", "http", "http-body", - "http-body-util", + "iri-string", "pin-project-lite", + "tower", "tower-layer", "tower-service", "tracing", @@ -2325,21 +3199,21 @@ dependencies = [ [[package]] name = "tower-layer" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "log", "pin-project-lite", @@ -2349,20 +3223,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.27" +version = "0.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.104", ] [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" dependencies = [ "once_cell", "valuable", @@ -2381,13 +3255,13 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ "nu-ansi-term", "sharded-slab", - "smallvec 1.13.2", + "smallvec 1.15.1", "thread_local", "tracing-core", "tracing-log", @@ -2400,10 +3274,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "typenum" -version = "1.17.0" +name = "typed-arena" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + +[[package]] +name = "typenum" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unarray" @@ -2413,45 +3293,48 @@ checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - -[[package]] -name = "unicode-bidi" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "unicode-width" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c" [[package]] name = "unicode-xid" -version = "0.2.4" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] name = "untrusted" @@ -2461,9 +3344,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", @@ -2471,10 +3354,22 @@ dependencies = [ ] [[package]] -name = "utf8parse" -version = "0.2.1" +name = "utf-8" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "v_eval" @@ -2491,12 +3386,24 @@ name = "v_htmlescape" version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c" +dependencies = [ + "buf-min", +] + +[[package]] +name = "v_jsonescape" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be8219cc464ba10c48c3231a6871f11d26d831c5c45a47467eea387ea7bb10e8" +dependencies = [ + "buf-min", +] [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -2506,19 +3413,29 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.1" @@ -2530,52 +3447,63 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", + "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.104", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.42" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", + "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2583,28 +3511,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" dependencies = [ "futures-util", "js-sys", @@ -2615,9 +3546,19 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" dependencies = [ "js-sys", "wasm-bindgen", @@ -2625,9 +3566,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +checksum = "7e8983c3ab33d6fb807cfcdad2491c4ea8cbc8ed839181c7dfd9c67c83e261b2" dependencies = [ "rustls-pki-types", ] @@ -2648,6 +3589,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2656,20 +3606,61 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.52.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-targets 0.52.5", + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", ] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-implement" +version = "0.60.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ - "windows-targets 0.48.5", + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link", ] [[package]] @@ -2678,148 +3669,169 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.2", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 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", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "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]] name = "windows-targets" -version = "0.52.5" +version = "0.53.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef" dependencies = [ - "windows_aarch64_gnullvm 0.52.5", - "windows_aarch64_msvc 0.52.5", - "windows_i686_gnu 0.52.5", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.5", - "windows_x86_64_gnu 0.52.5", - "windows_x86_64_gnullvm 0.52.5", - "windows_x86_64_msvc 0.52.5", + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] -name = "winnow" -version = "0.5.40" +name = "wit-bindgen-rt" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "memchr", + "bitflags 2.9.1", ] [[package]] -name = "winreg" -version = "0.52.0" +name = "writeable" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] +checksum = "ea2f10b9bb0928dfb1b42b65e1f9e36f7f54dbdf08457afefb38afcdec4fa2bb" [[package]] name = "xz2" @@ -2830,6 +3842,12 @@ dependencies = [ "lzma-sys", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "yansi-term" version = "0.1.2" @@ -2845,6 +3863,7 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfce1df93f3b16e5272221a559e60bbbaaa71dbc042a43996d223e51a690aab2" dependencies = [ + "buf-min", "yarte_derive", "yarte_helpers", ] @@ -2871,6 +3890,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", + "v_jsonescape", "yarte_codegen", "yarte_helpers", "yarte_hir", @@ -2883,13 +3903,18 @@ version = "0.15.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0d1076f8cee9541ea5ffbecd9102f751252c91f085e7d30a18a3ce805ebd3ee" dependencies = [ + "buf-min", + "chrono", "dtoa", "itoa", "prettyplease", + "ryu", "serde", + "serde_json", "syn 1.0.109", "toml", "v_htmlescape", + "v_jsonescape", ] [[package]] @@ -2925,23 +3950,68 @@ dependencies = [ ] [[package]] -name = "zerocopy" -version = "0.7.34" +name = "yoke" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "5f41bb01b8226ef4bfd589436a297c53d118f65921786300e427be8d487695cc" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38da3c9736e16c5d3c8c597a9aaa5d1fa565d0532ae05e27c24aa62fb32c0ab6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.66", + "syn 2.0.104", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", + "synstructure", ] [[package]] @@ -2949,69 +4019,119 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + +[[package]] +name = "zerotrie" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36f0bbd478583f79edad978b407914f61b2972f5af6fa089686016be8f9af595" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a05eb080e015ba39cc9e23bbe5e7fb04d5fb040350f99f34e338d5fdd294428" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b96237efa0c878c64bd89c436f661be4e46b2f3eff1ebb976f7ef2321d2f58f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] [[package]] name = "zip" -version = "0.6.6" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261" +checksum = "fabe6324e908f85a1c52063ce7aa26b68dcb7eb6dbc83a2d148403c9bc3eba50" dependencies = [ "aes", - "byteorder", + "arbitrary", "bzip2", "constant_time_eq", "crc32fast", "crossbeam-utils", + "deflate64", + "displaydoc", "flate2", + "getrandom 0.3.3", "hmac", + "indexmap", + "lzma-rs", + "memchr", "pbkdf2", "sha1", + "thiserror 2.0.12", "time", - "zstd 0.11.2+zstd.1.5.2", + "xz2", + "zeroize", + "zopfli", + "zstd", +] + +[[package]] +name = "zopfli" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edfc5ee405f504cd4984ecc6f14d02d55cfda60fa4b689434ef4102aae150cd7" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", ] [[package]] name = "zstd" -version = "0.11.2+zstd.1.5.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ - "zstd-safe 5.0.2+zstd.1.5.2", -] - -[[package]] -name = "zstd" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" -dependencies = [ - "zstd-safe 7.1.0", + "zstd-safe", ] [[package]] name = "zstd-safe" -version = "5.0.2+zstd.1.5.2" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" -dependencies = [ - "libc", - "zstd-sys", -] - -[[package]] -name = "zstd-safe" -version = "7.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" +version = "2.0.15+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +checksum = "eb81183ddd97d0c74cedf1d50d85c8d08c1b8b68ee863bdee9e706eedba1a237" dependencies = [ "cc", "pkg-config", diff --git a/Cargo.toml b/Cargo.toml index 495d81b..904e578 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "artifactview" -version = "0.1.0" +version = "0.4.9" edition = "2021" authors = ["ThetaDev "] license = "MIT" @@ -21,49 +21,73 @@ async_zip = { path = "crates/async_zip", features = [ "tokio-fs", "deflate", ] } -axum = { version = "0.7.5", features = ["http2"] } -axum-extra = { version = "0.9.3", features = ["typed-header"] } +axum = { version = "0.8.0", default-features = false, features = [ + "http1", + "http2", + "json", + "query", + "tokio", + "tracing", +] } +axum-extra = { version = "0.10.0", features = ["typed-header"] } +comrak = { version = "0.40.0", default-features = false } dotenvy = "0.15.7" envy = { path = "crates/envy" } flate2 = "1.0.30" futures-lite = "2.3.0" -governor = "0.6.3" +governor = "0.10.0" headers = "0.4.0" -hex = "0.4.3" http = "1.1.0" humansize = "2.1.3" +junit-parser = { path = "crates/junit-parser" } mime = "0.3.17" mime_guess = "2.0.4" once_cell = "1.19.0" path_macro = "1.0.0" percent-encoding = "2.3.1" pin-project = "1.1.5" -quick_cache = "0.5.1" -rand = "0.8.5" +quick_cache = "0.6.0" +rand = "0.9.0" regex = "1.10.4" reqwest = { version = "0.12.4", default-features = false, features = [ "json", "stream", ] } +secrecy = { version = "0.10.0", features = ["serde"] } serde = { version = "1.0.203", features = ["derive"] } -serde-env = "0.1.1" +serde-env = "0.2.0" serde-hex = "0.1.0" serde_json = "1.0.117" -thiserror = "1.0.61" +serde_urlencoded = "0.7.1" +syntect = { version = "5.2.0", default-features = false, features = [ + "parsing", + "default-syntaxes", + "default-themes", + "html", + "regex-onig", +] } +thiserror = "2.0.0" +time = { version = "0.3.36", features = ["serde-human-readable", "macros"] } tokio = { version = "1.37.0", features = ["macros", "fs", "rt-multi-thread"] } tokio-util = { version = "0.7.11", features = ["io"] } -tower-http = { version = "0.5.2", features = ["trace", "set-header"] } +tower-http = { version = "0.6.0", features = ["trace", "set-header"] } tracing = "0.1.40" tracing-subscriber = "0.3.18" url = "2.5.0" -yarte = "0.15.7" +yarte = { version = "0.15.7", features = ["json"] } [build-dependencies] yarte_helpers = "0.15.8" [dev-dependencies] +axum-test = "17.0.0" +flate2 = "1.0.30" +httpdate = "1.0.3" +insta = { version = "1.39.0", features = ["json"] } proptest = "1.4.0" -rstest = { version = "0.20.0", default-features = false } +rstest = { version = "0.25.0", default-features = false } +scraper = "0.23.0" +temp_testdir = "0.2.3" [workspace] members = [".", "crates/*"] diff --git a/Dockerfile b/Dockerfile index 9fc13e0..a6b958a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,10 +1,10 @@ FROM debian:bookworm-slim -ARG TARGETARCH_ALT +ARG TARGETARCH RUN apt-get update && apt-get install -y libssl3 ca-certificates dumb-init && apt-get clean -COPY target/${TARGETARCH_ALT}-unknown-linux-gnu/release/artifactview /usr/bin/artifactview +COPY target/${TARGETARCH}/release/artifactview /usr/bin/artifactview EXPOSE 3000 ENTRYPOINT ["dumb-init", "artifactview"] diff --git a/Justfile b/Justfile index 7e99822..14695cb 100644 --- a/Justfile +++ b/Justfile @@ -1,5 +1,8 @@ test: - cargo test + cargo nextest run --no-fail-fast + +compress-res: + cd resources && zopfli *.css release: #!/usr/bin/env bash @@ -22,7 +25,7 @@ release: eval "git-cliff $CLIFF_ARGS --output '$CHANGELOG'" fi - git add "$CHANGELOG" + git add . git commit -m "chore(release): release $CRATE v$VERSION" awk 'BEGIN{RS="(^|\n)## [^\n]+\n*"} NR==2 { print }' "$CHANGELOG" | git tag -as -F - --cleanup whitespace "$TAG" diff --git a/README.md b/README.md index 6d078a3..6748c8a 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,325 @@ # Artifactview -View CI build artifacts from Forgejo/Github using your web browser. +View CI build artifacts from Forgejo/GitHub using your web browser! Forgejo and GitHub's CI systems allow you to upload files and directories as [artifacts](https://github.com/actions/upload-artifact). These can be downloaded as zip files. However there is no simple way to view individual files of an artifact. -Artifactview is a small web application that can fetch these CI artifacts and serve -their contents. If the artifact contains a website, it is displayed normally, if it consists -of other files, a file listing is shown. +That's why I developed Artifactview. It is a small web application that fetches these CI +artifacts and serves their contents. -There is also full support for single page applications, placing a file named `200.html` in the -root directory it will be returned in case no file exists for the requested path. +It is a valuable tool in open source software development: you can quickly look at test +reports or coverage data or showcase your single page web applications to your +teammates. -Alternatively, if a file named `404.html` exists in the root directory, it will be returned with -status code 404 if no file was found. +## Features + +- ๐Ÿ“ฆ Quickly view CI artifacts in your browser without messing with zip files +- ๐Ÿ“‚ File listing for directories without index page +- ๐Ÿ  Every artifact has a unique subdomain to support pages with absolute paths +- ๐ŸŒŽ Full SPA support with `200.html` and `404.html` fallback pages +- ๐Ÿ‘๏ธ Viewer for Markdown, syntax-highlighted code and JUnit test reports +- ๐Ÿต Greasemonkey userscript to automatically add a "View artifact" button to + GitHub/Gitea/Forgejo +- ๐Ÿฆ€ Fast and efficient, only extracts files from zip archive if the client does not support gzip +- ๐Ÿ”— Automatically creates pull request comments with links to all build artifacts ## How to use -Artifactview accepts URLs in the given format: `-------.example.com` +Open a Github/Gitea/Forgejo actions run with artifacts and paste its URL into the input +box on the main page. You can also pass the run URL with the `?url=` parameter. + +Artifactview will show you a selection page where you will be able to choose the +artifact you want to browse. + +If there is no `index.html` or fallback page present, a file listing will be shown so +you can browse the contents of the artifact. + +![Artifact file listing](resources/screenshotFiles.png) + +If you want to use Artifactview to showcase a static website, you can make use of +fallback pages. If a file named `200.html` is placed in the root directory, it will be +served in case no file exists for the requested path. This allows serving single-page +applications with custom routing. A custom 404 error page is defined using a file named +`404.html` in the root directory. + +The behavior is the same as with other web hosts like surge.sh, so a lot of website +build tools already follow that convention. + +Artifactview includes different viewers to better display files of certain types that +browsers cannot handle by default. There is a renderer for markdown files as well as a +syntax highlighter for source code files. The viewers are only shown if the files are +accessed with the `?viewer=` URL parameter which is automatically set when opening a +file from a directory listing. You can always download the raw version of the file via +the link in the top right corner. + +![Code viewer](resources/screenshotCode.png) + +Artifactview even includes an interactive viewer for JUnit test reports (XML files with +`junit` in their filename). The application has been designed to be easily extendable, +so if you have suggestions on other viewers that should be added, feel free to create an +issue or a PR. + +![JUnit report viewer](resources/screenshotJUnit.png) + +Accessing Artifactview by copying the CI run URL into its homepage may be a little bit +tedious. That's why there are some convenient alternatives available. + +You can install the Greasemonkey userscript from the link at the bottom of the homepage. +The script adds a "View artifact" link with an eye icon next to every CI artifact on +both GitHub and Forgejo. + +If you want to give every collaborator to your project easy access to previews, you can +automatically create a comment with links to the artifacts under every pull request. + +![Pull request comment](./resources/screenshotPrComment.png) + +To accomplish that, simply add this step to your CI workflow (after uploading the +artifacts). + +```yaml +- name: ๐Ÿ”— Artifactview PR comment + if: ${{ always() && github.event_name == 'pull_request' }} + run: | + if [[ "$GITEA_ACTIONS" == "true" ]]; then RUN_NUMBER="$GITHUB_RUN_NUMBER"; else RUN_NUMBER="$GITHUB_RUN_ID"; fi + curl -SsL --fail-with-body -w "\n" -X POST https://av.thetadev.de/.well-known/api/prComment -H "Content-Type: application/json" \ + --data '{"url": "'"$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$RUN_NUMBER"'", "pr": ${{ github.event.number }}}' +``` + +## API + +Artifactview does have a HTTP API to access data about the CI artifacts. To make the API +available to every site without interfering with any paths from the artifacts, the +endpoints are located within the reserved `/.well-known/api` directory. + +### Get list of artifacts of a CI run + +`GET /.well-known/api/artifacts?url=` + +`GET -------.example.com/.well-known/api/artifacts` + +**Response** + +**Note:** the difference between `download_url` and `user_download_url` is that the +first one is used by the API client and the second one is shown to the user. +`user_download_url` is only set for GitHub artifacts. Forgejo does not have different +download URLs since it does not require authentication to download artifacts. + +```json +[ + { + "id": 1, + "name": "Example", + "size": 1523222, + "expired": false, + "download_url": "https://codeberg.org/thetadev/artifactview/actions/runs/28/artifacts/Example", + "user_download_url": null + } +] +``` + +### Get metadata of the current artifact + +`GET -------.example.com/.well-known/api/artifact` + +**Response** + +```json +{ + "id": 1, + "name": "Example", + "size": 1523222, + "expired": false, + "download_url": "https://codeberg.org/thetadev/artifactview/actions/runs/28/artifacts/Example", + "user_download_url": null +} +``` + +### Get all files from the artifact + +`GET -------.example.com/.well-known/api/files` + +**Response** + +```json +[ + { "name": "example.rs", "size": 406, "crc32": "2013120c" }, + { "name": "README.md", "size": 13060, "crc32": "61c692f0" } +] +``` + +### Create a pull request comment + +`POST /.well-known/api/prComment` + +Artifactview can create a comment under a pull request containing links to view the +artifacts. This way everyone looking at a project can easily access the artifact +previews. + +To use this feature, you need to setup an access token with the permission to create +comments for every code forge you want to use (more details in the section +[Access tokens](#access-tokens)). + +To prevent abuse and spamming, this endpoint is rate-limited and Artifactview will only +create comments after it verified that the workflow matches the given pull request and +the worflow is still running. + +| JSON parameter | Description | +| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `url` (string) โ• | CI workflow URL
Example: https://codeberg.org/ThetaDev/artifactview/actions/runs/31 | +| `pr` (int) โ• | Pull request number | +| `recreate` (bool) | If set to true, the pull request comment will be deleted and recreated if it already exists. If set to false or omitted, the comment will be edited instead. | +| `title` (string) | Comment title (default: "Latest build artifacts") | +| `artifact_titles` (map) | Set custom titles for your artifacts.
Example: `{"Hello": "๐Ÿ  Hello World ;-)"}` | +| `artifact_paths` (map) | Set custom paths for your artifacts if you want the links to point to a specific file (e.g. a test report).
Example: `{"Test": "/junit.xml?viewer=1"}` | + +**Response** + +```json +{ "status": 200, "msg": "created comment #2183634497" } +``` + +## Setup + +You can run artifactview using the docker image provided under +`thetadev256/artifactview:latest` or bare-metal using the provided binaries. + +Artifactview is designed to run behind a reverse proxy since it does not support HTTPS +by itself. If you are using a reverse proxy, you have to set the `REAL_IP_HEADER` option +to the client IP address header name provided by the proxy (usually `x-forwarded-for`. +Otherwise artifactview will assume it is being accessed by only 1 client (the proxy +itself) and the rate limiter would count all users as one. + +### Docker Compose + +Here is an example setup with docker-compose, using Traefik as a reverse proxy: + +```yaml +services: + artifactview: + image: thetadev256/artifactview:latest + restart: unless-stopped + networks: + - proxy + environment: + ROOT_DOMAIN: av.thetadev.de + REAL_IP_HEADER: x-forwarded-for + GITHUB_TOKEN: github_pat_123456 + REPO_WHITELIST: github.com;codeberg.org;code.thetadev.de + SITE_ALIASES: gh=>github.com;cb=>codeberg.org;th=>code.thetadev.de + labels: + - "traefik.enable=true" + - "traefik.docker.network=proxy" + - "traefik.http.routers.artifactview.entrypoints=websecure" + - "traefik.http.routers.artifactview.rule=HostRegexp(`^[a-z0-9-]*.?av.thetadev.de$`)" + +networks: + proxy: + external: true +``` + +### Configuration + +Artifactview is configured using environment variables. + +Note that some variables contain lists and maps of values. Lists need to have their +values separated with semicolons. Maps use an arrow `=>` between key and value, with +pairs separated by semicolons. + +Example list: `foo;bar`, example map: `foo=>f1;bar=>b1` + +| Variable | Default | Description | +| --------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `PORT` | 3000 | HTTP port | +| `CACHE_DIR` | /tmp/artifactview | Temporary directory where to store the artifacts | +| `ROOT_DOMAIN` | localhost:3000 | Public hostname+port number under which artifactview is accessible. If this is configured incorrectly, artifactview will show the error message "host does not end with configured ROOT_DOMAIN" | +| `RUST_LOG` | info | Logging level | +| `NO_HTTPS` | false | Set to True if the website is served without HTTPS (used if testing artifactview without an ) | +| `MAX_ARTIFACT_SIZE` | 100000000 (100 MB) | Maximum size of the artifact zip file to be downloaded | +| `MAX_FILE_SIZE` | 100000000 (100 MB) | Maximum contained file size to be served | +| `MAX_FILE_COUNT` | 10000 | Maximum amount of files within a zip file | +| `MAX_AGE_H` | 12 | Maximum age in hours after which cached artifacts are deleted | +| `ZIP_TIMEOUT_MS` | 1000 | Maximum time in milliseconds for reading the index of a zip file. If this takes too long, the zip file is most likely excessively large or malicious (zip bomb) | +| `GITHUB_TOKEN` | - | GitHub API token for downloading artifacts and creating PR comments. Using a fine-grained token with public read permissions is recommended | +| `FORGEJO_TOKENS` | - | Forgejo API tokens for creating PR comments
Example: `codeberg.org=>fc010f65348468d05e570806275528c936ce93a4` | +| `MEM_CACHE_SIZE` | 50 | Artifactview keeps artifact metadata as well as the zip file indexes in memory to improve performance. The amount of cached items is adjustable. | +| `REAL_IP_HEADER` | - | Get the client IP address from a HTTP request header
If Artifactview is exposed to the network directly, this option has to be unset. If you are using a reverse proxy the proxy needs to be configured to send the actual client IP as a request header.
For most proxies this header is `x-forwarded-for`. | +| `LIMIT_ARTIFACTS_PER_MIN` | 5 | Limit the amount of downloaded artifacts per IP address and minute to prevent excessive resource usage. | +| `LIMIT_PR_COMMENTS_PER_MIN` | 5 | Limit the amount of pull request comment requests per IP address and minute to prevent spamming. | +| `REPO_BLACKLIST` | - | List of sites/users/repos that can NOT be accessed. The blacklist takes precedence over the whitelist (repos included in both lists cannot be accessed)
Example: `github.com/evil-corp/world-destruction;codeberg.org/blackhat;example.org` | +| `REPO_WHITELIST` | - | List of sites/users/repos that can ONLY be accessed. If the whitelist is empty, it will be ignored and any repository can be accessed. Uses the same syntax as `REPO_BLACKLIST`. | +| `SITE_ALIASES` | - | Aliases for sites to make URLs shorter
Example: `gh => github.com;cb => codeberg.org` | +| `SUGGESTED_SITES` | codeberg.org; github.com; gitea.com | List of suggested code forges (host only, without https://, separated by `;`). If repo_whitelist is empty, this value is used for the matched sites in the userscript. The first value is used in the placeholder URL on the home page. | +| `VIEWER_MAX_SIZE` | 500000 | Maximum file size to be displayed using the viewer | + +### Access tokens + +GitHub does not allow downloading artifacts for public repositories for unauthenticated +users. So you need to setup an access token to use Artifactview with GitHub. + +If you are not using the `prComment` feature, you can use a fine-grained access token +with the "Public repositories (read-only)" permission. If you want to create pull +request comments, you have to use a classic token with the "public_repo" scope enabled +(the fine-grained tokens did not work in my test). + +Forgejo does not require access tokens to download artifacts on public repositories, so +you only need to create a token if you want to use the `prComment`-API. In this case, +the token needs the following permissions: + +- Repository and Organization Access: Public only +- issue: Read and write +- user: Read (for determining own user ID) + +Note that if you are using Artifactview to create pull request comments, it is +recommended to create a second bot account instead of using your main account. + +## Technical details + +### URL format + +Artifactview uses URLs in the given format for accessing the individual artifacts: +`-------.hostname` Example: `https://github-com--theta-dev--example-project--4-11.example.com` -## Security considerations +The reason for using subdomains instead of URL paths is that many websites expect to be +served from a separate subdomain and access resources using absolute paths. Using URLs +like `example.com/github.com/theta-dev/example-project/4/11/path/to/file` would make the +application easier to host, but it would not be possible to preview a React/Vue/Svelte +web project. -It is recommended to use the whitelist feature to limit Artifactview to access only trusted -servers, users and organizations. +Since domains only allow letters, numbers and dashes but repository names allow dots and +underscores, these escape sequences are used to access repositories with special +characters in their names. + +- `-0` -> `.` +- `-1` -> `-` +- `-2` -> `_` + +Another issue with using subdomains is that they are limited to a maximum of 63 +characters. Most user and repository names are short enough for this not to become a +problem, but it could still happen that a CI run becomes inaccessible. Since the run ID +is incremented on each new CI run, it might even happen that Artifactview works fine at +the beginning of a project, but the subdomains exceed the length limit in the future. + +That's why I added aliases for forge URLs. You can for example alias github.com as gh, +shaving 8 characters from the subdomain. This makes the subdomains short enogh that you +will be unlikely to hit the limit even with longer user/project names. + +### Security considerations + +It is recommended to use the whitelist feature to allow artifactview to access only +trusted servers, users and organizations. Since many [well-known URIs](https://www.iana.org/assignments/well-known-uris/well-known-uris.xhtml) -are used to configure security-relevant properties of a website or are used to attest -ownership of a website (like `.well-known/acme-challenge` for issuing TLS certificates), -Artifactview will serve no files from the `.well-known` folder. +are used to configure security-relevant properties of a website or attest ownership of a +website (like `.well-known/acme-challenge` for issuing TLS certificates), Artifactview +will serve no files from the `.well-known` folder. There is a configurable limit for both the maximum downloaded artifact size and the -maximum size of individual files to be served (100MB by default). -Additionally there is a configurable timeout for the zip file indexing operation. -These measures should protect the server againt denial-of-service attacks like -overfilling the server drive or uploading zip bombs. +maximum size of individual files to be served (100 MB by default). Additionally there is +a configurable timeout for the zip file indexing operation. These measures should +protect the server against denial-of-service attacks like overfilling the server drive +or uploading zip bombs. diff --git a/crates/async_zip/Cargo.toml b/crates/async_zip/Cargo.toml index 2f32d76..bb672fc 100644 --- a/crates/async_zip/Cargo.toml +++ b/crates/async_zip/Cargo.toml @@ -37,7 +37,7 @@ rustdoc-args = ["--cfg", "docsrs"] crc32fast = "1" futures-lite = { version = "2.1.0", default-features = false, features = ["std"] } pin-project = "1" -thiserror = "1" +thiserror = "2" async-compression = { version = "0.4.2", default-features = false, features = ["futures-io"], optional = true } chrono = { version = "0.4", default-features = false, features = ["clock"], optional = true } @@ -49,7 +49,7 @@ tokio-util = { version = "0.7", features = ["compat"], optional = true } tokio = { version = "1", features = ["full"] } tokio-util = { version = "0.7", features = ["compat"] } env_logger = "0.11.2" -zip = "0.6.3" +zip = "2.2.2" # shared across multiple examples # anyhow = "1" diff --git a/crates/async_zip/src/base/read/io/combined_record.rs b/crates/async_zip/src/base/read/io/combined_record.rs index d3d41d9..0e95db0 100644 --- a/crates/async_zip/src/base/read/io/combined_record.rs +++ b/crates/async_zip/src/base/read/io/combined_record.rs @@ -15,6 +15,7 @@ pub struct CombinedCentralDirectoryRecord { pub num_entries_in_directory: u64, pub directory_size: u64, pub offset_of_start_of_directory: u64, + #[allow(dead_code)] pub file_comment_length: u16, } diff --git a/crates/async_zip/src/base/read/io/entry.rs b/crates/async_zip/src/base/read/io/entry.rs index 64e81c6..1657bb1 100644 --- a/crates/async_zip/src/base/read/io/entry.rs +++ b/crates/async_zip/src/base/read/io/entry.rs @@ -51,7 +51,7 @@ where } } -impl<'a, R, E> AsyncRead for ZipEntryReader<'a, R, E> +impl AsyncRead for ZipEntryReader<'_, R, E> where R: AsyncBufRead + Unpin, { @@ -60,7 +60,7 @@ where } } -impl<'a, R, E> ZipEntryReader<'a, R, E> +impl ZipEntryReader<'_, R, E> where R: AsyncBufRead + Unpin, { @@ -118,7 +118,7 @@ enum OwnedEntry<'a> { Borrow(&'a ZipEntry), } -impl<'a> OwnedEntry<'a> { +impl OwnedEntry<'_> { pub fn entry(&self) -> &'_ ZipEntry { match self { OwnedEntry::Owned(entry) => entry, diff --git a/crates/async_zip/src/base/read/io/owned.rs b/crates/async_zip/src/base/read/io/owned.rs index 371ffab..4e336d0 100644 --- a/crates/async_zip/src/base/read/io/owned.rs +++ b/crates/async_zip/src/base/read/io/owned.rs @@ -17,7 +17,7 @@ pub(crate) enum OwnedReader<'a, R> { Borrow(#[pin] &'a mut R), } -impl<'a, R> OwnedReader<'a, R> +impl OwnedReader<'_, R> where R: AsyncBufRead + Unpin, { @@ -30,7 +30,7 @@ where } } -impl<'a, R> AsyncBufRead for OwnedReader<'a, R> +impl AsyncBufRead for OwnedReader<'_, R> where R: AsyncBufRead + Unpin, { @@ -49,7 +49,7 @@ where } } -impl<'a, R> AsyncRead for OwnedReader<'a, R> +impl AsyncRead for OwnedReader<'_, R> where R: AsyncBufRead + Unpin, { diff --git a/crates/async_zip/src/base/write/compressed_writer.rs b/crates/async_zip/src/base/write/compressed_writer.rs index 3b71421..f24b4c3 100644 --- a/crates/async_zip/src/base/write/compressed_writer.rs +++ b/crates/async_zip/src/base/write/compressed_writer.rs @@ -64,7 +64,7 @@ impl<'b, W: AsyncWrite + Unpin> CompressedAsyncWriter<'b, W> { } } -impl<'b, W: AsyncWrite + Unpin> AsyncWrite for CompressedAsyncWriter<'b, W> { +impl AsyncWrite for CompressedAsyncWriter<'_, W> { fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { match *self { CompressedAsyncWriter::Stored(ref mut inner) => Pin::new(inner).poll_write(cx, buf), diff --git a/crates/async_zip/src/base/write/entry_stream.rs b/crates/async_zip/src/base/write/entry_stream.rs index cc41f0e..5f30aa2 100644 --- a/crates/async_zip/src/base/write/entry_stream.rs +++ b/crates/async_zip/src/base/write/entry_stream.rs @@ -251,7 +251,7 @@ impl<'b, W: AsyncWrite + Unpin> EntryStreamWriter<'b, W> { } } -impl<'a, W: AsyncWrite + Unpin> AsyncWrite for EntryStreamWriter<'a, W> { +impl AsyncWrite for EntryStreamWriter<'_, W> { fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context, buf: &[u8]) -> Poll> { let poll = Pin::new(&mut self.writer).poll_write(cx, buf); diff --git a/crates/async_zip/src/tests/read/zip64/mod.rs b/crates/async_zip/src/tests/read/zip64/mod.rs index 758d410..c1b88c2 100644 --- a/crates/async_zip/src/tests/read/zip64/mod.rs +++ b/crates/async_zip/src/tests/read/zip64/mod.rs @@ -70,7 +70,7 @@ fn generate_zip64many_zip() -> std::path::PathBuf { let zip_file = std::fs::File::create(&path).unwrap(); let mut zip = zip::ZipWriter::new(zip_file); - let options = FileOptions::default().compression_method(zip::CompressionMethod::Stored); + let options = FileOptions::<()>::default().compression_method(zip::CompressionMethod::Stored); for i in 0..2_u32.pow(16) + 1 { zip.start_file(format!("{i}.txt"), options).unwrap(); diff --git a/crates/async_zip/src/tests/write/zip64/mod.rs b/crates/async_zip/src/tests/write/zip64/mod.rs index 01f3211..4b46e6c 100644 --- a/crates/async_zip/src/tests/write/zip64/mod.rs +++ b/crates/async_zip/src/tests/write/zip64/mod.rs @@ -36,7 +36,7 @@ async fn test_write_zip64_file() { let cursor = std::io::Cursor::new(buffer); let mut zip = zip::read::ZipArchive::new(cursor).unwrap(); let mut file1 = zip.by_name("file1").unwrap(); - assert_eq!(file1.extra_data(), &[] as &[u8]); + assert_eq!(file1.extra_data(), Some(&[] as &[u8])); let mut buffer = Vec::new(); file1.read_to_end(&mut buffer).unwrap(); assert_eq!(buffer.as_slice(), &[0, 0, 0, 0]); @@ -74,7 +74,7 @@ async fn test_write_large_zip64_file() { assert_eq!(zip64.compressed_size.unwrap(), BATCHED_FILE_SIZE as u64); assert_eq!(zip64.uncompressed_size.unwrap(), BATCHED_FILE_SIZE as u64); } - e => panic!("Expected a Zip64 extended field, got {:?}", e), + e => panic!("Expected a Zip64 extended field, got {e:?}"), } assert_eq!(cd_entry.header.uncompressed_size, NON_ZIP64_MAX_SIZE); assert_eq!(cd_entry.header.compressed_size, NON_ZIP64_MAX_SIZE); diff --git a/crates/envy/src/error.rs b/crates/envy/src/error.rs index 3d0fec1..243bf73 100644 --- a/crates/envy/src/error.rs +++ b/crates/envy/src/error.rs @@ -15,15 +15,15 @@ impl StdError for Error {} impl fmt::Display for Error { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { match self { - Error::MissingValue(field) => write!(fmt, "missing value for {}", &field), - Error::Custom(ref msg) => write!(fmt, "{}", msg), + Error::MissingValue(field) => write!(fmt, "missing value for {field}"), + Error::Custom(ref msg) => write!(fmt, "{msg}"), } } } impl SerdeError for Error { fn custom(msg: T) -> Self { - Error::Custom(format!("{}", msg)) + Error::Custom(format!("{msg}")) } fn missing_field(field: &'static str) -> Error { diff --git a/crates/envy/src/lib.rs b/crates/envy/src/lib.rs index 3f00ae4..18f51e4 100644 --- a/crates/envy/src/lib.rs +++ b/crates/envy/src/lib.rs @@ -77,7 +77,7 @@ where struct Val(String, String); -impl<'de> IntoDeserializer<'de, Error> for Val { +impl IntoDeserializer<'_, Error> for Val { type Deserializer = Self; fn into_deserializer(self) -> Self::Deserializer { @@ -87,7 +87,7 @@ impl<'de> IntoDeserializer<'de, Error> for Val { struct VarName(String); -impl<'de> IntoDeserializer<'de, Error> for VarName { +impl IntoDeserializer<'_, Error> for VarName { type Deserializer = Self; fn into_deserializer(self) -> Self::Deserializer { @@ -248,7 +248,7 @@ struct Deserializer<'de, Iter: Iterator> { inner: MapDeserializer<'de, Vars, Error>, } -impl<'de, Iter: Iterator> Deserializer<'de, Iter> { +impl> Deserializer<'_, Iter> { fn new(vars: Iter) -> Self { Deserializer { inner: MapDeserializer::new(Vars(vars)), @@ -308,7 +308,7 @@ where /// These types are created with with the [prefixed](fn.prefixed.html) module function pub struct Prefixed<'a>(Cow<'a, str>); -impl<'a> Prefixed<'a> { +impl Prefixed<'_> { /// Deserializes a type based on prefixed env variables pub fn from_env(&self) -> Result where @@ -390,7 +390,7 @@ impl<'a> SplitEscaped<'a> { } } -impl<'a> Iterator for SplitEscaped<'a> { +impl Iterator for SplitEscaped<'_> { type Item = String; fn next(&mut self) -> Option { @@ -492,7 +492,7 @@ mod tests { newtype: CustomNewType(42) } ), - Err(e) => panic!("{:#?}", e), + Err(e) => panic!("{e:#?}"), } } @@ -564,7 +564,7 @@ mod tests { newtype: CustomNewType(42) } ), - Err(e) => panic!("{:#?}", e), + Err(e) => panic!("{e:#?}"), } } diff --git a/crates/junit-parser/Cargo.toml b/crates/junit-parser/Cargo.toml new file mode 100644 index 0000000..290cda5 --- /dev/null +++ b/crates/junit-parser/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "junit-parser" +version = "0.1.0" +edition = "2021" +authors = ["Boris Faure "] +license = "BSD-2-Clause" +repository = "https://github.com/borisfaure/junit-parser" + +[dependencies] +quick-xml = { version = "0.37.0", features = ["escape-html"] } +thiserror = "2.0.0" +time = { version = "0.3.36", features = ["parsing", "serde-well-known"] } +serde = { version = "1.0", features = ["derive"] } + +[dev-dependencies] +insta = { version = "1.39.0", features = ["json"] } +once_cell = "1.19.0" +path_macro = "1.0.0" diff --git a/crates/junit-parser/src/errors.rs b/crates/junit-parser/src/errors.rs new file mode 100644 index 0000000..bd9f70e --- /dev/null +++ b/crates/junit-parser/src/errors.rs @@ -0,0 +1,40 @@ +#![warn(missing_docs)] +use thiserror::Error; + +/// Error enumerates all possible errors returned by this library. +#[derive(Error, Debug)] +pub enum Error { + /// Error while parsing XML + #[error("Error while parsing XML: {0}")] + Xml(#[from] ::quick_xml::Error), + /// Error while converting f64 attribute + #[error("Error while converting f64 attribute: {0}")] + ParseFloat(#[from] std::num::ParseFloatError), + /// Error while converting u64 attribute + #[error("Error while converting u64 attribute: {0}")] + ParseInt(#[from] std::num::ParseIntError), + /// Error while converting bytes to Utf8 + #[error("Error while converting bytes to Utf8: {0}")] + ParseUt8(#[from] std::str::Utf8Error), + #[error("Error while parsing timestamp: {0}")] + ParseTimestamp(#[from] time::error::Parse), + /// Error parsing the `property` element: missing `name` + #[error("Missing `name` attribute in property")] + MissingPropertyName, +} + +impl From<::quick_xml::events::attributes::AttrError> for Error { + #[inline] + /// Convert [`::quick_xml::events::attributes`] into [`Error::XMLError`] + fn from(err: ::quick_xml::events::attributes::AttrError) -> Error { + Error::Xml(err.into()) + } +} + +impl From<::quick_xml::escape::EscapeError> for Error { + #[inline] + /// Convert [`::quick_xml::escape::EscapeError`] into [`Error::XMLError`] + fn from(err: ::quick_xml::escape::EscapeError) -> Error { + Error::Xml(err.into()) + } +} diff --git a/crates/junit-parser/src/lib.rs b/crates/junit-parser/src/lib.rs new file mode 100644 index 0000000..1b8a093 --- /dev/null +++ b/crates/junit-parser/src/lib.rs @@ -0,0 +1,629 @@ +use std::borrow::Cow; +use std::io::BufRead; + +use quick_xml::errors::IllFormedError; +use quick_xml::escape::unescape; +use quick_xml::events::BytesStart as XMLBytesStart; +use quick_xml::events::Event as XMLEvent; +use quick_xml::name::QName; +use quick_xml::Error as XMLError; +use quick_xml::Reader as XMLReader; +use serde::{Deserialize, Serialize}; +use std::str; +use time::OffsetDateTime; + +mod errors; + +use errors::Error; + +/// Struct representing a JUnit report, containing test suites +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct TestSuites { + /// Name of the test suites, from the `name` attribute + pub name: String, + /// List of tests suites represented by [`TestSuite`] + pub suites: Vec, + /// How long the test suites took to run, from the `time` attribute + pub time: f64, + /// Number of tests in the test suites, from the `tests` attribute + pub tests: u64, + /// Number of tests in error in the test suites, from the `errors` attribute + pub errors: u64, + /// Number of tests in failure in the test suites, from the `failures` attribute + pub failures: u64, + /// Number of tests skipped in the test suites, from the `skipped` attribute + pub skipped: u64, + /// Number of tests that passed after failed attempts + pub flaky: u64, +} + +/// A test suite, containing test cases [`TestCase`] +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct TestSuite { + /// Name of the test suite, from the `name` attribute + pub name: String, + /// Timestamp when the test suite was run, from the `timestamp` attribute + #[serde(with = "time::serde::rfc3339::option")] + pub timestamp: Option, + /// List of status of tests represented by [`TestCase`] + pub cases: Vec, + /// How long the test suite took to run, from the `time` attribute + pub time: f64, + /// Number of tests in the test suite, from the `tests` attribute + pub tests: u64, + /// Number of tests in error in the test suite, from the `errors` attribute + pub errors: u64, + /// Number of tests in failure in the test suite, from the `failures` attribute + pub failures: u64, + /// Number of tests skipped in the test suites, from the `skipped` attribute + pub skipped: u64, + /// Number of tests that passed after failed attempts + pub flaky: u64, +} + +/// A test case +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct TestCase { + /// Name of the test case, from the `name` attribute + pub name: String, + /// Original name, from the `name` attribute + pub original_name: String, + /// Class name, from the `classname` attribute + pub classname: Option, + /// Timestamp when the test case was run + #[serde(with = "time::serde::rfc3339::option")] + pub timestamp: Option, + /// Run time in seconds + pub time: f64, + /// Status of the test case + pub status: TestStatus, + /// stdout output from the `system-out` element + pub system_out: Option, + /// stderr output from the `system-err` element + pub system_err: Option, + /// Previous test attempts, from `rerunFailure` and `flakyFailure` element + pub retries: Vec, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub enum TestStatus { + #[default] + Success, + Error(Message), + Failure(Message), + Flaky, + Skipped, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct Message { + pub message: String, + pub text: String, +} + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct Retry { + /// Timestamp when the retry was run + #[serde(with = "time::serde::rfc3339::option")] + pub timestamp: Option, + /// Run time in seconds + pub time: f64, + /// Status of the retry + pub status: TestStatus, + /// stdout output from the `system-out` element + pub system_out: Option, + /// stderr output from the `system-err` element + pub system_err: Option, +} + +impl TestSuites { + /// Fill up `self` with attributes from the XML tag + fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> { + for a in e.attributes() { + let a = a?; + match a.key { + QName(b"time") => self.time = try_from_attribute_value_f64(a.value)?, + QName(b"tests") => self.tests = try_from_attribute_value_u64(a.value)?, + QName(b"errors") => self.errors = try_from_attribute_value_u64(a.value)?, + QName(b"failures") => self.failures = try_from_attribute_value_u64(a.value)?, + QName(b"skipped") => self.skipped = try_from_attribute_value_u64(a.value)?, + QName(b"name") => self.name = try_from_attribute_value_string(a.value)?, + _ => {} + }; + } + Ok(()) + } + + /// New [`TestSuites`] from empty XML tag + fn new_empty(e: &XMLBytesStart) -> Result { + let mut ts = Self::default(); + ts.parse_attributes(e)?; + Ok(ts) + } + + /// New [`TestSuites`] from XML tree + fn from_reader(e: &XMLBytesStart, r: &mut XMLReader) -> Result { + let mut ts = Self::default(); + ts.parse_attributes(e)?; + let mut buf = Vec::new(); + loop { + match r.read_event_into(&mut buf) { + Ok(XMLEvent::End(ref e)) if e.name() == QName(b"testsuites") => break, + Ok(XMLEvent::End(ref e)) if e.name() == QName(b"testrun") => break, + Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testsuite") => { + ts.suites.push(TestSuite::from_reader(e, r)?); + } + Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testsuite") => { + ts.suites.push(TestSuite::new_empty(e)?); + } + Ok(XMLEvent::Eof) => { + return Err(XMLError::IllFormed(IllFormedError::MissingEndTag( + "testsuites".to_string(), + )) + .into()); + } + Err(err) => return Err(err.into()), + _ => (), + } + } + + ts.flaky = ts.suites.iter().map(|s| s.flaky).sum(); + + Ok(ts) + } +} + +impl TestSuite { + /// Fill up `self` with attributes from the XML tag + fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> { + for a in e.attributes() { + let a = a?; + match a.key { + QName(b"time") => self.time = try_from_attribute_value_f64(a.value)?, + QName(b"tests") => self.tests = try_from_attribute_value_u64(a.value)?, + QName(b"errors") => self.errors = try_from_attribute_value_u64(a.value)?, + QName(b"failures") => self.failures = try_from_attribute_value_u64(a.value)?, + QName(b"skipped") => self.skipped = try_from_attribute_value_u64(a.value)?, + QName(b"name") => self.name = try_from_attribute_value_string(a.value)?, + QName(b"timestamp") => { + self.timestamp = Some(try_from_attribute_value_timestamp(a.value)?) + } + _ => {} + }; + } + Ok(()) + } + + /// New [`TestSuite`] from empty XML tag + fn new_empty(e: &XMLBytesStart) -> Result { + let mut ts = Self::default(); + ts.parse_attributes(e)?; + Ok(ts) + } + + /// New [`TestSuite`] from XML tree + fn from_reader(e: &XMLBytesStart, r: &mut XMLReader) -> Result { + let mut ts = Self::default(); + ts.parse_attributes(e)?; + let mut buf = Vec::new(); + loop { + match r.read_event_into(&mut buf) { + Ok(XMLEvent::End(ref e)) if e.name() == QName(b"testsuite") => break, + Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testcase") => { + ts.cases.push(TestCase::from_reader(e, r)?); + } + Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testcase") => { + ts.cases.push(TestCase::new_empty(e)?); + } + Ok(XMLEvent::Eof) => { + return Err(XMLError::IllFormed(IllFormedError::MissingEndTag( + "testsuite".to_string(), + )) + .into()); + } + Err(err) => return Err(err.into()), + _ => (), + } + } + + ts.flaky = ts + .cases + .iter() + .filter(|c| matches!(c.status, TestStatus::Flaky)) + .count() as u64; + + Ok(ts) + } +} + +impl TestCase { + /// Fill up `self` with attributes from the XML tag + fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> { + for a in e.attributes() { + let a = a?; + match a.key { + QName(b"time") => self.time = try_from_attribute_value_f64(a.value)?, + QName(b"timestamp") => { + self.timestamp = Some(try_from_attribute_value_timestamp(a.value)?) + } + QName(b"name") => self.original_name = try_from_attribute_value_string(a.value)?, + QName(b"classname") => { + self.classname = Some(try_from_attribute_value_string(a.value)?) + } + _ => {} + } + } + if let Some(cn) = self.classname.as_ref() { + self.name = format!("{}::{}", cn, self.original_name); + } else { + self.name.clone_from(&self.original_name); + } + Ok(()) + } + + /// New [`TestCase`] from empty XML tag + fn new_empty(e: &XMLBytesStart) -> Result { + let mut tc = Self::default(); + tc.parse_attributes(e)?; + Ok(tc) + } + + /// New [`TestCase`] from XML tree + fn from_reader(e: &XMLBytesStart, r: &mut XMLReader) -> Result { + let mut tc = Self::default(); + tc.parse_attributes(e)?; + let mut buf = Vec::new(); + loop { + match r.read_event_into(&mut buf)? { + XMLEvent::End(ref e) if e.name() == QName(b"testcase") => break, + XMLEvent::Start(ref e) if e.name() == QName(b"skipped") => { + tc.status = TestStatus::Skipped; + } + XMLEvent::Empty(ref e) if e.name() == QName(b"skipped") => { + tc.status = TestStatus::Skipped; + } + XMLEvent::Start(ref e) if e.name() == QName(b"failure") => { + let msg = Message::from_reader(e, r)?; + tc.status = TestStatus::Failure(msg); + } + XMLEvent::Empty(ref e) if e.name() == QName(b"failure") => { + let msg = Message::new_empty(e)?; + tc.status = TestStatus::Failure(msg); + } + XMLEvent::Start(ref e) if e.name() == QName(b"error") => { + let msg = Message::from_reader(e, r)?; + tc.status = TestStatus::Error(msg); + } + XMLEvent::Empty(ref e) if e.name() == QName(b"error") => { + let msg = Message::new_empty(e)?; + tc.status = TestStatus::Error(msg); + } + XMLEvent::Start(ref e) if e.name() == QName(b"system-out") => { + tc.system_out = parse_system(e, r)?; + } + XMLEvent::Start(ref e) if e.name() == QName(b"system-err") => { + tc.system_err = parse_system(e, r)?; + } + XMLEvent::Empty(ref e) if e.name() == QName(b"rerunFailure") => { + tc.retries.push(Retry::new_empty(e)?); + } + XMLEvent::Start(ref e) if e.name() == QName(b"rerunFailure") => { + tc.retries.push(Retry::from_reader(e, r)?); + } + XMLEvent::Empty(ref e) if e.name() == QName(b"flakyFailure") => { + tc.status = TestStatus::Flaky; + tc.retries.push(Retry::new_empty(e)?); + } + XMLEvent::Start(ref e) if e.name() == QName(b"flakyFailure") => { + tc.status = TestStatus::Flaky; + tc.retries.push(Retry::from_reader(e, r)?); + } + XMLEvent::Eof => { + return Err(XMLError::IllFormed(IllFormedError::MissingEndTag( + "testcase".to_string(), + )) + .into()); + } + _ => (), + } + } + Ok(tc) + } + + pub fn status_txt(&self) -> Cow<'static, str> { + match self.status { + TestStatus::Success => "Success".into(), + TestStatus::Error(_) => "Error".into(), + TestStatus::Failure(_) => { + if self.retries.is_empty() { + "Failure".into() + } else { + format!("Failure (after {} retries)", self.retries.len()).into() + } + } + TestStatus::Flaky => format!( + "Flaky (passed after {} failed attempt{})", + self.retries.len(), + if self.retries.len() == 1 { "s" } else { "" } + ) + .into(), + TestStatus::Skipped => "Skipped".into(), + } + } +} + +impl TestStatus { + pub fn id(&self) -> &'static str { + match self { + TestStatus::Success => "success", + TestStatus::Error(_) => "error", + TestStatus::Failure(_) => "failure", + TestStatus::Flaky => "flaky", + TestStatus::Skipped => "skipped", + } + } + + pub fn message(&self) -> Option<&Message> { + match self { + TestStatus::Error(msg) => Some(msg), + TestStatus::Failure(msg) => Some(msg), + _ => None, + } + } +} + +impl Message { + /// Fill up `self` with attributes from the XML tag + fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> { + for a in e.attributes() { + let a = a?; + if let QName(b"message") = a.key { + self.message = try_from_attribute_value_string(a.value)? + } + } + Ok(()) + } + + /// New [`Message`] from empty XML tag + fn new_empty(e: &XMLBytesStart) -> Result { + let mut tf = Self::default(); + tf.parse_attributes(e)?; + Ok(tf) + } + + /// New [`Message`] from XML tree + fn from_reader(e: &XMLBytesStart, r: &mut XMLReader) -> Result { + let name = e.name(); + let mut msg = Self::default(); + msg.parse_attributes(e)?; + let mut buf = Vec::new(); + loop { + match r.read_event_into(&mut buf) { + Ok(XMLEvent::End(ref e)) if e.name() == name => break, + Ok(XMLEvent::Text(e)) => { + msg.text += e.unescape()?.trim(); + } + Ok(XMLEvent::Eof) => { + return Err(XMLError::IllFormed(IllFormedError::MissingEndTag( + String::from_utf8(name.0.to_vec()).unwrap_or_default(), + )) + .into()); + } + Err(err) => return Err(err.into()), + _ => (), + } + } + Ok(msg) + } +} + +impl Retry { + /// Fill up `self` with attributes from the XML tag + fn parse_attributes(&mut self, e: &XMLBytesStart) -> Result<(), Error> { + for a in e.attributes() { + let a = a?; + match a.key { + QName(b"time") => self.time = try_from_attribute_value_f64(a.value)?, + QName(b"timestamp") => { + self.timestamp = Some(try_from_attribute_value_timestamp(a.value)?) + } + _ => {} + }; + } + Ok(()) + } + + fn new_empty(e: &XMLBytesStart) -> Result { + let mut rt = Self::default(); + rt.parse_attributes(e)?; + Ok(rt) + } + + fn from_reader(e: &XMLBytesStart, r: &mut XMLReader) -> Result { + let name = e.name(); + let mut rt = Self::default(); + rt.parse_attributes(e)?; + + let mut msg = Message::new_empty(e)?; + let mut buf = Vec::new(); + loop { + match r.read_event_into(&mut buf) { + Ok(XMLEvent::End(ref e)) if e.name() == name => break, + Ok(XMLEvent::Text(e)) => { + msg.text += e.unescape()?.trim(); + } + Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"system-out") => { + rt.system_out = parse_system(e, r)?; + } + Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"system-err") => { + rt.system_err = parse_system(e, r)?; + } + Ok(XMLEvent::Eof) => { + return Err(XMLError::IllFormed(IllFormedError::MissingEndTag( + String::from_utf8(name.0.to_vec()).unwrap_or_default(), + )) + .into()); + } + Err(err) => return Err(err.into()), + _ => (), + } + } + + rt.status = TestStatus::Failure(msg); + Ok(rt) + } +} + +/// Try to decode attribute value as [`f64`] +fn try_from_attribute_value_f64(value: Cow<[u8]>) -> Result { + match str::from_utf8(&value)? { + "" => Ok(f64::default()), + s => Ok(s.parse::()?), + } +} + +/// Try to decode attribute value as [`u64`] +fn try_from_attribute_value_u64(value: Cow<[u8]>) -> Result { + match str::from_utf8(&value)? { + "" => Ok(u64::default()), + s => Ok(s.parse::()?), + } +} + +/// Try to decode and unescape attribute value as [`String`] +fn try_from_attribute_value_string(value: Cow<[u8]>) -> Result { + let s = str::from_utf8(&value)?; + let u = unescape(s)?; + Ok(u.to_string()) +} + +/// Try to decode and unescape attribute value as [`String`] +fn try_from_attribute_value_timestamp(value: Cow<[u8]>) -> Result { + let s = str::from_utf8(&value)?; + let t = OffsetDateTime::parse(s, &time::format_description::well_known::Rfc3339)?; + Ok(t) +} + +/// Parse a chunk of xml as system-out or system-err +fn parse_system( + orig: &XMLBytesStart, + r: &mut XMLReader, +) -> Result, Error> { + let mut buf = Vec::new(); + let mut res = None; + loop { + match r.read_event_into(&mut buf) { + Ok(XMLEvent::End(ref e)) if e.name() == orig.name() => break, + Ok(XMLEvent::Text(e)) => { + res = Some(e.unescape()?.to_string()); + } + Ok(XMLEvent::Eof) => { + return Err(XMLError::IllFormed(IllFormedError::MissingEndTag( + String::from_utf8(orig.name().0.to_vec()).unwrap_or_default(), + )) + .into()); + } + Err(err) => return Err(err.into()), + _ => (), + } + } + Ok(res) +} + +/// Creates a [`TestSuites`](struct.TestSuites.html) structure from a JUnit XML data read from `reader` +/// +/// # Example +/// ``` +/// use std::io::Cursor; +/// let xml = r#" +/// +/// +/// +/// +/// details about failure +/// +/// +/// "#; +/// let cursor = Cursor::new(xml); +/// let r = junit_parser::from_reader(cursor); +/// assert!(r.is_ok()); +/// ``` +pub fn from_reader(reader: B) -> Result { + let mut r = XMLReader::from_reader(reader); + let mut buf = Vec::new(); + loop { + match r.read_event_into(&mut buf) { + Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testsuites") => { + return TestSuites::new_empty(e); + } + Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testrun") => { + return TestSuites::new_empty(e); + } + Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testsuites") => { + return TestSuites::from_reader(e, &mut r); + } + Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testrun") => { + return TestSuites::from_reader(e, &mut r); + } + Ok(XMLEvent::Empty(ref e)) if e.name() == QName(b"testsuite") => { + let ts = TestSuite::new_empty(e)?; + let mut suites = TestSuites::default(); + suites.suites.push(ts); + return Ok(suites); + } + Ok(XMLEvent::Start(ref e)) if e.name() == QName(b"testsuite") => { + let ts = TestSuite::from_reader(e, &mut r)?; + let mut suites = TestSuites::default(); + suites.suites.push(ts); + return Ok(suites); + } + Ok(XMLEvent::Eof) => { + return Err(XMLError::IllFormed(IllFormedError::MissingEndTag( + "testsuites".to_string(), + )) + .into()); + } + Err(err) => return Err(err.into()), + _ => (), + } + } +} + +/// Creates a [`TestSuites`](struct.TestSuites.html) structure from a JUnit XML data read from a string +pub fn from_str(s: &str) -> Result { + from_reader(s.as_bytes()) +} + +#[cfg(test)] +mod tests { + use std::{fs::File, io::BufReader, path::PathBuf}; + + use once_cell::sync::Lazy; + use path_macro::path; + + use super::*; + + pub static TESTFILES: Lazy = + Lazy::new(|| path!(env!("CARGO_MANIFEST_DIR") / "testfiles")); + + fn parse_test(n: &str) { + let file = File::open(path!(*TESTFILES / format!("{n}.junit.xml"))).unwrap(); + let suites = from_reader(BufReader::new(file)).unwrap(); + insta::assert_json_snapshot!(format!("parse_{n}"), suites); + } + + #[test] + fn parse_simple() { + parse_test("simple") + } + + #[test] + fn parse_vite() { + parse_test("vite") + } + + #[test] + fn parse_retry() { + parse_test("retry") + } +} diff --git a/crates/junit-parser/src/snapshots/junit_parser__tests__parse_retry.snap b/crates/junit-parser/src/snapshots/junit_parser__tests__parse_retry.snap new file mode 100644 index 0000000..925485e --- /dev/null +++ b/crates/junit-parser/src/snapshots/junit_parser__tests__parse_retry.snap @@ -0,0 +1,4776 @@ +--- +source: crates/junit-parser/src/lib.rs +expression: suites +--- +{ + "name": "nextest-run", + "suites": [ + { + "name": "rustypipe", + "timestamp": null, + "cases": [ + { + "name": "rustypipe::client::channel::tests::channel_agegate", + "original_name": "client::channel::tests::channel_agegate", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.887+02:00", + "time": 0.01, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::t_channel_info_ctoken", + "original_name": "client::channel::tests::t_channel_info_ctoken", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.897+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::t_order_ctoken", + "original_name": "client::channel::tests::t_order_ctoken", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.905+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_05_empty", + "original_name": "client::channel::tests::map_channel_videos::case_05_empty", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.888+02:00", + "time": 0.036, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_info", + "original_name": "client::channel::tests::map_channel_info", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.887+02:00", + "time": 0.043, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_02_music", + "original_name": "client::channel::tests::map_channel_videos::case_02_music", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.888+02:00", + "time": 0.061, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_playlists", + "original_name": "client::channel::tests::map_channel_playlists", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.887+02:00", + "time": 0.079, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_12_pageheader", + "original_name": "client::channel::tests::map_channel_videos::case_12_pageheader", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.89+02:00", + "time": 0.085, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_10_shorts", + "original_name": "client::channel::tests::map_channel_videos::case_10_shorts", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.89+02:00", + "time": 0.09, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_04_live", + "original_name": "client::channel::tests::map_channel_videos::case_04_live", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.888+02:00", + "time": 0.093, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_artist::tests::map_music_artist::case_3_no_artist", + "original_name": "client::music_artist::tests::map_music_artist::case_3_no_artist", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.93+02:00", + "time": 0.057, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_11_livestreams", + "original_name": "client::channel::tests::map_channel_videos::case_11_livestreams", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.89+02:00", + "time": 0.107, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_08_richgrid2", + "original_name": "client::channel::tests::map_channel_videos::case_08_richgrid2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.889+02:00", + "time": 0.108, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_01_base", + "original_name": "client::channel::tests::map_channel_videos::case_01_base", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.887+02:00", + "time": 0.111, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_07_richgrid", + "original_name": "client::channel::tests::map_channel_videos::case_07_richgrid", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.889+02:00", + "time": 0.111, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_13_pageheader2", + "original_name": "client::channel::tests::map_channel_videos::case_13_pageheader2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.891+02:00", + "time": 0.118, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_artist::tests::map_music_artist_secondary_channel", + "original_name": "client::music_artist::tests::map_music_artist_secondary_channel", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.975+02:00", + "time": 0.036, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_03_withshorts", + "original_name": "client::channel::tests::map_channel_videos::case_03_withshorts", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.888+02:00", + "time": 0.125, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_06_upcoming", + "original_name": "client::channel::tests::map_channel_videos::case_06_upcoming", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.889+02:00", + "time": 0.131, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_artist::tests::map_music_artist::case_2_only_singles", + "original_name": "client::music_artist::tests::map_music_artist::case_2_only_singles", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.925+02:00", + "time": 0.096, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_09_coachella", + "original_name": "client::channel::tests::map_channel_videos::case_09_coachella", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.889+02:00", + "time": 0.134, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_details::tests::map_lyrics", + "original_name": "client::music_details::tests::map_lyrics", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.988+02:00", + "time": 0.039, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_details::tests::map_music_details::case_1_mv", + "original_name": "client::music_details::tests::map_music_details::case_1_mv", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.997+02:00", + "time": 0.042, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_details::tests::map_music_details::case_2_track", + "original_name": "client::music_details::tests::map_music_details::case_2_track", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.997+02:00", + "time": 0.056, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_genres::tests::map_music_genres", + "original_name": "client::music_genres::tests::map_music_genres", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.02+02:00", + "time": 0.038, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_new::tests::map_music_new_videos::case_1_default", + "original_name": "client::music_new::tests::map_music_new_videos::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.023+02:00", + "time": 0.074, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_album::case_3_single", + "original_name": "client::music_playlist::tests::map_music_album::case_3_single", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.058+02:00", + "time": 0.042, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_details::tests::map_music_radio::case_1_mv", + "original_name": "client::music_details::tests::map_music_radio::case_1_mv", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.998+02:00", + "time": 0.114, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_album::case_2_various_artists", + "original_name": "client::music_playlist::tests::map_music_album::case_2_various_artists", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.053+02:00", + "time": 0.064, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_details::tests::map_music_radio::case_2_track", + "original_name": "client::music_details::tests::map_music_radio::case_2_track", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34+02:00", + "time": 0.126, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_artist::tests::map_music_artist_no_cont", + "original_name": "client::music_artist::tests::map_music_artist_no_cont", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.966+02:00", + "time": 0.166, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_album::case_1_one_artist", + "original_name": "client::music_playlist::tests::map_music_album::case_1_one_artist", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.039+02:00", + "time": 0.095, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_details::tests::map_related", + "original_name": "client::music_details::tests::map_related", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.009+02:00", + "time": 0.134, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_album::case_7_two_columns", + "original_name": "client::music_playlist::tests::map_music_album::case_7_two_columns", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.117+02:00", + "time": 0.053, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_album::case_5_unavailable", + "original_name": "client::music_playlist::tests::map_music_album::case_5_unavailable", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.1+02:00", + "time": 0.076, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_album::case_4_description", + "original_name": "client::music_playlist::tests::map_music_album::case_4_description", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.098+02:00", + "time": 0.079, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_album::case_6_unavailable", + "original_name": "client::music_playlist::tests::map_music_album::case_6_unavailable", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.112+02:00", + "time": 0.084, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_artist::tests::map_music_artist::case_4_only_more_singles", + "original_name": "client::music_artist::tests::map_music_artist::case_4_only_more_singles", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.949+02:00", + "time": 0.255, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_playlist::case_3_nomusic", + "original_name": "client::music_playlist::tests::map_music_playlist::case_3_nomusic", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.134+02:00", + "time": 0.092, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_charts::tests::map_music_charts::case_1_default", + "original_name": "client::music_charts::tests::map_music_charts::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.98+02:00", + "time": 0.249, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_artists", + "original_name": "client::music_search::tests::map_music_search_artists", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.177+02:00", + "time": 0.056, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_playlist::case_5_n_album", + "original_name": "client::music_playlist::tests::map_music_playlist::case_5_n_album", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.169+02:00", + "time": 0.078, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_albums", + "original_name": "client::music_search::tests::map_music_search_albums", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.175+02:00", + "time": 0.092, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_main::case_1_default", + "original_name": "client::music_search::tests::map_music_search_main::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.195+02:00", + "time": 0.077, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_main::case_2_typo", + "original_name": "client::music_search::tests::map_music_search_main::case_2_typo", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.204+02:00", + "time": 0.076, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_charts::tests::map_music_charts::case_2_us", + "original_name": "client::music_charts::tests::map_music_charts::case_2_us", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.982+02:00", + "time": 0.3, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_main::case_3_radio", + "original_name": "client::music_search::tests::map_music_search_main::case_3_radio", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.226+02:00", + "time": 0.079, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_new::tests::map_music_new_videos::case_2_default", + "original_name": "client::music_new::tests::map_music_new_videos::case_2_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.026+02:00", + "time": 0.28, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_artist::tests::map_music_artist::case_1_default", + "original_name": "client::music_artist::tests::map_music_artist::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:33.913+02:00", + "time": 0.398, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_main::case_4_artist", + "original_name": "client::music_search::tests::map_music_search_main::case_4_artist", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.229+02:00", + "time": 0.082, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_suggestion::case_2_empty", + "original_name": "client::music_search::tests::map_music_search_suggestion::case_2_empty", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.272+02:00", + "time": 0.039, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_suggestion::case_1_default", + "original_name": "client::music_search::tests::map_music_search_suggestion::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.267+02:00", + "time": 0.063, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_playlists::case_1_ytm", + "original_name": "client::music_search::tests::map_music_search_playlists::case_1_ytm", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.233+02:00", + "time": 0.102, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_playlists::case_2_community", + "original_name": "client::music_search::tests::map_music_search_playlists::case_2_community", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.247+02:00", + "time": 0.089, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_tracks::case_4_no_artist_link", + "original_name": "client::music_search::tests::map_music_search_tracks::case_4_no_artist_link", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.307+02:00", + "time": 0.062, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_tracks::case_2_typo", + "original_name": "client::music_search::tests::map_music_search_tracks::case_2_typo", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.281+02:00", + "time": 0.095, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_music_playlists::case_1_playlist_related", + "original_name": "client::pagination::tests::map_continuation_music_playlists::case_1_playlist_related", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.33+02:00", + "time": 0.059, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_tracks::case_1_default", + "original_name": "client::music_search::tests::map_music_search_tracks::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.28+02:00", + "time": 0.109, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_items::case_1_search", + "original_name": "client::pagination::tests::map_continuation_items::case_1_search", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.311+02:00", + "time": 0.083, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_tracks::case_3_videos", + "original_name": "client::music_search::tests::map_music_search_tracks::case_3_videos", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.305+02:00", + "time": 0.09, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::player::tests::cipher_to_url", + "original_name": "client::player::tests::cipher_to_url", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.395+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_playlists::case_1_channel_playlists", + "original_name": "client::pagination::tests::map_continuation_playlists::case_1_channel_playlists", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.335+02:00", + "time": 0.071, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_genres::tests::map_music_genre::case_2_mood", + "original_name": "client::music_genres::tests::map_music_genre::case_2_mood", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.013+02:00", + "time": 0.401, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_playlist::case_1_short", + "original_name": "client::music_playlist::tests::map_music_playlist::case_1_short", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.126+02:00", + "time": 0.288, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_items::case_3_recommendations", + "original_name": "client::pagination::tests::map_continuation_items::case_3_recommendations", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.312+02:00", + "time": 0.103, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_items::case_2_startpage", + "original_name": "client::pagination::tests::map_continuation_items::case_2_startpage", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.312+02:00", + "time": 0.119, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_tracks::case_2_search_tracks", + "original_name": "client::pagination::tests::map_continuation_tracks::case_2_search_tracks", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.368+02:00", + "time": 0.077, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_genres::tests::map_music_genre::case_1_default", + "original_name": "client::music_genres::tests::map_music_genre::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.011+02:00", + "time": 0.44, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_playlist::case_2_long", + "original_name": "client::music_playlist::tests::map_music_playlist::case_2_long", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.132+02:00", + "time": 0.338, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::player::tests::map_player_data::case_5_ios", + "original_name": "client::player::tests::map_player_data::case_5_ios", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.414+02:00", + "time": 0.059, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_videos::case_1_channel_videos", + "original_name": "client::pagination::tests::map_continuation_videos::case_1_channel_videos", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.389+02:00", + "time": 0.087, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::player::tests::map_player_data::case_1_desktop", + "original_name": "client::player::tests::map_player_data::case_1_desktop", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.395+02:00", + "time": 0.083, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::response::music_item::tests::map_album_type_samples", + "original_name": "client::response::music_item::tests::map_album_type_samples", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.47+02:00", + "time": 0.014, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_playlist::case_4_two_columns", + "original_name": "client::music_playlist::tests::map_music_playlist::case_4_two_columns", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.143+02:00", + "time": 0.344, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_tracks::case_3_radio_tracks", + "original_name": "client::pagination::tests::map_continuation_tracks::case_3_radio_tracks", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.376+02:00", + "time": 0.114, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::player::tests::map_player_data::case_4_android", + "original_name": "client::player::tests::map_player_data::case_4_android", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.413+02:00", + "time": 0.084, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::player::tests::map_player_data::case_2_desktop_music", + "original_name": "client::player::tests::map_player_data::case_2_desktop_music", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.405+02:00", + "time": 0.098, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::player::tests::map_player_data::case_3_tv_html5_embed", + "original_name": "client::player::tests::map_player_data::case_3_tv_html5_embed", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.406+02:00", + "time": 0.108, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::search::tests::t_map_search::case_3_empty", + "original_name": "client::search::tests::t_map_search::case_3_empty", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.478+02:00", + "time": 0.061, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::search::tests::t_map_search::case_4_ab3_channel_handles", + "original_name": "client::search::tests::t_map_search::case_4_ab3_channel_handles", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.484+02:00", + "time": 0.064, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_new::tests::map_music_new_albums::case_1_default", + "original_name": "client::music_new::tests::map_music_new_albums::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.021+02:00", + "time": 0.535, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::search::tests::t_map_search::case_2_playlists", + "original_name": "client::search::tests::t_map_search::case_2_playlists", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.476+02:00", + "time": 0.09, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_videos::case_2_playlist", + "original_name": "client::pagination::tests::map_continuation_videos::case_2_playlist", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.39+02:00", + "time": 0.184, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::playlist::tests::map_playlist_data::case_3_nomusic", + "original_name": "client::playlist::tests::map_playlist_data::case_3_nomusic", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.446+02:00", + "time": 0.153, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_tracks::case_1_playlist_tracks", + "original_name": "client::pagination::tests::map_continuation_tracks::case_1_playlist_tracks", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.336+02:00", + "time": 0.267, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_comments::case_4_frameworkupd_reply", + "original_name": "client::video_details::tests::map_comments::case_4_frameworkupd_reply", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.574+02:00", + "time": 0.054, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::playlist::tests::map_playlist_data::case_1_short", + "original_name": "client::playlist::tests::map_playlist_data::case_1_short", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.415+02:00", + "time": 0.214, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_comments::case_2_latest", + "original_name": "client::video_details::tests::map_comments::case_2_latest", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.555+02:00", + "time": 0.079, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_comments::case_1_top", + "original_name": "client::video_details::tests::map_comments::case_1_top", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.548+02:00", + "time": 0.09, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::search::tests::t_map_search::case_1_default", + "original_name": "client::search::tests::t_map_search::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.473+02:00", + "time": 0.167, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::trends::tests::map_startpage", + "original_name": "client::trends::tests::map_startpage", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.503+02:00", + "time": 0.145, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_comments::case_3_frameworkupd", + "original_name": "client::video_details::tests::map_comments::case_3_frameworkupd", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.566+02:00", + "time": 0.088, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::playlist::tests::map_playlist_data::case_2_long", + "original_name": "client::playlist::tests::map_playlist_data::case_2_long", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.43+02:00", + "time": 0.227, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::trends::tests::map_trending::case_2_page_header_renderer", + "original_name": "client::trends::tests::map_trending::case_2_page_header_renderer", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.539+02:00", + "time": 0.128, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::playlist::tests::map_playlist_data::case_4_live", + "original_name": "client::playlist::tests::map_playlist_data::case_4_live", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.451+02:00", + "time": 0.226, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details_not_found", + "original_name": "client::video_details::tests::map_video_details_not_found", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.668+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_06_agegate", + "original_name": "client::video_details::tests::map_video_details::case_06_agegate", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.638+02:00", + "time": 0.043, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_02_music", + "original_name": "client::video_details::tests::map_video_details::case_02_music", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.603+02:00", + "time": 0.08, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_01_mv", + "original_name": "client::video_details::tests::map_video_details::case_01_mv", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.599+02:00", + "time": 0.086, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_deobfuscate_nsig", + "original_name": "deobfuscate::tests::t_deobfuscate_nsig", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.677+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_deobfuscate_sig", + "original_name": "deobfuscate::tests::t_deobfuscate_sig", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.679+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_extract_js_fn", + "original_name": "deobfuscate::tests::t_extract_js_fn", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.681+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_extract_js_fn_eviljs", + "original_name": "deobfuscate::tests::t_extract_js_fn_eviljs", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.683+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_get_nsig_fn_name", + "original_name": "deobfuscate::tests::t_get_nsig_fn_name", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.686+02:00", + "time": 0.012, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_get_nsig_fn", + "original_name": "deobfuscate::tests::t_get_nsig_fn", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.685+02:00", + "time": 0.014, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_get_sts", + "original_name": "deobfuscate::tests::t_get_sts", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.698+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_03_ccommons", + "original_name": "client::video_details::tests::map_video_details::case_03_ccommons", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.628+02:00", + "time": 0.087, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::tests::t_extract_desktop_client_version", + "original_name": "client::tests::t_extract_desktop_client_version", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.487+02:00", + "time": 0.235, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_05_live", + "original_name": "client::video_details::tests::map_video_details::case_05_live", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.635+02:00", + "time": 0.092, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_07_ab_newdesc", + "original_name": "client::video_details::tests::map_video_details::case_07_ab_newdesc", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.64+02:00", + "time": 0.088, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_08_ab_new_cont", + "original_name": "client::video_details::tests::map_video_details::case_08_ab_new_cont", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.648+02:00", + "time": 0.091, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_04_chapters", + "original_name": "client::video_details::tests::map_video_details::case_04_chapters", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.629+02:00", + "time": 0.114, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::model::richtext::tests::to_html", + "original_name": "model::richtext::tests::to_html", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.709+02:00", + "time": 0.045, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::model::richtext::tests::to_markdown", + "original_name": "model::richtext::tests::to_markdown", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.714+02:00", + "time": 0.04, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_09_ab_no_recommends", + "original_name": "client::video_details::tests::map_video_details::case_09_ab_no_recommends", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.654+02:00", + "time": 0.116, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_10_ab_new_likes", + "original_name": "client::video_details::tests::map_video_details::case_10_ab_new_likes", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.657+02:00", + "time": 0.121, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_03", + "original_name": "param::search_filter::tests::t_filter::case_03", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.739+02:00", + "time": 0.043, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_01", + "original_name": "param::search_filter::tests::t_filter::case_01", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.727+02:00", + "time": 0.056, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_07", + "original_name": "param::search_filter::tests::t_filter::case_07", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.769+02:00", + "time": 0.013, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_06", + "original_name": "param::search_filter::tests::t_filter::case_06", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.755+02:00", + "time": 0.03, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_02", + "original_name": "param::search_filter::tests::t_filter::case_02", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.728+02:00", + "time": 0.056, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_04", + "original_name": "param::search_filter::tests::t_filter::case_04", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.743+02:00", + "time": 0.041, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_05", + "original_name": "param::search_filter::tests::t_filter::case_05", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.754+02:00", + "time": 0.03, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_08", + "original_name": "param::search_filter::tests::t_filter::case_08", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.778+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_10", + "original_name": "param::search_filter::tests::t_filter::case_10", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.783+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_11", + "original_name": "param::search_filter::tests::t_filter::case_11", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.784+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_09", + "original_name": "param::search_filter::tests::t_filter::case_09", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.783+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_13", + "original_name": "param::search_filter::tests::t_filter::case_13", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.785+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_15", + "original_name": "param::search_filter::tests::t_filter::case_15", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.786+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_14", + "original_name": "param::search_filter::tests::t_filter::case_14", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.786+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_12", + "original_name": "param::search_filter::tests::t_filter::case_12", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.785+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_16", + "original_name": "param::search_filter::tests::t_filter::case_16", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.786+02:00", + "time": 0.01, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_17", + "original_name": "param::search_filter::tests::t_filter::case_17", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.791+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_get_sig_fn_name", + "original_name": "deobfuscate::tests::t_get_sig_fn_name", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.69+02:00", + "time": 0.108, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_18", + "original_name": "param::search_filter::tests::t_filter::case_18", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.791+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_20", + "original_name": "param::search_filter::tests::t_filter::case_20", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.793+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_19", + "original_name": "param::search_filter::tests::t_filter::case_19", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.792+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_22", + "original_name": "param::search_filter::tests::t_filter::case_22", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.793+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::tests::t_get_visitor_data", + "original_name": "client::tests::t_get_visitor_data", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.497+02:00", + "time": 0.305, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_21", + "original_name": "param::search_filter::tests::t_filter::case_21", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.793+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_23", + "original_name": "param::search_filter::tests::t_filter::case_23", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.794+02:00", + "time": 0.012, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_get_player_js_url", + "original_name": "deobfuscate::tests::t_get_player_js_url", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.687+02:00", + "time": 0.119, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::model::richtext::tests::to_plaintext", + "original_name": "model::richtext::tests::to_plaintext", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.722+02:00", + "time": 0.089, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_get_sig_fn", + "original_name": "deobfuscate::tests::t_get_sig_fn", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.69+02:00", + "time": 0.122, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::tests::t_extract_music_client_version", + "original_name": "client::tests::t_extract_music_client_version", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.49+02:00", + "time": 0.324, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_1_default", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.796+02:00", + "time": 0.018, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_2_bitrate", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_2_bitrate", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.798+02:00", + "time": 0.02, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_3_m4a_format", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_3_m4a_format", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.798+02:00", + "time": 0.02, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_4_m4a_codec", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_4_m4a_codec", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.799+02:00", + "time": 0.019, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_6_br_fallback", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_6_br_fallback", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.8+02:00", + "time": 0.02, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_5_french", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_5_french", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.8+02:00", + "time": 0.02, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_8_noformat", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_8_noformat", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.802+02:00", + "time": 0.021, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_7_lang_fallback", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_7_lang_fallback", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.801+02:00", + "time": 0.023, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_audio_stream::case_1_default", + "original_name": "param::stream_filter::tests::t_select_video_audio_stream::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.806+02:00", + "time": 0.018, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_9_nocodec", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_9_nocodec", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.805+02:00", + "time": 0.019, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_audio_stream::case_2_webm", + "original_name": "param::stream_filter::tests::t_select_video_audio_stream::case_2_webm", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.807+02:00", + "time": 0.022, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_audio_stream::case_3_noaudio", + "original_name": "param::stream_filter::tests::t_select_video_audio_stream::case_3_noaudio", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.811+02:00", + "time": 0.017, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_audio_stream::case_4_novideo", + "original_name": "param::stream_filter::tests::t_select_video_audio_stream::case_4_novideo", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.812+02:00", + "time": 0.017, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_audio_stream::case_5_noformat", + "original_name": "param::stream_filter::tests::t_select_video_audio_stream::case_5_noformat", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.814+02:00", + "time": 0.016, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_2_hdr", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_2_hdr", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.818+02:00", + "time": 0.015, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_3_resolution", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_3_resolution", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.819+02:00", + "time": 0.014, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_4_resolution_fps", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_4_resolution_fps", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.82+02:00", + "time": 0.014, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_6_webm_format", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_6_webm_format", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.821+02:00", + "time": 0.015, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::tests::t_ignore_any", + "original_name": "serializer::tests::t_ignore_any", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.827+02:00", + "time": 0.01, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_5_res_fallback", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_5_res_fallback", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.82+02:00", + "time": 0.017, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_1_default", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.815+02:00", + "time": 0.022, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_7_vp9_codec", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_7_vp9_codec", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.824+02:00", + "time": 0.019, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_8_noformat", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_8_noformat", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.825+02:00", + "time": 0.018, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_9_nocodec", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_9_nocodec", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.827+02:00", + "time": 0.016, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_deserialize_text::case_2", + "original_name": "serializer::text::tests::t_deserialize_text::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.833+02:00", + "time": 0.01, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_deserialize_text::case_1", + "original_name": "serializer::text::tests::t_deserialize_text::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.831+02:00", + "time": 0.012, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_deserialize_text::case_3", + "original_name": "serializer::text::tests::t_deserialize_text::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.834+02:00", + "time": 0.012, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_deserialize_text::case_4", + "original_name": "serializer::text::tests::t_deserialize_text::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.836+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_links_empty", + "original_name": "serializer::text::tests::t_links_empty", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.844+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::trends::tests::map_trending::case_1_base", + "original_name": "client::trends::tests::map_trending::case_1_base", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.514+02:00", + "time": 0.342, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::split_text_cmp", + "original_name": "serializer::text::tests::split_text_cmp", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.829+02:00", + "time": 0.033, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::parse_language::case_2", + "original_name": "util::tests::parse_language::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.852+02:00", + "time": 0.01, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::protobuf::tests::t_parse_proto", + "original_name": "util::protobuf::tests::t_parse_proto", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.845+02:00", + "time": 0.017, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::styled_comment", + "original_name": "serializer::text::tests::styled_comment", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.829+02:00", + "time": 0.034, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::parse_language::case_1", + "original_name": "util::tests::parse_language::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.849+02:00", + "time": 0.015, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::parse_language::case_3", + "original_name": "util::tests::parse_language::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.855+02:00", + "time": 0.01, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_link_channel", + "original_name": "serializer::text::tests::t_link_channel", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.837+02:00", + "time": 0.033, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::parse_language::case_5", + "original_name": "util::tests::parse_language::case_5", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.862+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::parse_language::case_6", + "original_name": "util::tests::parse_language::case_6", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.863+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::split_words", + "original_name": "util::tests::split_words", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.863+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::split_char", + "original_name": "util::tests::split_char", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.863+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_attributed_description", + "original_name": "serializer::text::tests::t_attributed_description", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.83+02:00", + "time": 0.044, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::parse_language::case_4", + "original_name": "util::tests::parse_language::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.862+02:00", + "time": 0.012, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_link_album", + "original_name": "serializer::text::tests::t_link_album", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.837+02:00", + "time": 0.041, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_link_video", + "original_name": "serializer::text::tests::t_link_video", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.842+02:00", + "time": 0.036, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::vec_log_err::tests::skip_error", + "original_name": "serializer::vec_log_err::tests::skip_error", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.844+02:00", + "time": 0.034, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_links_artists", + "original_name": "serializer::text::tests::t_links_artists", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.844+02:00", + "time": 0.034, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_link_none", + "original_name": "serializer::text::tests::t_link_none", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.838+02:00", + "time": 0.04, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_country_from_name::case_1", + "original_name": "util::tests::t_country_from_name::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.866+02:00", + "time": 0.013, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_country_from_name::case_3", + "original_name": "util::tests::t_country_from_name::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.872+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr::case_1", + "original_name": "util::tests::t_parse_large_numstr::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.872+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_country_from_name::case_2", + "original_name": "util::tests::t_country_from_name::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.87+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr::case_3", + "original_name": "util::tests::t_parse_large_numstr::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.873+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_link_web", + "original_name": "serializer::text::tests::t_link_web", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.843+02:00", + "time": 0.038, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr::case_2", + "original_name": "util::tests::t_parse_large_numstr::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.872+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr::case_4", + "original_name": "util::tests::t_parse_large_numstr::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.874+02:00", + "time": 0.012, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr::case_5", + "original_name": "util::tests::t_parse_large_numstr::case_5", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.877+02:00", + "time": 0.01, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::vec_log_err::tests::log_error", + "original_name": "serializer::vec_log_err::tests::log_error", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.844+02:00", + "time": 0.044, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr::case_7", + "original_name": "util::tests::t_parse_large_numstr::case_7", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.878+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_num::case_2", + "original_name": "util::tests::t_parse_num::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.879+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_num::case_1", + "original_name": "util::tests::t_parse_num::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.879+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_numeric_vec::case_2", + "original_name": "util::tests::t_parse_numeric_vec::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.881+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_numeric_vec::case_1", + "original_name": "util::tests::t_parse_numeric_vec::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.88+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr::case_6", + "original_name": "util::tests::t_parse_large_numstr::case_6", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.878+02:00", + "time": 0.012, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_video_length::case_4", + "original_name": "util::tests::t_parse_video_length::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.884+02:00", + "time": 0.012, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_numeric_vec::case_3", + "original_name": "util::tests::t_parse_numeric_vec::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.881+02:00", + "time": 0.015, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_video_length::case_1", + "original_name": "util::tests::t_parse_video_length::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.882+02:00", + "time": 0.015, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_retry_delay::case_3", + "original_name": "util::tests::t_retry_delay::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.889+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_retry_delay::case_1", + "original_name": "util::tests::t_retry_delay::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.888+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_video_length::case_2", + "original_name": "util::tests::t_parse_video_length::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.882+02:00", + "time": 0.015, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_sanitize_yt_url::case_1", + "original_name": "util::tests::t_sanitize_yt_url::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.89+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_retry_delay::case_2", + "original_name": "util::tests::t_retry_delay::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.888+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_sanitize_yt_url::case_2", + "original_name": "util::tests::t_sanitize_yt_url::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.891+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_retry_delay::case_4", + "original_name": "util::tests::t_retry_delay::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.889+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_video_length::case_3", + "original_name": "util::tests::t_parse_video_length::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.882+02:00", + "time": 0.017, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_video_length::case_6", + "original_name": "util::tests::t_parse_video_length::case_6", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.887+02:00", + "time": 0.013, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_video_length::case_5", + "original_name": "util::tests::t_parse_video_length::case_5", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.886+02:00", + "time": 0.019, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_sanitize_yt_url::case_3", + "original_name": "util::tests::t_sanitize_yt_url::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.892+02:00", + "time": 0.014, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_vec_try_swap_remove", + "original_name": "util::tests::t_vec_try_swap_remove", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.898+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_sanitize_yt_url::case_4", + "original_name": "util::tests::t_sanitize_yt_url::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.897+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_vec_try_remove", + "original_name": "util::tests::t_vec_try_remove", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.897+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse::case_1_de", + "original_name": "util::timeago::tests::t_parse::case_1_de", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.898+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse::case_3_nbsp", + "original_name": "util::timeago::tests::t_parse::case_3_nbsp", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.899+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse::case_2_ar", + "original_name": "util::timeago::tests::t_parse::case_2_ar", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.898+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_date::case_2", + "original_name": "util::timeago::tests::t_parse_date::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.9+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_date::case_4", + "original_name": "util::timeago::tests::t_parse_date::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.901+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_date::case_3", + "original_name": "util::timeago::tests::t_parse_date::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.901+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_date::case_1", + "original_name": "util::timeago::tests::t_parse_date::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.9+02:00", + "time": 0.01, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_date::case_5", + "original_name": "util::timeago::tests::t_parse_date::case_5", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.901+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_date::case_6", + "original_name": "util::timeago::tests::t_parse_date::case_6", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.905+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_video_duration2::case_1", + "original_name": "util::timeago::tests::t_parse_video_duration2::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.907+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_video_duration2::case_4", + "original_name": "util::timeago::tests::t_parse_video_duration2::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.908+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_video_duration2::case_3", + "original_name": "util::timeago::tests::t_parse_video_duration2::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.907+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_video_duration2::case_5", + "original_name": "util::timeago::tests::t_parse_video_duration2::case_5", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.908+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_to_datetime", + "original_name": "util::timeago::tests::t_to_datetime", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.911+02:00", + "time": 0.006, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_video_duration2::case_2", + "original_name": "util::timeago::tests::t_parse_video_duration2::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.907+02:00", + "time": 0.01, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_date_samples", + "original_name": "util::timeago::tests::t_parse_date_samples", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.906+02:00", + "time": 0.024, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_timeago_table", + "original_name": "util::timeago::tests::t_timeago_table", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.91+02:00", + "time": 0.024, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_testfile_short", + "original_name": "util::timeago::tests::t_testfile_short", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.91+02:00", + "time": 0.028, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_testfile", + "original_name": "util::timeago::tests::t_testfile", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.91+02:00", + "time": 0.033, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_video_duration", + "original_name": "util::timeago::tests::t_parse_video_duration", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.906+02:00", + "time": 0.073, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr_samples", + "original_name": "util::tests::t_parse_large_numstr_samples", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.879+02:00", + "time": 0.248, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_update", + "original_name": "deobfuscate::tests::t_update", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:31:34.699+02:00", + "time": 1.823, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + } + ], + "time": 0.0, + "tests": 248, + "errors": 0, + "failures": 0, + "skipped": 0, + "flaky": 0 + }, + { + "name": "rustypipe::youtube", + "timestamp": null, + "cases": [ + { + "name": "rustypipe::youtube::channel_more::case_1_artist", + "original_name": "channel_more::case_1_artist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:34.915+02:00", + "time": 0.117, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_not_found::case_1_not_exist", + "original_name": "channel_not_found::case_1_not_exist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:34.923+02:00", + "time": 0.132, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_not_found::case_2_gaming", + "original_name": "channel_not_found::case_2_gaming", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:34.93+02:00", + "time": 0.134, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_not_found::case_3_movies", + "original_name": "channel_not_found::case_3_movies", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:34.934+02:00", + "time": 0.136, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_info", + "original_name": "channel_info", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:34.914+02:00", + "time": 0.159, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_more::case_4_music", + "original_name": "channel_more::case_4_music", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:34.917+02:00", + "time": 0.158, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_age_restriction", + "original_name": "channel_age_restriction", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:34.912+02:00", + "time": 0.194, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_livestreams", + "original_name": "channel_livestreams", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:34.914+02:00", + "time": 0.326, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_more::case_3_livestream", + "original_name": "channel_more::case_3_livestream", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:34.916+02:00", + "time": 0.371, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_latest::case_3_shorts", + "original_name": "channel_order_latest::case_3_shorts", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.065+02:00", + "time": 0.252, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_more::case_2_shorts", + "original_name": "channel_more::case_2_shorts", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:34.915+02:00", + "time": 0.413, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_not_found::case_6_live", + "original_name": "channel_not_found::case_6_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:34.98+02:00", + "time": 0.381, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_not_found::case_5_learning", + "original_name": "channel_not_found::case_5_learning", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:34.942+02:00", + "time": 0.452, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_popular::case_3_shorts", + "original_name": "channel_order_popular::case_3_shorts", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.127+02:00", + "time": 0.293, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_latest::case_1_videos", + "original_name": "channel_order_latest::case_1_videos", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.032+02:00", + "time": 0.396, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_oldest::case_1_videos", + "original_name": "channel_order_oldest::case_1_videos", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.07+02:00", + "time": 0.423, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_oldest::case_2_live", + "original_name": "channel_order_oldest::case_2_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.073+02:00", + "time": 0.436, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::ab3_search_channel_handles", + "original_name": "ab3_search_channel_handles", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:34.911+02:00", + "time": 0.608, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_tab_not_found::case_2_live", + "original_name": "channel_tab_not_found::case_2_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.36+02:00", + "time": 0.164, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_not_found::case_4_sports", + "original_name": "channel_not_found::case_4_sports", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:34.938+02:00", + "time": 0.598, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_latest::case_2_live", + "original_name": "channel_order_latest::case_2_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.055+02:00", + "time": 0.484, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_popular::case_1_videos", + "original_name": "channel_order_popular::case_1_videos", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.075+02:00", + "time": 0.474, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_tab_not_found::case_1_shorts", + "original_name": "channel_tab_not_found::case_1_shorts", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.328+02:00", + "time": 0.221, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_shorts", + "original_name": "channel_shorts", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.317+02:00", + "time": 0.237, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player::case_1_music", + "original_name": "get_player::case_1_music", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.42+02:00", + "time": 0.172, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_popular::case_2_live", + "original_name": "channel_order_popular::case_2_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.107+02:00", + "time": 0.489, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player::case_2_hdr", + "original_name": "get_player::case_2_hdr", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.428+02:00", + "time": 0.203, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_1_not_found", + "original_name": "get_player_error::case_1_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.536+02:00", + "time": 0.123, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_2_deleted", + "original_name": "get_player_error::case_2_deleted", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.539+02:00", + "time": 0.128, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player::case_4_live", + "original_name": "get_player::case_4_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.509+02:00", + "time": 0.161, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_3_censored", + "original_name": "get_player_error::case_3_censored", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.549+02:00", + "time": 0.128, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_playlists", + "original_name": "channel_playlists", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.24+02:00", + "time": 0.466, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player::case_3_multilanguage", + "original_name": "get_player::case_3_multilanguage", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.493+02:00", + "time": 0.221, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player::case_5_was_live", + "original_name": "get_player::case_5_was_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.52+02:00", + "time": 0.197, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_5_private", + "original_name": "get_player_error::case_5_private", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.554+02:00", + "time": 0.165, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_4_geoblock", + "original_name": "get_player_error::case_4_geoblock", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.55+02:00", + "time": 0.189, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_8_members_only", + "original_name": "get_player_error::case_8_members_only", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.631+02:00", + "time": 0.148, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_7_premium_only", + "original_name": "get_player_error::case_7_premium_only", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.596+02:00", + "time": 0.22, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player::case_6_agelimit", + "original_name": "get_player::case_6_agelimit", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.524+02:00", + "time": 0.299, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_6_age_restricted", + "original_name": "get_player_error::case_6_age_restricted", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.592+02:00", + "time": 0.25, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error_paid", + "original_name": "get_player_error_paid", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.659+02:00", + "time": 0.228, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_videos", + "original_name": "channel_videos", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.394+02:00", + "time": 0.511, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_playlist::case_2_nomusic", + "original_name": "get_playlist::case_2_nomusic", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.717+02:00", + "time": 0.212, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_from_client::case_3_android", + "original_name": "get_player_from_client::case_3_android", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.677+02:00", + "time": 0.382, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_from_client::case_2_tv_html5_embed", + "original_name": "get_player_from_client::case_2_tv_html5_embed", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.67+02:00", + "time": 0.412, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_from_client::case_1_desktop", + "original_name": "get_player_from_client::case_1_desktop", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.667+02:00", + "time": 0.5, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details_not_found", + "original_name": "get_video_details_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.06+02:00", + "time": 0.114, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::invalid_ctoken::case_1_desktop", + "original_name": "invalid_ctoken::case_1_desktop", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.082+02:00", + "time": 0.1, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_playlist::case_3_live", + "original_name": "get_playlist::case_3_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.72+02:00", + "time": 0.48, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_playlist::case_1_long", + "original_name": "get_playlist::case_1_long", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.714+02:00", + "time": 0.516, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::invalid_ctoken::case_2_music", + "original_name": "invalid_ctoken::case_2_music", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.167+02:00", + "time": 0.122, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_from_client::case_4_ios", + "original_name": "get_player_from_client::case_4_ios", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.706+02:00", + "time": 0.206, + "status": { + "Failure": { + "message": "", + "text": "thread 'get_player_from_client::case_4_ios' panicked at\n tests/youtube.rs:71:14:\n video #247\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\n running 1 test\n test get_player_from_client::case_4_ios ... FAILED\n\n failures:\n\n failures:\n get_player_from_client::case_4_ios\n\n test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished\n in 0.20s\n\n ", + "system_err": "thread 'get_player_from_client::case_4_ios' panicked at\n tests/youtube.rs:71:14:\n video #247\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n", + "retries": [ + { + "timestamp": "2024-05-31T22:31:35.914+02:00", + "time": 0.17, + "status": { + "Failure": { + "message": "", + "text": "thread\n 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14: video\n #247 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\n running 1 test\n test get_player_from_client::case_4_ios ... FAILED\n\n failures:\n\n failures:\n get_player_from_client::case_4_ios\n\n test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished\n in 0.16s\n\n ", + "system_err": "thread\n 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14:\n video #247\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n" + }, + { + "timestamp": "2024-05-31T22:31:36.085+02:00", + "time": 0.168, + "status": { + "Failure": { + "message": "", + "text": "thread\n 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14: video\n #247 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\n running 1 test\n test get_player_from_client::case_4_ios ... FAILED\n\n failures:\n\n failures:\n get_player_from_client::case_4_ios\n\n test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished\n in 0.16s\n\n ", + "system_err": "thread\n 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14:\n video #247\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n" + }, + { + "timestamp": "2024-05-31T22:31:36.254+02:00", + "time": 0.176, + "status": { + "Failure": { + "message": "", + "text": "thread\n 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14: video\n #247 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\n running 1 test\n test get_player_from_client::case_4_ios ... FAILED\n\n failures:\n\n failures:\n get_player_from_client::case_4_ios\n\n test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished\n in 0.17s\n\n ", + "system_err": "thread\n 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14:\n video #247\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n" + } + ] + }, + { + "name": "rustypipe::youtube::music_album::case_3_single", + "original_name": "music_album::case_3_single", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.199+02:00", + "time": 0.306, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details_no_desc", + "original_name": "get_video_details_no_desc", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.929+02:00", + "time": 0.633, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_1_one_artist", + "original_name": "music_album::case_1_one_artist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.174+02:00", + "time": 0.402, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_5_show", + "original_name": "music_album::case_5_show", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.289+02:00", + "time": 0.305, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_4_ep", + "original_name": "music_album::case_4_ep", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.23+02:00", + "time": 0.382, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_2_various_artists", + "original_name": "music_album::case_2_various_artists", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.182+02:00", + "time": 0.431, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_search", + "original_name": "channel_search", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.287+02:00", + "time": 1.349, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_7_no_year", + "original_name": "music_album::case_7_no_year", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.506+02:00", + "time": 0.19, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album_not_found", + "original_name": "music_album_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.575+02:00", + "time": 0.132, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details_agegate", + "original_name": "get_video_details_agegate", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.816+02:00", + "time": 0.96, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_artist_albums_not_found", + "original_name": "music_artist_albums_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.708+02:00", + "time": 0.115, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_artist::case_4_no_artist", + "original_name": "music_artist::case_4_no_artist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.636+02:00", + "time": 0.21, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_artist_not_found", + "original_name": "music_artist_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.776+02:00", + "time": 0.149, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_artist::case_2_no_more_albums", + "original_name": "music_artist::case_2_no_more_albums", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.612+02:00", + "time": 0.401, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_6_unavailable", + "original_name": "music_album::case_6_unavailable", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.43+02:00", + "time": 0.598, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_artist::case_3_only_singles", + "original_name": "music_artist::case_3_only_singles", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.613+02:00", + "time": 0.417, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details_ccommons", + "original_name": "get_video_details_ccommons", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.823+02:00", + "time": 1.215, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_details::case_1_mv", + "original_name": "music_details::case_1_mv", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.925+02:00", + "time": 0.174, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_details_not_found", + "original_name": "music_details_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.03+02:00", + "time": 0.093, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_charts::case_1_de", + "original_name": "music_charts::case_1_de", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.823+02:00", + "time": 0.343, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_details::case_2_track", + "original_name": "music_details::case_2_track", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.013+02:00", + "time": 0.179, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_artist::case_1_basic_all", + "original_name": "music_artist::case_1_basic_all", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.595+02:00", + "time": 0.614, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_genre_not_found", + "original_name": "music_genre_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.122+02:00", + "time": 0.128, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_details::case_3_track_details", + "original_name": "music_details::case_3_track_details", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.028+02:00", + "time": 0.232, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_genres", + "original_name": "music_genres", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.167+02:00", + "time": 0.145, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details_music", + "original_name": "get_video_details_music", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.905+02:00", + "time": 1.421, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details_chapters", + "original_name": "get_video_details_chapters", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.842+02:00", + "time": 1.519, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_lyrics", + "original_name": "music_lyrics", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.192+02:00", + "time": 0.248, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_lyrics_not_found", + "original_name": "music_lyrics_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.209+02:00", + "time": 0.242, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details_live", + "original_name": "get_video_details_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.886+02:00", + "time": 1.579, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_charts::case_2_us", + "original_name": "music_charts::case_2_us", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.184+02:00", + "time": 0.316, + "status": "Flaky", + "system_out": null, + "system_err": null, + "retries": [ + { + "timestamp": "2024-05-31T22:31:36.846+02:00", + "time": 0.337, + "status": { + "Failure": { + "message": "", + "text": "thread\n 'music_charts::case_2_us' panicked at tests/youtube.rs:2494:9: expected >= 8\n charts playlists, got 0 note: run with `RUST_BACKTRACE=1` environment variable to display a\n backtrace" + } + }, + "system_out": "\n running 1 test\n test music_charts::case_2_us ... FAILED\n\n failures:\n\n failures:\n music_charts::case_2_us\n\n test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished\n in 0.33s\n\n ", + "system_err": "thread\n 'music_charts::case_2_us' panicked at tests/youtube.rs:2494:9:\n expected >= 8 charts playlists, got 0\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n" + } + ] + }, + { + "name": "rustypipe::youtube::get_video_comments", + "original_name": "get_video_comments", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.739+02:00", + "time": 1.769, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_artist::case_5_secondary_channel", + "original_name": "music_artist::case_5_secondary_channel", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.696+02:00", + "time": 0.825, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_playlist::case_3_nomusic", + "original_name": "music_playlist::case_3_nomusic", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.361+02:00", + "time": 0.218, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_playlist_not_found", + "original_name": "music_playlist_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.465+02:00", + "time": 0.134, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_new_videos", + "original_name": "music_new_videos", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.26+02:00", + "time": 0.39, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_8_version_no_artist", + "original_name": "music_album::case_8_version_no_artist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.522+02:00", + "time": 0.308, + "status": { + "Failure": { + "message": "", + "text": "thread 'music_album::case_8_version_no_artist' panicked\n at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_version_no_artist' failed in line 1593\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\n running 1 test\n โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n Snapshot file: tests/snapshots/youtube__music_album_version_no_artist.snap\n Snapshot: music_album_version_no_artist\n Source: tests/youtube.rs:1593\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n Expression: album\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n -old snapshot\n +new results\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n 122 122 โ”‚ width: 544,\n 123 123 โ”‚ height: 544,\n 124 124 โ”‚ ),\n 125 125 โ”‚ ],\n 126 โ”‚- artists: [],\n 127 โ”‚- artist_id: None,\n 126 โ”‚+ artists: [\n 127 โ”‚+ ArtistId(\n 128 โ”‚+ id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 129 โ”‚+ name: \"S.p. Kodandapani\",\n 130 โ”‚+ ),\n 131 โ”‚+ ArtistId(\n 132 โ”‚+ id: Some(\"UCbeyiCEAJt5buhxxLAPwVMg\"),\n 133 โ”‚+ name: \"S. P. Kodandapani\",\n 134 โ”‚+ ),\n 135 โ”‚+ ],\n 136 โ”‚+ artist_id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 128 137 โ”‚ album_type: Ep,\n 129 138 โ”‚ year: None,\n 130 โ”‚- by_va: true,\n 139 โ”‚+ by_va: false,\n 131 140 โ”‚ ),\n 132 141 โ”‚ ],\n 133 142 โ”‚ )\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n To update snapshots run `cargo insta review`\n Stopped on the first failure. Run `cargo insta test` to run all snapshots.\n test music_album::case_8_version_no_artist ... FAILED\n\n failures:\n\n failures:\n music_album::case_8_version_no_artist\n\n test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished\n in 0.30s\n\n ", + "system_err": "stored new snapshot\n /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_version_no_artist.snap.new\n thread 'music_album::case_8_version_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_version_no_artist' failed in line 1593\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n", + "retries": [ + { + "timestamp": "2024-05-31T22:31:36.83+02:00", + "time": 0.33, + "status": { + "Failure": { + "message": "", + "text": "thread\n 'music_album::case_8_version_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_version_no_artist' failed in line 1593 note:\n run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\n running 1 test\n โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n Snapshot file: tests/snapshots/youtube__music_album_version_no_artist.snap\n Snapshot: music_album_version_no_artist\n Source: tests/youtube.rs:1593\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n Expression: album\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n -old snapshot\n +new results\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n 122 122 โ”‚ width: 544,\n 123 123 โ”‚ height: 544,\n 124 124 โ”‚ ),\n 125 125 โ”‚ ],\n 126 โ”‚- artists: [],\n 127 โ”‚- artist_id: None,\n 126 โ”‚+ artists: [\n 127 โ”‚+ ArtistId(\n 128 โ”‚+ id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 129 โ”‚+ name: \"S.p. Kodandapani\",\n 130 โ”‚+ ),\n 131 โ”‚+ ArtistId(\n 132 โ”‚+ id: Some(\"UCbeyiCEAJt5buhxxLAPwVMg\"),\n 133 โ”‚+ name: \"S. P. Kodandapani\",\n 134 โ”‚+ ),\n 135 โ”‚+ ],\n 136 โ”‚+ artist_id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 128 137 โ”‚ album_type: Ep,\n 129 138 โ”‚ year: None,\n 130 โ”‚- by_va: true,\n 139 โ”‚+ by_va: false,\n 131 140 โ”‚ ),\n 132 141 โ”‚ ],\n 133 142 โ”‚ )\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n To update snapshots run `cargo insta review`\n Stopped on the first failure. Run `cargo insta test` to run all snapshots.\n test music_album::case_8_version_no_artist ... FAILED\n\n failures:\n\n failures:\n music_album::case_8_version_no_artist\n\n test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished\n in 0.32s\n\n ", + "system_err": "stored\n new snapshot\n /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_version_no_artist.snap.new\n thread 'music_album::case_8_version_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_version_no_artist' failed in line 1593\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n" + }, + { + "timestamp": "2024-05-31T22:31:37.162+02:00", + "time": 0.214, + "status": { + "Failure": { + "message": "", + "text": "thread\n 'music_album::case_8_version_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_version_no_artist' failed in line 1593 note:\n run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\n running 1 test\n โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n Snapshot file: tests/snapshots/youtube__music_album_version_no_artist.snap\n Snapshot: music_album_version_no_artist\n Source: tests/youtube.rs:1593\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n Expression: album\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n -old snapshot\n +new results\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n 122 122 โ”‚ width: 544,\n 123 123 โ”‚ height: 544,\n 124 124 โ”‚ ),\n 125 125 โ”‚ ],\n 126 โ”‚- artists: [],\n 127 โ”‚- artist_id: None,\n 126 โ”‚+ artists: [\n 127 โ”‚+ ArtistId(\n 128 โ”‚+ id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 129 โ”‚+ name: \"S.p. Kodandapani\",\n 130 โ”‚+ ),\n 131 โ”‚+ ArtistId(\n 132 โ”‚+ id: Some(\"UCbeyiCEAJt5buhxxLAPwVMg\"),\n 133 โ”‚+ name: \"S. P. Kodandapani\",\n 134 โ”‚+ ),\n 135 โ”‚+ ],\n 136 โ”‚+ artist_id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 128 137 โ”‚ album_type: Ep,\n 129 138 โ”‚ year: None,\n 130 โ”‚- by_va: true,\n 139 โ”‚+ by_va: false,\n 131 140 โ”‚ ),\n 132 141 โ”‚ ],\n 133 142 โ”‚ )\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n To update snapshots run `cargo insta review`\n Stopped on the first failure. Run `cargo insta test` to run all snapshots.\n test music_album::case_8_version_no_artist ... FAILED\n\n failures:\n\n failures:\n music_album::case_8_version_no_artist\n\n test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished\n in 0.21s\n\n ", + "system_err": "stored\n new snapshot\n /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_version_no_artist.snap.new\n thread 'music_album::case_8_version_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_version_no_artist' failed in line 1593\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n" + }, + { + "timestamp": "2024-05-31T22:31:37.377+02:00", + "time": 0.276, + "status": { + "Failure": { + "message": "", + "text": "thread\n 'music_album::case_8_version_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_version_no_artist' failed in line 1593 note:\n run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\n running 1 test\n โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n Snapshot file: tests/snapshots/youtube__music_album_version_no_artist.snap\n Snapshot: music_album_version_no_artist\n Source: tests/youtube.rs:1593\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n Expression: album\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n -old snapshot\n +new results\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n 122 122 โ”‚ width: 544,\n 123 123 โ”‚ height: 544,\n 124 124 โ”‚ ),\n 125 125 โ”‚ ],\n 126 โ”‚- artists: [],\n 127 โ”‚- artist_id: None,\n 126 โ”‚+ artists: [\n 127 โ”‚+ ArtistId(\n 128 โ”‚+ id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 129 โ”‚+ name: \"S.p. Kodandapani\",\n 130 โ”‚+ ),\n 131 โ”‚+ ArtistId(\n 132 โ”‚+ id: Some(\"UCbeyiCEAJt5buhxxLAPwVMg\"),\n 133 โ”‚+ name: \"S. P. Kodandapani\",\n 134 โ”‚+ ),\n 135 โ”‚+ ],\n 136 โ”‚+ artist_id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 128 137 โ”‚ album_type: Ep,\n 129 138 โ”‚ year: None,\n 130 โ”‚- by_va: true,\n 139 โ”‚+ by_va: false,\n 131 140 โ”‚ ),\n 132 141 โ”‚ ],\n 133 142 โ”‚ )\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n To update snapshots run `cargo insta review`\n Stopped on the first failure. Run `cargo insta test` to run all snapshots.\n test music_album::case_8_version_no_artist ... FAILED\n\n failures:\n\n failures:\n music_album::case_8_version_no_artist\n\n test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished\n in 0.27s\n\n ", + "system_err": "stored\n new snapshot\n /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_version_no_artist.snap.new\n thread 'music_album::case_8_version_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_version_no_artist' failed in line 1593\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n" + } + ] + }, + { + "name": "rustypipe::youtube::music_radio_not_found", + "original_name": "music_radio_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.521+02:00", + "time": 0.143, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details", + "original_name": "get_video_details", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:35.779+02:00", + "time": 1.891, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_playlist::case_2_short", + "original_name": "music_playlist::case_2_short", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.326+02:00", + "time": 0.346, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_new_albums", + "original_name": "music_new_albums", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.25+02:00", + "time": 0.433, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_9_no_artist", + "original_name": "music_album::case_9_no_artist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:36.562+02:00", + "time": 0.306, + "status": { + "Failure": { + "message": "", + "text": "thread 'music_album::case_9_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_no_artist' failed in line 1593\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\n running 1 test\n โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n Snapshot file: tests/snapshots/youtube__music_album_no_artist.snap\n Snapshot: music_album_no_artist\n Source: tests/youtube.rs:1593\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n Expression: album\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n -old snapshot\n +new results\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n 1 1 โ”‚ id: \"MPREb_bqWA6mAZFWS\",\n 2 2 โ”‚ playlist_id: Some(\"OLAK5uy_mUiRbMqeQXFUH6h9KB87RcEmNtm45Qvs0\"),\n 3 3 โ”‚ name: \"Pedha Rasi Peddamma Katha\",\n 4 4 โ”‚ cover: \"[cover]\",\n 5 โ”‚- artists: [],\n 6 โ”‚- artist_id: None,\n 5 โ”‚+ artists: [\n 6 โ”‚+ ArtistId(\n 7 โ”‚+ id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 8 โ”‚+ name: \"S.p. Kodandapani\",\n 9 โ”‚+ ),\n 10 โ”‚+ ArtistId(\n 11 โ”‚+ id: Some(\"UCbeyiCEAJt5buhxxLAPwVMg\"),\n 12 โ”‚+ name: \"S. P. Kodandapani\",\n 13 โ”‚+ ),\n 14 โ”‚+ ],\n 15 โ”‚+ artist_id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 7 16 โ”‚ description: None,\n 8 17 โ”‚ album_type: Ep,\n 9 18 โ”‚ year: Some(1968),\n 10 19 โ”‚ by_va: false,\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n To update snapshots run `cargo insta review`\n Stopped on the first failure. Run `cargo insta test` to run all snapshots.\n test music_album::case_9_no_artist ... FAILED\n\n failures:\n\n failures:\n music_album::case_9_no_artist\n\n test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished\n in 0.30s\n\n ", + "system_err": "stored new snapshot\n /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_no_artist.snap.new\n thread 'music_album::case_9_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_no_artist' failed in line 1593\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n", + "retries": [ + { + "timestamp": "2024-05-31T22:31:36.869+02:00", + "time": 0.257, + "status": { + "Failure": { + "message": "", + "text": "thread\n 'music_album::case_9_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_no_artist' failed in line 1593 note: run with\n `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\n running 1 test\n โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n Snapshot file: tests/snapshots/youtube__music_album_no_artist.snap\n Snapshot: music_album_no_artist\n Source: tests/youtube.rs:1593\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n Expression: album\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n -old snapshot\n +new results\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n 1 1 โ”‚ id: \"MPREb_bqWA6mAZFWS\",\n 2 2 โ”‚ playlist_id: Some(\"OLAK5uy_mUiRbMqeQXFUH6h9KB87RcEmNtm45Qvs0\"),\n 3 3 โ”‚ name: \"Pedha Rasi Peddamma Katha\",\n 4 4 โ”‚ cover: \"[cover]\",\n 5 โ”‚- artists: [],\n 6 โ”‚- artist_id: None,\n 5 โ”‚+ artists: [\n 6 โ”‚+ ArtistId(\n 7 โ”‚+ id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 8 โ”‚+ name: \"S.p. Kodandapani\",\n 9 โ”‚+ ),\n 10 โ”‚+ ArtistId(\n 11 โ”‚+ id: Some(\"UCbeyiCEAJt5buhxxLAPwVMg\"),\n 12 โ”‚+ name: \"S. P. Kodandapani\",\n 13 โ”‚+ ),\n 14 โ”‚+ ],\n 15 โ”‚+ artist_id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 7 16 โ”‚ description: None,\n 8 17 โ”‚ album_type: Ep,\n 9 18 โ”‚ year: Some(1968),\n 10 19 โ”‚ by_va: false,\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n To update snapshots run `cargo insta review`\n Stopped on the first failure. Run `cargo insta test` to run all snapshots.\n test music_album::case_9_no_artist ... FAILED\n\n failures:\n\n failures:\n music_album::case_9_no_artist\n\n test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished\n in 0.25s\n\n ", + "system_err": "stored\n new snapshot\n /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_no_artist.snap.new\n thread 'music_album::case_9_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_no_artist' failed in line 1593\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n" + }, + { + "timestamp": "2024-05-31T22:31:37.127+02:00", + "time": 0.26, + "status": { + "Failure": { + "message": "", + "text": "thread\n 'music_album::case_9_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_no_artist' failed in line 1593 note: run with\n `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\n running 1 test\n โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n Snapshot file: tests/snapshots/youtube__music_album_no_artist.snap\n Snapshot: music_album_no_artist\n Source: tests/youtube.rs:1593\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n Expression: album\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n -old snapshot\n +new results\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n 1 1 โ”‚ id: \"MPREb_bqWA6mAZFWS\",\n 2 2 โ”‚ playlist_id: Some(\"OLAK5uy_mUiRbMqeQXFUH6h9KB87RcEmNtm45Qvs0\"),\n 3 3 โ”‚ name: \"Pedha Rasi Peddamma Katha\",\n 4 4 โ”‚ cover: \"[cover]\",\n 5 โ”‚- artists: [],\n 6 โ”‚- artist_id: None,\n 5 โ”‚+ artists: [\n 6 โ”‚+ ArtistId(\n 7 โ”‚+ id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 8 โ”‚+ name: \"S.p. Kodandapani\",\n 9 โ”‚+ ),\n 10 โ”‚+ ArtistId(\n 11 โ”‚+ id: Some(\"UCbeyiCEAJt5buhxxLAPwVMg\"),\n 12 โ”‚+ name: \"S. P. Kodandapani\",\n 13 โ”‚+ ),\n 14 โ”‚+ ],\n 15 โ”‚+ artist_id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 7 16 โ”‚ description: None,\n 8 17 โ”‚ album_type: Ep,\n 9 18 โ”‚ year: Some(1968),\n 10 19 โ”‚ by_va: false,\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n To update snapshots run `cargo insta review`\n Stopped on the first failure. Run `cargo insta test` to run all snapshots.\n test music_album::case_9_no_artist ... FAILED\n\n failures:\n\n failures:\n music_album::case_9_no_artist\n\n test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished\n in 0.25s\n\n ", + "system_err": "stored\n new snapshot\n /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_no_artist.snap.new\n thread 'music_album::case_9_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_no_artist' failed in line 1593\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n" + }, + { + "timestamp": "2024-05-31T22:31:37.388+02:00", + "time": 0.308, + "status": { + "Failure": { + "message": "", + "text": "thread\n 'music_album::case_9_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_no_artist' failed in line 1593 note: run with\n `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\n running 1 test\n โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\n Snapshot file: tests/snapshots/youtube__music_album_no_artist.snap\n Snapshot: music_album_no_artist\n Source: tests/youtube.rs:1593\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n Expression: album\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n -old snapshot\n +new results\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n 1 1 โ”‚ id: \"MPREb_bqWA6mAZFWS\",\n 2 2 โ”‚ playlist_id: Some(\"OLAK5uy_mUiRbMqeQXFUH6h9KB87RcEmNtm45Qvs0\"),\n 3 3 โ”‚ name: \"Pedha Rasi Peddamma Katha\",\n 4 4 โ”‚ cover: \"[cover]\",\n 5 โ”‚- artists: [],\n 6 โ”‚- artist_id: None,\n 5 โ”‚+ artists: [\n 6 โ”‚+ ArtistId(\n 7 โ”‚+ id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 8 โ”‚+ name: \"S.p. Kodandapani\",\n 9 โ”‚+ ),\n 10 โ”‚+ ArtistId(\n 11 โ”‚+ id: Some(\"UCbeyiCEAJt5buhxxLAPwVMg\"),\n 12 โ”‚+ name: \"S. P. Kodandapani\",\n 13 โ”‚+ ),\n 14 โ”‚+ ],\n 15 โ”‚+ artist_id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 7 16 โ”‚ description: None,\n 8 17 โ”‚ album_type: Ep,\n 9 18 โ”‚ year: Some(1968),\n 10 19 โ”‚ by_va: false,\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n To update snapshots run `cargo insta review`\n Stopped on the first failure. Run `cargo insta test` to run all snapshots.\n test music_album::case_9_no_artist ... FAILED\n\n failures:\n\n failures:\n music_album::case_9_no_artist\n\n test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished\n in 0.30s\n\n ", + "system_err": "stored\n new snapshot\n /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_no_artist.snap.new\n thread 'music_album::case_9_no_artist' panicked at\n /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\n snapshot assertion for 'music_album_no_artist' failed in line 1593\n note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n" + } + ] + }, + { + "name": "rustypipe::youtube::music_radio_track_not_found", + "original_name": "music_radio_track_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.653+02:00", + "time": 0.135, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_playlist::case_1_long", + "original_name": "music_playlist::case_1_long", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.311+02:00", + "time": 0.586, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_related::case_1_a", + "original_name": "music_related::case_1_a", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.663+02:00", + "time": 0.435, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_playlist_cont::case_2_ytm", + "original_name": "music_playlist_cont::case_2_ytm", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.45+02:00", + "time": 0.685, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_related::case_2_b", + "original_name": "music_related::case_2_b", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.67+02:00", + "time": 0.467, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_radio_playlist_not_found", + "original_name": "music_radio_playlist_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.6+02:00", + "time": 0.546, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_playlist_related", + "original_name": "music_playlist_related", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.5+02:00", + "time": 0.721, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_albums::case_2_ep", + "original_name": "music_search_albums::case_2_ep", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.683+02:00", + "time": 0.55, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_albums::case_1_single", + "original_name": "music_search_albums::case_1_single", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.672+02:00", + "time": 0.564, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_playlist_cont::case_1_user", + "original_name": "music_playlist_cont::case_1_user", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.439+02:00", + "time": 0.799, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_artists", + "original_name": "music_search_artists", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.788+02:00", + "time": 0.473, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_radio_artist", + "original_name": "music_radio_artist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.508+02:00", + "time": 0.833, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_suggestion::case_1_default", + "original_name": "music_search_suggestion::case_1_default", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.261+02:00", + "time": 0.122, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_radio_playlist", + "original_name": "music_radio_playlist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.579+02:00", + "time": 0.848, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_radio_track", + "original_name": "music_radio_track", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.65+02:00", + "time": 0.842, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_suggestion::case_2_empty", + "original_name": "music_search_suggestion::case_2_empty", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.341+02:00", + "time": 0.184, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_episode::case_1_main", + "original_name": "music_search_episode::case_1_main", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.099+02:00", + "time": 0.447, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::playlist_not_found", + "original_name": "playlist_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.545+02:00", + "time": 0.096, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_playlists", + "original_name": "music_search_playlists", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.236+02:00", + "time": 0.41, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_episode::case_2_videos", + "original_name": "music_search_episode::case_2_videos", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.136+02:00", + "time": 0.542, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_channel_not_found", + "original_name": "resolve_channel_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.642+02:00", + "time": 0.094, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_01", + "original_name": "resolve_string::case_01", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.646+02:00", + "time": 0.09, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_03", + "original_name": "resolve_string::case_03", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.735+02:00", + "time": 0.027, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_playlists_community", + "original_name": "music_search_playlists_community", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.239+02:00", + "time": 0.538, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_02", + "original_name": "resolve_string::case_02", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.678+02:00", + "time": 0.101, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_main::case_1_default", + "original_name": "music_search_main::case_1_default", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.221+02:00", + "time": 0.58, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_07", + "original_name": "resolve_string::case_07", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.779+02:00", + "time": 0.025, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_main::case_2_typo", + "original_name": "music_search_main::case_2_typo", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.233+02:00", + "time": 0.574, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_08", + "original_name": "resolve_string::case_08", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.801+02:00", + "time": 0.028, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_09", + "original_name": "resolve_string::case_09", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.804+02:00", + "time": 0.027, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_04", + "original_name": "resolve_string::case_04", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.737+02:00", + "time": 0.096, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_genre::case_2_pop", + "original_name": "music_genre::case_2_pop", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.099+02:00", + "time": 1.745, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_05", + "original_name": "resolve_string::case_05", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.762+02:00", + "time": 0.094, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_11", + "original_name": "resolve_string::case_11", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.829+02:00", + "time": 0.031, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_12", + "original_name": "resolve_string::case_12", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.832+02:00", + "time": 0.03, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_genre_radio", + "original_name": "music_search_genre_radio", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.136+02:00", + "time": 0.734, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_06", + "original_name": "resolve_string::case_06", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.777+02:00", + "time": 0.097, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_03", + "original_name": "resolve_url::case_03", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.856+02:00", + "time": 0.028, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_albums::case_3_album", + "original_name": "music_search_albums::case_3_album", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.696+02:00", + "time": 1.2, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_06", + "original_name": "resolve_url::case_06", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.87+02:00", + "time": 0.027, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_07", + "original_name": "resolve_url::case_07", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.873+02:00", + "time": 0.025, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_08", + "original_name": "resolve_url::case_08", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.883+02:00", + "time": 0.027, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_10", + "original_name": "resolve_string::case_10", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.807+02:00", + "time": 0.109, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_09", + "original_name": "resolve_url::case_09", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.895+02:00", + "time": 0.028, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_10", + "original_name": "resolve_url::case_10", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.898+02:00", + "time": 0.026, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_11", + "original_name": "resolve_url::case_11", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.898+02:00", + "time": 0.028, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_12", + "original_name": "resolve_url::case_12", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.91+02:00", + "time": 0.026, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_13", + "original_name": "resolve_url::case_13", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.916+02:00", + "time": 0.027, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_02", + "original_name": "resolve_url::case_02", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.844+02:00", + "time": 0.1, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_14", + "original_name": "resolve_url::case_14", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.923+02:00", + "time": 0.027, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_16", + "original_name": "resolve_url::case_16", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.926+02:00", + "time": 0.026, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_05", + "original_name": "resolve_url::case_05", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.862+02:00", + "time": 0.094, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_04", + "original_name": "resolve_url::case_04", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.86+02:00", + "time": 0.097, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_19", + "original_name": "resolve_url::case_19", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.944+02:00", + "time": 0.029, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_18", + "original_name": "resolve_url::case_18", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.943+02:00", + "time": 0.031, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_main2", + "original_name": "music_search_main2", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.146+02:00", + "time": 0.834, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_20", + "original_name": "resolve_url::case_20", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.95+02:00", + "time": 0.031, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_15", + "original_name": "resolve_url::case_15", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.924+02:00", + "time": 0.097, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_01", + "original_name": "resolve_url::case_01", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.832+02:00", + "time": 0.193, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_17", + "original_name": "resolve_url::case_17", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.936+02:00", + "time": 0.099, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::playlist_cont", + "original_name": "playlist_cont", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.492+02:00", + "time": 0.627, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::playlist_cont2", + "original_name": "playlist_cont2", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.525+02:00", + "time": 0.636, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_suggestion", + "original_name": "search_suggestion", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:39.021+02:00", + "time": 0.198, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::trending", + "original_name": "trending", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:39.035+02:00", + "time": 0.23, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_suggestion_empty", + "original_name": "search_suggestion_empty", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:39.025+02:00", + "time": 0.337, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_empty", + "original_name": "search_empty", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.956+02:00", + "time": 0.504, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_filter_item_type::case_2_channel", + "original_name": "search_filter_item_type::case_2_channel", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.973+02:00", + "time": 0.862, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_artists_cont", + "original_name": "music_search_artists_cont", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.897+02:00", + "time": 1.951, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_videos", + "original_name": "music_search_videos", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.426+02:00", + "time": 1.536, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_filter_item_type::case_3_playlist", + "original_name": "search_filter_item_type::case_3_playlist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.974+02:00", + "time": 1.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_filter_item_type::case_1_video", + "original_name": "search_filter_item_type::case_1_video", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.957+02:00", + "time": 1.145, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_tracks", + "original_name": "music_search_tracks", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.383+02:00", + "time": 1.845, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_sensitive::case_2_filter", + "original_name": "search_sensitive::case_2_filter", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.981+02:00", + "time": 1.581, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_sensitive::case_1_no_filter", + "original_name": "search_sensitive::case_1_no_filter", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.98+02:00", + "time": 1.661, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search", + "original_name": "search", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:38.952+02:00", + "time": 2.02, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_genre::case_1_chill", + "original_name": "music_genre::case_1_chill", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:31:37.037+02:00", + "time": 5.251, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + } + ], + "time": 0.0, + "tests": 170, + "errors": 0, + "failures": 3, + "skipped": 0, + "flaky": 1 + } + ], + "time": 8.402, + "tests": 418, + "errors": 0, + "failures": 3, + "skipped": 0, + "flaky": 1 +} diff --git a/crates/junit-parser/src/snapshots/junit_parser__tests__parse_simple.snap b/crates/junit-parser/src/snapshots/junit_parser__tests__parse_simple.snap new file mode 100644 index 0000000..6c02b04 --- /dev/null +++ b/crates/junit-parser/src/snapshots/junit_parser__tests__parse_simple.snap @@ -0,0 +1,4663 @@ +--- +source: crates/junit-parser/src/lib.rs +assertion_line: 588 +expression: suites +--- +{ + "name": "nextest-run", + "suites": [ + { + "name": "rustypipe::youtube", + "timestamp": null, + "cases": [ + { + "name": "rustypipe::youtube::channel_more::case_1_artist", + "original_name": "channel_more::case_1_artist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.689+02:00", + "time": 0.144, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_more::case_4_music", + "original_name": "channel_more::case_4_music", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.694+02:00", + "time": 0.15, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_not_found::case_3_movies", + "original_name": "channel_not_found::case_3_movies", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.722+02:00", + "time": 0.129, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_age_restriction", + "original_name": "channel_age_restriction", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.685+02:00", + "time": 0.17, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_not_found::case_1_not_exist", + "original_name": "channel_not_found::case_1_not_exist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.696+02:00", + "time": 0.159, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_info", + "original_name": "channel_info", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.689+02:00", + "time": 0.182, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_not_found::case_2_gaming", + "original_name": "channel_not_found::case_2_gaming", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.703+02:00", + "time": 0.186, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_more::case_3_livestream", + "original_name": "channel_more::case_3_livestream", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.69+02:00", + "time": 0.373, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_livestreams", + "original_name": "channel_livestreams", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.689+02:00", + "time": 0.395, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_not_found::case_4_sports", + "original_name": "channel_not_found::case_4_sports", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.722+02:00", + "time": 0.391, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_not_found::case_6_live", + "original_name": "channel_not_found::case_6_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.749+02:00", + "time": 0.365, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_more::case_2_shorts", + "original_name": "channel_more::case_2_shorts", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.69+02:00", + "time": 0.432, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_latest::case_3_shorts", + "original_name": "channel_order_latest::case_3_shorts", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.851+02:00", + "time": 0.281, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_latest::case_1_videos", + "original_name": "channel_order_latest::case_1_videos", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.833+02:00", + "time": 0.372, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_not_found::case_5_learning", + "original_name": "channel_not_found::case_5_learning", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.723+02:00", + "time": 0.52, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_latest::case_2_live", + "original_name": "channel_order_latest::case_2_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.844+02:00", + "time": 0.399, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_popular::case_1_videos", + "original_name": "channel_order_popular::case_1_videos", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.87+02:00", + "time": 0.382, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_tab_not_found::case_1_shorts", + "original_name": "channel_tab_not_found::case_1_shorts", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.114+02:00", + "time": 0.149, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_tab_not_found::case_2_live", + "original_name": "channel_tab_not_found::case_2_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.122+02:00", + "time": 0.156, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_popular::case_2_live", + "original_name": "channel_order_popular::case_2_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.889+02:00", + "time": 0.406, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_oldest::case_1_videos", + "original_name": "channel_order_oldest::case_1_videos", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.855+02:00", + "time": 0.441, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::ab3_search_channel_handles", + "original_name": "ab3_search_channel_handles", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.683+02:00", + "time": 0.624, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_popular::case_3_shorts", + "original_name": "channel_order_popular::case_3_shorts", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.891+02:00", + "time": 0.426, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_playlists", + "original_name": "channel_playlists", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.064+02:00", + "time": 0.265, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_order_oldest::case_2_live", + "original_name": "channel_order_oldest::case_2_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:31.856+02:00", + "time": 0.476, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_shorts", + "original_name": "channel_shorts", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.113+02:00", + "time": 0.249, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_1_not_found", + "original_name": "get_player_error::case_1_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.295+02:00", + "time": 0.104, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player::case_4_live", + "original_name": "get_player::case_4_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.252+02:00", + "time": 0.161, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player::case_1_music", + "original_name": "get_player::case_1_music", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.205+02:00", + "time": 0.213, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_2_deleted", + "original_name": "get_player_error::case_2_deleted", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.296+02:00", + "time": 0.128, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_3_censored", + "original_name": "get_player_error::case_3_censored", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.308+02:00", + "time": 0.137, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player::case_5_was_live", + "original_name": "get_player::case_5_was_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.262+02:00", + "time": 0.203, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player::case_3_multilanguage", + "original_name": "get_player::case_3_multilanguage", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.244+02:00", + "time": 0.223, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_5_private", + "original_name": "get_player_error::case_5_private", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.329+02:00", + "time": 0.137, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_4_geoblock", + "original_name": "get_player_error::case_4_geoblock", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.317+02:00", + "time": 0.156, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player::case_2_hdr", + "original_name": "get_player::case_2_hdr", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.243+02:00", + "time": 0.233, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_6_age_restricted", + "original_name": "get_player_error::case_6_age_restricted", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.332+02:00", + "time": 0.213, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_videos", + "original_name": "channel_videos", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.133+02:00", + "time": 0.431, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_7_premium_only", + "original_name": "get_player_error::case_7_premium_only", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.362+02:00", + "time": 0.229, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error::case_8_members_only", + "original_name": "get_player_error::case_8_members_only", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.398+02:00", + "time": 0.208, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player::case_6_agelimit", + "original_name": "get_player::case_6_agelimit", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.278+02:00", + "time": 0.353, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_error_paid", + "original_name": "get_player_error_paid", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.413+02:00", + "time": 0.218, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_playlist::case_2_nomusic", + "original_name": "get_playlist::case_2_nomusic", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.467+02:00", + "time": 0.213, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_from_client::case_4_ios", + "original_name": "get_player_from_client::case_4_ios", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.465+02:00", + "time": 0.252, + "status": { + "Failure": { + "message": "", + "text": "thread 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14:\nvideo #247\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\nrunning 1 test\ntest get_player_from_client::case_4_ios ... FAILED\n\nfailures:\n\nfailures:\n get_player_from_client::case_4_ios\n\ntest result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished in 0.24s\n\n", + "system_err": "thread 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14:\nvideo #247\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n", + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_from_client::case_1_desktop", + "original_name": "get_player_from_client::case_1_desktop", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.418+02:00", + "time": 0.313, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_from_client::case_2_tv_html5_embed", + "original_name": "get_player_from_client::case_2_tv_html5_embed", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.424+02:00", + "time": 0.327, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_player_from_client::case_3_android", + "original_name": "get_player_from_client::case_3_android", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.445+02:00", + "time": 0.374, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::invalid_ctoken::case_1_desktop", + "original_name": "invalid_ctoken::case_1_desktop", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.731+02:00", + "time": 0.089, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details_not_found", + "original_name": "get_video_details_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.717+02:00", + "time": 0.113, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::invalid_ctoken::case_2_music", + "original_name": "invalid_ctoken::case_2_music", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.751+02:00", + "time": 0.13, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_playlist::case_1_long", + "original_name": "get_playlist::case_1_long", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.467+02:00", + "time": 0.425, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_playlist::case_3_live", + "original_name": "get_playlist::case_3_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.473+02:00", + "time": 0.548, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_3_single", + "original_name": "music_album::case_3_single", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.83+02:00", + "time": 0.282, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_5_show", + "original_name": "music_album::case_5_show", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.892+02:00", + "time": 0.251, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_1_one_artist", + "original_name": "music_album::case_1_one_artist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.819+02:00", + "time": 0.328, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_2_various_artists", + "original_name": "music_album::case_2_various_artists", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.82+02:00", + "time": 0.359, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_4_ep", + "original_name": "music_album::case_4_ep", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.881+02:00", + "time": 0.395, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album_not_found", + "original_name": "music_album_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.178+02:00", + "time": 0.11, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_7_no_year", + "original_name": "music_album::case_7_no_year", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.112+02:00", + "time": 0.207, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_8_version_no_artist", + "original_name": "music_album::case_8_version_no_artist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.143+02:00", + "time": 0.239, + "status": { + "Failure": { + "message": "", + "text": "thread 'music_album::case_8_version_no_artist' panicked at /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\nsnapshot assertion for 'music_album_version_no_artist' failed in line 1593\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\nrunning 1 test\nโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\nSnapshot file: tests/snapshots/youtube__music_album_version_no_artist.snap\nSnapshot: music_album_version_no_artist\nSource: tests/youtube.rs:1593\nโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\nExpression: album\nโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n-old snapshot\n+new results\nโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n 122 122 โ”‚ width: 544,\n 123 123 โ”‚ height: 544,\n 124 124 โ”‚ ),\n 125 125 โ”‚ ],\n 126 โ”‚- artists: [],\n 127 โ”‚- artist_id: None,\n 126 โ”‚+ artists: [\n 127 โ”‚+ ArtistId(\n 128 โ”‚+ id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 129 โ”‚+ name: \"S.p. Kodandapani\",\n 130 โ”‚+ ),\n 131 โ”‚+ ArtistId(\n 132 โ”‚+ id: Some(\"UCbeyiCEAJt5buhxxLAPwVMg\"),\n 133 โ”‚+ name: \"S. P. Kodandapani\",\n 134 โ”‚+ ),\n 135 โ”‚+ ],\n 136 โ”‚+ artist_id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 128 137 โ”‚ album_type: Ep,\n 129 138 โ”‚ year: None,\n 130 โ”‚- by_va: true,\n 139 โ”‚+ by_va: false,\n 131 140 โ”‚ ),\n 132 141 โ”‚ ],\n 133 142 โ”‚ )\nโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\nTo update snapshots run `cargo insta review`\nStopped on the first failure. Run `cargo insta test` to run all snapshots.\ntest music_album::case_8_version_no_artist ... FAILED\n\nfailures:\n\nfailures:\n music_album::case_8_version_no_artist\n\ntest result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished in 0.23s\n\n", + "system_err": "stored new snapshot /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_version_no_artist.snap.new\nthread 'music_album::case_8_version_no_artist' panicked at /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\nsnapshot assertion for 'music_album_version_no_artist' failed in line 1593\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n", + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details_agegate", + "original_name": "get_video_details_agegate", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.563+02:00", + "time": 0.869, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_9_no_artist", + "original_name": "music_album::case_9_no_artist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.147+02:00", + "time": 0.287, + "status": { + "Failure": { + "message": "", + "text": "thread 'music_album::case_9_no_artist' panicked at /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\nsnapshot assertion for 'music_album_no_artist' failed in line 1593\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\nrunning 1 test\nโ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”\nSnapshot file: tests/snapshots/youtube__music_album_no_artist.snap\nSnapshot: music_album_no_artist\nSource: tests/youtube.rs:1593\nโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\nExpression: album\nโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n-old snapshot\n+new results\nโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n 1 1 โ”‚ id: \"MPREb_bqWA6mAZFWS\",\n 2 2 โ”‚ playlist_id: Some(\"OLAK5uy_mUiRbMqeQXFUH6h9KB87RcEmNtm45Qvs0\"),\n 3 3 โ”‚ name: \"Pedha Rasi Peddamma Katha\",\n 4 4 โ”‚ cover: \"[cover]\",\n 5 โ”‚- artists: [],\n 6 โ”‚- artist_id: None,\n 5 โ”‚+ artists: [\n 6 โ”‚+ ArtistId(\n 7 โ”‚+ id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 8 โ”‚+ name: \"S.p. Kodandapani\",\n 9 โ”‚+ ),\n 10 โ”‚+ ArtistId(\n 11 โ”‚+ id: Some(\"UCbeyiCEAJt5buhxxLAPwVMg\"),\n 12 โ”‚+ name: \"S. P. Kodandapani\",\n 13 โ”‚+ ),\n 14 โ”‚+ ],\n 15 โ”‚+ artist_id: Some(\"UCJ59s7i18Dxj_pKVIGETNow\"),\n 7 16 โ”‚ description: None,\n 8 17 โ”‚ album_type: Ep,\n 9 18 โ”‚ year: Some(1968),\n 10 19 โ”‚ by_va: false,\nโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\nTo update snapshots run `cargo insta review`\nStopped on the first failure. Run `cargo insta test` to run all snapshots.\ntest music_album::case_9_no_artist ... FAILED\n\nfailures:\n\nfailures:\n music_album::case_9_no_artist\n\ntest result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished in 0.28s\n\n", + "system_err": "stored new snapshot /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_no_artist.snap.new\nthread 'music_album::case_9_no_artist' panicked at /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9:\nsnapshot assertion for 'music_album_no_artist' failed in line 1593\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n", + "retries": [] + }, + { + "name": "rustypipe::youtube::music_album::case_6_unavailable", + "original_name": "music_album::case_6_unavailable", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.021+02:00", + "time": 0.441, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details_no_desc", + "original_name": "get_video_details_no_desc", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.679+02:00", + "time": 0.796, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_artist::case_4_no_artist", + "original_name": "music_artist::case_4_no_artist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.321+02:00", + "time": 0.204, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_artist_albums_not_found", + "original_name": "music_artist_albums_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.432+02:00", + "time": 0.118, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_artist_not_found", + "original_name": "music_artist_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.434+02:00", + "time": 0.121, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_artist::case_2_no_more_albums", + "original_name": "music_artist::case_2_no_more_albums", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.288+02:00", + "time": 0.329, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_artist::case_3_only_singles", + "original_name": "music_artist::case_3_only_singles", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.319+02:00", + "time": 0.361, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_details::case_1_mv", + "original_name": "music_details::case_1_mv", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.525+02:00", + "time": 0.159, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_details_not_found", + "original_name": "music_details_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.618+02:00", + "time": 0.099, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::channel_search", + "original_name": "channel_search", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.084+02:00", + "time": 1.646, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_artist::case_1_basic_all", + "original_name": "music_artist::case_1_basic_all", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.275+02:00", + "time": 0.457, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_details::case_2_track", + "original_name": "music_details::case_2_track", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.55+02:00", + "time": 0.184, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_charts::case_1_de", + "original_name": "music_charts::case_1_de", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.462+02:00", + "time": 0.313, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_details::case_3_track_details", + "original_name": "music_details::case_3_track_details", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.555+02:00", + "time": 0.246, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_charts::case_2_us", + "original_name": "music_charts::case_2_us", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.475+02:00", + "time": 0.334, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_artist::case_5_secondary_channel", + "original_name": "music_artist::case_5_secondary_channel", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.382+02:00", + "time": 0.449, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_genre_not_found", + "original_name": "music_genre_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.717+02:00", + "time": 0.146, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_genres", + "original_name": "music_genres", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.731+02:00", + "time": 0.138, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details_chapters", + "original_name": "get_video_details_chapters", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.606+02:00", + "time": 1.295, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_lyrics_not_found", + "original_name": "music_lyrics_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.734+02:00", + "time": 0.203, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_comments", + "original_name": "get_video_comments", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.476+02:00", + "time": 1.487, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_playlist_not_found", + "original_name": "music_playlist_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.937+02:00", + "time": 0.105, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details_ccommons", + "original_name": "get_video_details_ccommons", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.592+02:00", + "time": 1.462, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_playlist::case_3_nomusic", + "original_name": "music_playlist::case_3_nomusic", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.863+02:00", + "time": 0.231, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details_music", + "original_name": "get_video_details_music", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.631+02:00", + "time": 1.507, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details_live", + "original_name": "get_video_details_live", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.63+02:00", + "time": 1.538, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_radio_not_found", + "original_name": "music_radio_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.053+02:00", + "time": 0.126, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_playlist::case_2_short", + "original_name": "music_playlist::case_2_short", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.831+02:00", + "time": 0.389, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_new_videos", + "original_name": "music_new_videos", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.801+02:00", + "time": 0.425, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_new_albums", + "original_name": "music_new_albums", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.775+02:00", + "time": 0.475, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::get_video_details", + "original_name": "get_video_details", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:32.545+02:00", + "time": 1.715, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_playlist::case_1_long", + "original_name": "music_playlist::case_1_long", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.809+02:00", + "time": 0.452, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_radio_track_not_found", + "original_name": "music_radio_track_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.179+02:00", + "time": 0.152, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_lyrics", + "original_name": "music_lyrics", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.732+02:00", + "time": 0.612, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_related::case_1_a", + "original_name": "music_related::case_1_a", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.22+02:00", + "time": 0.429, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_playlist_cont::case_1_user", + "original_name": "music_playlist_cont::case_1_user", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.869+02:00", + "time": 0.797, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_related::case_2_b", + "original_name": "music_related::case_2_b", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.226+02:00", + "time": 0.443, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_albums::case_2_ep", + "original_name": "music_search_albums::case_2_ep", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.26+02:00", + "time": 0.44, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_radio_artist", + "original_name": "music_radio_artist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.042+02:00", + "time": 0.678, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_playlist_related", + "original_name": "music_playlist_related", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.964+02:00", + "time": 0.757, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_radio_playlist_not_found", + "original_name": "music_radio_playlist_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.137+02:00", + "time": 0.585, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_albums::case_1_single", + "original_name": "music_search_albums::case_1_single", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.25+02:00", + "time": 0.578, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_artists", + "original_name": "music_search_artists", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.331+02:00", + "time": 0.515, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_suggestion::case_1_default", + "original_name": "music_search_suggestion::case_1_default", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.846+02:00", + "time": 0.116, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_radio_track", + "original_name": "music_radio_track", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.168+02:00", + "time": 0.809, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_playlist_cont::case_2_ytm", + "original_name": "music_playlist_cont::case_2_ytm", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.901+02:00", + "time": 1.161, + "status": { + "Failure": { + "message": "", + "text": "thread 'music_playlist_cont::case_2_ytm' panicked at tests/youtube.rs:1534:5:\nexpected >= 227 tracks, got 128\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\nrunning 1 test\ntest music_playlist_cont::case_2_ytm ... FAILED\n\nfailures:\n\nfailures:\n music_playlist_cont::case_2_ytm\n\ntest result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished in 1.15s\n\n", + "system_err": "thread 'music_playlist_cont::case_2_ytm' panicked at tests/youtube.rs:1534:5:\nexpected >= 227 tracks, got 128\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n", + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_suggestion::case_2_empty", + "original_name": "music_search_suggestion::case_2_empty", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.961+02:00", + "time": 0.124, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_radio_playlist", + "original_name": "music_radio_playlist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.094+02:00", + "time": 1.024, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_episode::case_1_main", + "original_name": "music_search_episode::case_1_main", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.649+02:00", + "time": 0.484, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_episode::case_2_videos", + "original_name": "music_search_episode::case_2_videos", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.666+02:00", + "time": 0.469, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_playlists", + "original_name": "music_search_playlists", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.722+02:00", + "time": 0.44, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_main::case_1_default", + "original_name": "music_search_main::case_1_default", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.72+02:00", + "time": 0.465, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_main::case_2_typo", + "original_name": "music_search_main::case_2_typo", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.721+02:00", + "time": 0.496, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_03", + "original_name": "resolve_string::case_03", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.217+02:00", + "time": 0.028, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::playlist_not_found", + "original_name": "playlist_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.133+02:00", + "time": 0.115, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_channel_not_found", + "original_name": "resolve_channel_not_found", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.135+02:00", + "time": 0.114, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_genre_radio", + "original_name": "music_search_genre_radio", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.669+02:00", + "time": 0.589, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_01", + "original_name": "resolve_string::case_01", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.162+02:00", + "time": 0.099, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_07", + "original_name": "resolve_string::case_07", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.258+02:00", + "time": 0.026, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_08", + "original_name": "resolve_string::case_08", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.26+02:00", + "time": 0.028, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_02", + "original_name": "resolve_string::case_02", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.185+02:00", + "time": 0.104, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_playlists_community", + "original_name": "music_search_playlists_community", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.828+02:00", + "time": 0.478, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_09", + "original_name": "resolve_string::case_09", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.284+02:00", + "time": 0.028, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_main2", + "original_name": "music_search_main2", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.7+02:00", + "time": 0.622, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_11", + "original_name": "resolve_string::case_11", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.289+02:00", + "time": 0.038, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_12", + "original_name": "resolve_string::case_12", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.306+02:00", + "time": 0.029, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_06", + "original_name": "resolve_string::case_06", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.249+02:00", + "time": 0.103, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_04", + "original_name": "resolve_string::case_04", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.245+02:00", + "time": 0.107, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_03", + "original_name": "resolve_url::case_03", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.327+02:00", + "time": 0.026, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_05", + "original_name": "resolve_string::case_05", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.248+02:00", + "time": 0.124, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_06", + "original_name": "resolve_url::case_06", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.352+02:00", + "time": 0.026, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_07", + "original_name": "resolve_url::case_07", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.353+02:00", + "time": 0.026, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_string::case_10", + "original_name": "resolve_string::case_10", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.289+02:00", + "time": 0.098, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_genre::case_2_pop", + "original_name": "music_genre::case_2_pop", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.684+02:00", + "time": 1.709, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_08", + "original_name": "resolve_url::case_08", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.371+02:00", + "time": 0.027, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_09", + "original_name": "resolve_url::case_09", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.378+02:00", + "time": 0.026, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_10", + "original_name": "resolve_url::case_10", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.379+02:00", + "time": 0.028, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_01", + "original_name": "resolve_url::case_01", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.312+02:00", + "time": 0.096, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_11", + "original_name": "resolve_url::case_11", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.387+02:00", + "time": 0.029, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_02", + "original_name": "resolve_url::case_02", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.322+02:00", + "time": 0.094, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_13", + "original_name": "resolve_url::case_13", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.399+02:00", + "time": 0.027, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_12", + "original_name": "resolve_url::case_12", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.393+02:00", + "time": 0.034, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_04", + "original_name": "resolve_url::case_04", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.335+02:00", + "time": 0.092, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_14", + "original_name": "resolve_url::case_14", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.404+02:00", + "time": 0.028, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_16", + "original_name": "resolve_url::case_16", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.408+02:00", + "time": 0.029, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_05", + "original_name": "resolve_url::case_05", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.351+02:00", + "time": 0.094, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_18", + "original_name": "resolve_url::case_18", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.416+02:00", + "time": 0.03, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_19", + "original_name": "resolve_url::case_19", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.426+02:00", + "time": 0.041, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_20", + "original_name": "resolve_url::case_20", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.427+02:00", + "time": 0.042, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_15", + "original_name": "resolve_url::case_15", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.407+02:00", + "time": 0.102, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_albums::case_3_album", + "original_name": "music_search_albums::case_3_album", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.261+02:00", + "time": 1.255, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::resolve_url::case_17", + "original_name": "resolve_url::case_17", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.416+02:00", + "time": 0.114, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::playlist_cont", + "original_name": "playlist_cont", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.085+02:00", + "time": 0.583, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_suggestion", + "original_name": "search_suggestion", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.509+02:00", + "time": 0.186, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_suggestion_empty", + "original_name": "search_suggestion_empty", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.516+02:00", + "time": 0.222, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::playlist_cont2", + "original_name": "playlist_cont2", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.118+02:00", + "time": 0.625, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::trending", + "original_name": "trending", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.53+02:00", + "time": 0.245, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_empty", + "original_name": "search_empty", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.432+02:00", + "time": 0.502, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_filter_item_type::case_2_channel", + "original_name": "search_filter_item_type::case_2_channel", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.445+02:00", + "time": 0.804, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_artists_cont", + "original_name": "music_search_artists_cont", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.344+02:00", + "time": 2.07, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_filter_item_type::case_3_playlist", + "original_name": "search_filter_item_type::case_3_playlist", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.447+02:00", + "time": 1.026, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_filter_item_type::case_1_video", + "original_name": "search_filter_item_type::case_1_video", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.437+02:00", + "time": 1.044, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_sensitive::case_2_filter", + "original_name": "search_sensitive::case_2_filter", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.469+02:00", + "time": 1.306, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_videos", + "original_name": "music_search_videos", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.062+02:00", + "time": 1.74, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::music_search_tracks", + "original_name": "music_search_tracks", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:34.977+02:00", + "time": 1.846, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search_sensitive::case_1_no_filter", + "original_name": "search_sensitive::case_1_no_filter", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.467+02:00", + "time": 1.821, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::youtube::search", + "original_name": "search", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:35.428+02:00", + "time": 2.177, + "status": { + "Failure": { + "message": "", + "text": "thread 'search' panicked at tests/youtube.rs:2751:9:\nexpected >= 10 items on page 2, got 4\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace" + } + }, + "system_out": "\nrunning 1 test\ntest search ... FAILED\n\nfailures:\n\nfailures:\n search\n\ntest result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished in 2.17s\n\n", + "system_err": "thread 'search' panicked at tests/youtube.rs:2751:9:\nexpected >= 10 items on page 2, got 4\nnote: run with `RUST_BACKTRACE=1` environment variable to display a backtrace\n", + "retries": [] + }, + { + "name": "rustypipe::youtube::music_genre::case_1_chill", + "original_name": "music_genre::case_1_chill", + "classname": "rustypipe::youtube", + "timestamp": "2024-05-31T22:35:33.68+02:00", + "time": 5.248, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + } + ], + "time": 0.0, + "tests": 170, + "errors": 0, + "failures": 5, + "skipped": 0, + "flaky": 0 + }, + { + "name": "rustypipe", + "timestamp": null, + "cases": [ + { + "name": "rustypipe::client::channel::tests::channel_agegate", + "original_name": "client::channel::tests::channel_agegate", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.709+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::t_channel_info_ctoken", + "original_name": "client::channel::tests::t_channel_info_ctoken", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.72+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_05_empty", + "original_name": "client::channel::tests::map_channel_videos::case_05_empty", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.711+02:00", + "time": 0.036, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::t_order_ctoken", + "original_name": "client::channel::tests::t_order_ctoken", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.728+02:00", + "time": 0.034, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_02_music", + "original_name": "client::channel::tests::map_channel_videos::case_02_music", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.71+02:00", + "time": 0.054, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_info", + "original_name": "client::channel::tests::map_channel_info", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.71+02:00", + "time": 0.057, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_playlists", + "original_name": "client::channel::tests::map_channel_playlists", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.71+02:00", + "time": 0.077, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_12_pageheader", + "original_name": "client::channel::tests::map_channel_videos::case_12_pageheader", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.713+02:00", + "time": 0.086, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_04_live", + "original_name": "client::channel::tests::map_channel_videos::case_04_live", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.711+02:00", + "time": 0.089, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_10_shorts", + "original_name": "client::channel::tests::map_channel_videos::case_10_shorts", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.712+02:00", + "time": 0.094, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_06_upcoming", + "original_name": "client::channel::tests::map_channel_videos::case_06_upcoming", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.711+02:00", + "time": 0.101, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_artist::tests::map_music_artist::case_3_no_artist", + "original_name": "client::music_artist::tests::map_music_artist::case_3_no_artist", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.765+02:00", + "time": 0.052, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_11_livestreams", + "original_name": "client::channel::tests::map_channel_videos::case_11_livestreams", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.713+02:00", + "time": 0.106, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_07_richgrid", + "original_name": "client::channel::tests::map_channel_videos::case_07_richgrid", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.712+02:00", + "time": 0.108, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_09_coachella", + "original_name": "client::channel::tests::map_channel_videos::case_09_coachella", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.712+02:00", + "time": 0.122, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_13_pageheader2", + "original_name": "client::channel::tests::map_channel_videos::case_13_pageheader2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.713+02:00", + "time": 0.121, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_03_withshorts", + "original_name": "client::channel::tests::map_channel_videos::case_03_withshorts", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.711+02:00", + "time": 0.124, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_08_richgrid2", + "original_name": "client::channel::tests::map_channel_videos::case_08_richgrid2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.712+02:00", + "time": 0.125, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::channel::tests::map_channel_videos::case_01_base", + "original_name": "client::channel::tests::map_channel_videos::case_01_base", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.71+02:00", + "time": 0.127, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_artist::tests::map_music_artist_secondary_channel", + "original_name": "client::music_artist::tests::map_music_artist_secondary_channel", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.799+02:00", + "time": 0.041, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_details::tests::map_lyrics", + "original_name": "client::music_details::tests::map_lyrics", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.812+02:00", + "time": 0.035, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_artist::tests::map_music_artist::case_2_only_singles", + "original_name": "client::music_artist::tests::map_music_artist::case_2_only_singles", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.762+02:00", + "time": 0.085, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_details::tests::map_music_details::case_1_mv", + "original_name": "client::music_details::tests::map_music_details::case_1_mv", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.817+02:00", + "time": 0.045, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_details::tests::map_music_details::case_2_track", + "original_name": "client::music_details::tests::map_music_details::case_2_track", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.819+02:00", + "time": 0.049, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_genres::tests::map_music_genres", + "original_name": "client::music_genres::tests::map_music_genres", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.837+02:00", + "time": 0.043, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_new::tests::map_music_new_videos::case_1_default", + "original_name": "client::music_new::tests::map_music_new_videos::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.847+02:00", + "time": 0.078, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_album::case_3_single", + "original_name": "client::music_playlist::tests::map_music_album::case_3_single", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.88+02:00", + "time": 0.052, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_album::case_2_various_artists", + "original_name": "client::music_playlist::tests::map_music_album::case_2_various_artists", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.867+02:00", + "time": 0.074, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_artist::tests::map_music_artist_no_cont", + "original_name": "client::music_artist::tests::map_music_artist_no_cont", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.787+02:00", + "time": 0.156, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_album::case_1_one_artist", + "original_name": "client::music_playlist::tests::map_music_album::case_1_one_artist", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.862+02:00", + "time": 0.091, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_details::tests::map_music_radio::case_1_mv", + "original_name": "client::music_details::tests::map_music_radio::case_1_mv", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.819+02:00", + "time": 0.139, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_details::tests::map_related", + "original_name": "client::music_details::tests::map_related", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.835+02:00", + "time": 0.138, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_details::tests::map_music_radio::case_2_track", + "original_name": "client::music_details::tests::map_music_radio::case_2_track", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.834+02:00", + "time": 0.139, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_album::case_7_two_columns", + "original_name": "client::music_playlist::tests::map_music_album::case_7_two_columns", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.942+02:00", + "time": 0.048, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_album::case_4_description", + "original_name": "client::music_playlist::tests::map_music_album::case_4_description", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.925+02:00", + "time": 0.067, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_artist::tests::map_music_artist::case_4_only_more_singles", + "original_name": "client::music_artist::tests::map_music_artist::case_4_only_more_singles", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.766+02:00", + "time": 0.235, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_album::case_5_unavailable", + "original_name": "client::music_playlist::tests::map_music_album::case_5_unavailable", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.932+02:00", + "time": 0.075, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_charts::tests::map_music_charts::case_1_default", + "original_name": "client::music_charts::tests::map_music_charts::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.8+02:00", + "time": 0.228, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_album::case_6_unavailable", + "original_name": "client::music_playlist::tests::map_music_album::case_6_unavailable", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.942+02:00", + "time": 0.086, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_playlist::case_5_n_album", + "original_name": "client::music_playlist::tests::map_music_playlist::case_5_n_album", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.991+02:00", + "time": 0.055, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_playlist::case_3_nomusic", + "original_name": "client::music_playlist::tests::map_music_playlist::case_3_nomusic", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.973+02:00", + "time": 0.081, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_artists", + "original_name": "client::music_search::tests::map_music_search_artists", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.001+02:00", + "time": 0.059, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_albums", + "original_name": "client::music_search::tests::map_music_search_albums", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.993+02:00", + "time": 0.087, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_charts::tests::map_music_charts::case_2_us", + "original_name": "client::music_charts::tests::map_music_charts::case_2_us", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.807+02:00", + "time": 0.285, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_main::case_1_default", + "original_name": "client::music_search::tests::map_music_search_main::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.007+02:00", + "time": 0.086, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_main::case_3_radio", + "original_name": "client::music_search::tests::map_music_search_main::case_3_radio", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.029+02:00", + "time": 0.088, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_new::tests::map_music_new_videos::case_2_default", + "original_name": "client::music_new::tests::map_music_new_videos::case_2_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.848+02:00", + "time": 0.286, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_suggestion::case_2_empty", + "original_name": "client::music_search::tests::map_music_search_suggestion::case_2_empty", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.092+02:00", + "time": 0.042, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_suggestion::case_1_default", + "original_name": "client::music_search::tests::map_music_search_suggestion::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.079+02:00", + "time": 0.055, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_main::case_2_typo", + "original_name": "client::music_search::tests::map_music_search_main::case_2_typo", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.028+02:00", + "time": 0.106, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_main::case_4_artist", + "original_name": "client::music_search::tests::map_music_search_main::case_4_artist", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.046+02:00", + "time": 0.091, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_artist::tests::map_music_artist::case_1_default", + "original_name": "client::music_artist::tests::map_music_artist::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.747+02:00", + "time": 0.391, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_playlists::case_1_ytm", + "original_name": "client::music_search::tests::map_music_search_playlists::case_1_ytm", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.054+02:00", + "time": 0.094, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_playlists::case_2_community", + "original_name": "client::music_search::tests::map_music_search_playlists::case_2_community", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.061+02:00", + "time": 0.096, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_music_playlists::case_1_playlist_related", + "original_name": "client::pagination::tests::map_continuation_music_playlists::case_1_playlist_related", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.138+02:00", + "time": 0.053, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_tracks::case_1_default", + "original_name": "client::music_search::tests::map_music_search_tracks::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.093+02:00", + "time": 0.1, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_tracks::case_4_no_artist_link", + "original_name": "client::music_search::tests::map_music_search_tracks::case_4_no_artist_link", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.134+02:00", + "time": 0.062, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_items::case_3_recommendations", + "original_name": "client::pagination::tests::map_continuation_items::case_3_recommendations", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.137+02:00", + "time": 0.074, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_playlists::case_1_channel_playlists", + "original_name": "client::pagination::tests::map_continuation_playlists::case_1_channel_playlists", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.147+02:00", + "time": 0.065, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::player::tests::cipher_to_url", + "original_name": "client::player::tests::cipher_to_url", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.213+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_items::case_1_search", + "original_name": "client::pagination::tests::map_continuation_items::case_1_search", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.135+02:00", + "time": 0.091, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_tracks::case_3_videos", + "original_name": "client::music_search::tests::map_music_search_tracks::case_3_videos", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.134+02:00", + "time": 0.093, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_items::case_2_startpage", + "original_name": "client::pagination::tests::map_continuation_items::case_2_startpage", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.135+02:00", + "time": 0.095, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_search::tests::map_music_search_tracks::case_2_typo", + "original_name": "client::music_search::tests::map_music_search_tracks::case_2_typo", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.117+02:00", + "time": 0.116, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_genres::tests::map_music_genre::case_2_mood", + "original_name": "client::music_genres::tests::map_music_genre::case_2_mood", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.837+02:00", + "time": 0.396, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_playlist::case_1_short", + "original_name": "client::music_playlist::tests::map_music_playlist::case_1_short", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.953+02:00", + "time": 0.303, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_tracks::case_2_search_tracks", + "original_name": "client::pagination::tests::map_continuation_tracks::case_2_search_tracks", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.191+02:00", + "time": 0.081, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_videos::case_1_channel_videos", + "original_name": "client::pagination::tests::map_continuation_videos::case_1_channel_videos", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.196+02:00", + "time": 0.092, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::player::tests::map_player_data::case_4_android", + "original_name": "client::player::tests::map_player_data::case_4_android", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.23+02:00", + "time": 0.062, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_playlist::case_4_two_columns", + "original_name": "client::music_playlist::tests::map_music_playlist::case_4_two_columns", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.974+02:00", + "time": 0.321, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::player::tests::map_player_data::case_5_ios", + "original_name": "client::player::tests::map_player_data::case_5_ios", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.233+02:00", + "time": 0.066, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::player::tests::map_player_data::case_3_tv_html5_embed", + "original_name": "client::player::tests::map_player_data::case_3_tv_html5_embed", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.227+02:00", + "time": 0.076, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::response::music_item::tests::map_album_type_samples", + "original_name": "client::response::music_item::tests::map_album_type_samples", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.292+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_playlist::tests::map_music_playlist::case_2_long", + "original_name": "client::music_playlist::tests::map_music_playlist::case_2_long", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.959+02:00", + "time": 0.344, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::player::tests::map_player_data::case_2_desktop_music", + "original_name": "client::player::tests::map_player_data::case_2_desktop_music", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.226+02:00", + "time": 0.081, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_genres::tests::map_music_genre::case_1_default", + "original_name": "client::music_genres::tests::map_music_genre::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.835+02:00", + "time": 0.473, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_tracks::case_3_radio_tracks", + "original_name": "client::pagination::tests::map_continuation_tracks::case_3_radio_tracks", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.193+02:00", + "time": 0.127, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::player::tests::map_player_data::case_1_desktop", + "original_name": "client::player::tests::map_player_data::case_1_desktop", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.221+02:00", + "time": 0.108, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::search::tests::t_map_search::case_3_empty", + "original_name": "client::search::tests::t_map_search::case_3_empty", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.303+02:00", + "time": 0.039, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::search::tests::t_map_search::case_2_playlists", + "original_name": "client::search::tests::t_map_search::case_2_playlists", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.299+02:00", + "time": 0.063, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::music_new::tests::map_music_new_albums::case_1_default", + "original_name": "client::music_new::tests::map_music_new_albums::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:30.841+02:00", + "time": 0.533, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::search::tests::t_map_search::case_4_ab3_channel_handles", + "original_name": "client::search::tests::t_map_search::case_4_ab3_channel_handles", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.303+02:00", + "time": 0.072, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_videos::case_2_playlist", + "original_name": "client::pagination::tests::map_continuation_videos::case_2_playlist", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.211+02:00", + "time": 0.177, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::tests::t_get_visitor_data", + "original_name": "client::tests::t_get_visitor_data", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.308+02:00", + "time": 0.109, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::playlist::tests::map_playlist_data::case_1_short", + "original_name": "client::playlist::tests::map_playlist_data::case_1_short", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.233+02:00", + "time": 0.188, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::playlist::tests::map_playlist_data::case_3_nomusic", + "original_name": "client::playlist::tests::map_playlist_data::case_3_nomusic", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.272+02:00", + "time": 0.159, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::pagination::tests::map_continuation_tracks::case_1_playlist_tracks", + "original_name": "client::pagination::tests::map_continuation_tracks::case_1_playlist_tracks", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.157+02:00", + "time": 0.291, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::search::tests::t_map_search::case_1_default", + "original_name": "client::search::tests::t_map_search::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.294+02:00", + "time": 0.161, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_comments::case_4_frameworkupd_reply", + "original_name": "client::video_details::tests::map_comments::case_4_frameworkupd_reply", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.388+02:00", + "time": 0.079, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_comments::case_1_top", + "original_name": "client::video_details::tests::map_comments::case_1_top", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.362+02:00", + "time": 0.108, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::trends::tests::map_trending::case_2_page_header_renderer", + "original_name": "client::trends::tests::map_trending::case_2_page_header_renderer", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.342+02:00", + "time": 0.131, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_comments::case_2_latest", + "original_name": "client::video_details::tests::map_comments::case_2_latest", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.373+02:00", + "time": 0.101, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::trends::tests::map_startpage", + "original_name": "client::trends::tests::map_startpage", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.32+02:00", + "time": 0.158, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::playlist::tests::map_playlist_data::case_2_long", + "original_name": "client::playlist::tests::map_playlist_data::case_2_long", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.256+02:00", + "time": 0.228, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details_not_found", + "original_name": "client::video_details::tests::map_video_details_not_found", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.484+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_comments::case_3_frameworkupd", + "original_name": "client::video_details::tests::map_comments::case_3_frameworkupd", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.376+02:00", + "time": 0.12, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_deobfuscate_nsig", + "original_name": "deobfuscate::tests::t_deobfuscate_nsig", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.495+02:00", + "time": 0.006, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_deobfuscate_sig", + "original_name": "deobfuscate::tests::t_deobfuscate_sig", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.495+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_02_music", + "original_name": "client::video_details::tests::map_video_details::case_02_music", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.422+02:00", + "time": 0.081, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_06_agegate", + "original_name": "client::video_details::tests::map_video_details::case_06_agegate", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.467+02:00", + "time": 0.04, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_extract_js_fn", + "original_name": "deobfuscate::tests::t_extract_js_fn", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.501+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::playlist::tests::map_playlist_data::case_4_live", + "original_name": "client::playlist::tests::map_playlist_data::case_4_live", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.288+02:00", + "time": 0.224, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_01_mv", + "original_name": "client::video_details::tests::map_video_details::case_01_mv", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.417+02:00", + "time": 0.095, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_extract_js_fn_eviljs", + "original_name": "deobfuscate::tests::t_extract_js_fn_eviljs", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.503+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_get_nsig_fn", + "original_name": "deobfuscate::tests::t_get_nsig_fn", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.503+02:00", + "time": 0.012, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_get_nsig_fn_name", + "original_name": "deobfuscate::tests::t_get_nsig_fn_name", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.508+02:00", + "time": 0.022, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_get_sts", + "original_name": "deobfuscate::tests::t_get_sts", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.514+02:00", + "time": 0.015, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::tests::t_extract_desktop_client_version", + "original_name": "client::tests::t_extract_desktop_client_version", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.304+02:00", + "time": 0.226, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_03_ccommons", + "original_name": "client::video_details::tests::map_video_details::case_03_ccommons", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.431+02:00", + "time": 0.104, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::tests::t_extract_music_client_version", + "original_name": "client::tests::t_extract_music_client_version", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.307+02:00", + "time": 0.233, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_01", + "original_name": "param::search_filter::tests::t_filter::case_01", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.535+02:00", + "time": 0.013, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_02", + "original_name": "param::search_filter::tests::t_filter::case_02", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.541+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_05_live", + "original_name": "client::video_details::tests::map_video_details::case_05_live", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.456+02:00", + "time": 0.095, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_03", + "original_name": "param::search_filter::tests::t_filter::case_03", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.547+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_04", + "original_name": "param::search_filter::tests::t_filter::case_04", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.548+02:00", + "time": 0.006, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_06", + "original_name": "param::search_filter::tests::t_filter::case_06", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.554+02:00", + "time": 0.006, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_05", + "original_name": "param::search_filter::tests::t_filter::case_05", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.551+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_08_ab_new_cont", + "original_name": "client::video_details::tests::map_video_details::case_08_ab_new_cont", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.473+02:00", + "time": 0.088, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_07_ab_newdesc", + "original_name": "client::video_details::tests::map_video_details::case_07_ab_newdesc", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.47+02:00", + "time": 0.093, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_07", + "original_name": "param::search_filter::tests::t_filter::case_07", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.555+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::model::richtext::tests::to_plaintext", + "original_name": "model::richtext::tests::to_plaintext", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.534+02:00", + "time": 0.031, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_09", + "original_name": "param::search_filter::tests::t_filter::case_09", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.56+02:00", + "time": 0.006, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::model::richtext::tests::to_markdown", + "original_name": "model::richtext::tests::to_markdown", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.534+02:00", + "time": 0.032, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::model::richtext::tests::to_html", + "original_name": "model::richtext::tests::to_html", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.529+02:00", + "time": 0.037, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_08", + "original_name": "param::search_filter::tests::t_filter::case_08", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.56+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_10", + "original_name": "param::search_filter::tests::t_filter::case_10", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.562+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_04_chapters", + "original_name": "client::video_details::tests::map_video_details::case_04_chapters", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.447+02:00", + "time": 0.122, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_12", + "original_name": "param::search_filter::tests::t_filter::case_12", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.563+02:00", + "time": 0.006, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_11", + "original_name": "param::search_filter::tests::t_filter::case_11", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.562+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_14", + "original_name": "param::search_filter::tests::t_filter::case_14", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.566+02:00", + "time": 0.006, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_15", + "original_name": "param::search_filter::tests::t_filter::case_15", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.566+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_13", + "original_name": "param::search_filter::tests::t_filter::case_13", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.565+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_16", + "original_name": "param::search_filter::tests::t_filter::case_16", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.567+02:00", + "time": 0.006, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_17", + "original_name": "param::search_filter::tests::t_filter::case_17", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.567+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_18", + "original_name": "param::search_filter::tests::t_filter::case_18", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.568+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_20", + "original_name": "param::search_filter::tests::t_filter::case_20", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.571+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_19", + "original_name": "param::search_filter::tests::t_filter::case_19", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.569+02:00", + "time": 0.01, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_21", + "original_name": "param::search_filter::tests::t_filter::case_21", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.571+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_22", + "original_name": "param::search_filter::tests::t_filter::case_22", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.573+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::search_filter::tests::t_filter::case_23", + "original_name": "param::search_filter::tests::t_filter::case_23", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.573+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_1_default", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.574+02:00", + "time": 0.015, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_2_bitrate", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_2_bitrate", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.574+02:00", + "time": 0.017, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_3_m4a_format", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_3_m4a_format", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.575+02:00", + "time": 0.017, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_6_br_fallback", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_6_br_fallback", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.579+02:00", + "time": 0.017, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_7_lang_fallback", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_7_lang_fallback", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.58+02:00", + "time": 0.017, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_9_nocodec", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_9_nocodec", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.58+02:00", + "time": 0.016, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_8_noformat", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_8_noformat", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.58+02:00", + "time": 0.018, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_4_m4a_codec", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_4_m4a_codec", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.578+02:00", + "time": 0.022, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_09_ab_no_recommends", + "original_name": "client::video_details::tests::map_video_details::case_09_ab_no_recommends", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.475+02:00", + "time": 0.126, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_audio_stream::case_1_default", + "original_name": "param::stream_filter::tests::t_select_video_audio_stream::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.589+02:00", + "time": 0.013, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_audio_stream::case_2_webm", + "original_name": "param::stream_filter::tests::t_select_video_audio_stream::case_2_webm", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.591+02:00", + "time": 0.013, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_audio_stream::case_5_french", + "original_name": "param::stream_filter::tests::t_select_audio_stream::case_5_french", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.578+02:00", + "time": 0.026, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_audio_stream::case_3_noaudio", + "original_name": "param::stream_filter::tests::t_select_video_audio_stream::case_3_noaudio", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.592+02:00", + "time": 0.013, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::video_details::tests::map_video_details::case_10_ab_new_likes", + "original_name": "client::video_details::tests::map_video_details::case_10_ab_new_likes", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.478+02:00", + "time": 0.131, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_audio_stream::case_4_novideo", + "original_name": "param::stream_filter::tests::t_select_video_audio_stream::case_4_novideo", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.596+02:00", + "time": 0.013, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_2_hdr", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_2_hdr", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.598+02:00", + "time": 0.012, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_1_default", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_1_default", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.597+02:00", + "time": 0.013, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_audio_stream::case_5_noformat", + "original_name": "param::stream_filter::tests::t_select_video_audio_stream::case_5_noformat", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.597+02:00", + "time": 0.014, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_3_resolution", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_3_resolution", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.599+02:00", + "time": 0.012, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_5_res_fallback", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_5_res_fallback", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.602+02:00", + "time": 0.013, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_get_player_js_url", + "original_name": "deobfuscate::tests::t_get_player_js_url", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.51+02:00", + "time": 0.106, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_7_vp9_codec", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_7_vp9_codec", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.604+02:00", + "time": 0.012, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::tests::t_ignore_any", + "original_name": "serializer::tests::t_ignore_any", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.609+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_8_noformat", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_8_noformat", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.605+02:00", + "time": 0.013, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_6_webm_format", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_6_webm_format", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.604+02:00", + "time": 0.014, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_deserialize_text::case_1", + "original_name": "serializer::text::tests::t_deserialize_text::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.612+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_4_resolution_fps", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_4_resolution_fps", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.601+02:00", + "time": 0.019, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::param::stream_filter::tests::t_select_video_only_stream::case_9_nocodec", + "original_name": "param::stream_filter::tests::t_select_video_only_stream::case_9_nocodec", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.608+02:00", + "time": 0.014, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_deserialize_text::case_2", + "original_name": "serializer::text::tests::t_deserialize_text::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.615+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_deserialize_text::case_3", + "original_name": "serializer::text::tests::t_deserialize_text::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.615+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_deserialize_text::case_4", + "original_name": "serializer::text::tests::t_deserialize_text::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.617+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_links_empty", + "original_name": "serializer::text::tests::t_links_empty", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.624+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_get_sig_fn", + "original_name": "deobfuscate::tests::t_get_sig_fn", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.512+02:00", + "time": 0.122, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_get_sig_fn_name", + "original_name": "deobfuscate::tests::t_get_sig_fn_name", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.513+02:00", + "time": 0.125, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::protobuf::tests::t_parse_proto", + "original_name": "util::protobuf::tests::t_parse_proto", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.631+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::parse_language::case_1", + "original_name": "util::tests::parse_language::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.634+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::split_text_cmp", + "original_name": "serializer::text::tests::split_text_cmp", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.61+02:00", + "time": 0.034, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::styled_comment", + "original_name": "serializer::text::tests::styled_comment", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.61+02:00", + "time": 0.034, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::parse_language::case_2", + "original_name": "util::tests::parse_language::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.638+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_attributed_description", + "original_name": "serializer::text::tests::t_attributed_description", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.611+02:00", + "time": 0.036, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::parse_language::case_3", + "original_name": "util::tests::parse_language::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.639+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::parse_language::case_4", + "original_name": "util::tests::parse_language::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.642+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_link_channel", + "original_name": "serializer::text::tests::t_link_channel", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.618+02:00", + "time": 0.032, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_link_none", + "original_name": "serializer::text::tests::t_link_none", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.618+02:00", + "time": 0.033, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::parse_language::case_5", + "original_name": "util::tests::parse_language::case_5", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.644+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::parse_language::case_6", + "original_name": "util::tests::parse_language::case_6", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.644+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_link_video", + "original_name": "serializer::text::tests::t_link_video", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.619+02:00", + "time": 0.032, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_link_web", + "original_name": "serializer::text::tests::t_link_web", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.62+02:00", + "time": 0.034, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_link_album", + "original_name": "serializer::text::tests::t_link_album", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.617+02:00", + "time": 0.037, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::split_words", + "original_name": "util::tests::split_words", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.647+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::split_char", + "original_name": "util::tests::split_char", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.645+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::vec_log_err::tests::skip_error", + "original_name": "serializer::vec_log_err::tests::skip_error", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.625+02:00", + "time": 0.031, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::text::tests::t_links_artists", + "original_name": "serializer::text::tests::t_links_artists", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.622+02:00", + "time": 0.034, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::serializer::vec_log_err::tests::log_error", + "original_name": "serializer::vec_log_err::tests::log_error", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.624+02:00", + "time": 0.033, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_country_from_name::case_2", + "original_name": "util::tests::t_country_from_name::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.649+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_country_from_name::case_3", + "original_name": "util::tests::t_country_from_name::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.65+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_country_from_name::case_1", + "original_name": "util::tests::t_country_from_name::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.648+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr::case_2", + "original_name": "util::tests::t_parse_large_numstr::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.652+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr::case_1", + "original_name": "util::tests::t_parse_large_numstr::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.651+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr::case_4", + "original_name": "util::tests::t_parse_large_numstr::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.652+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr::case_3", + "original_name": "util::tests::t_parse_large_numstr::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.652+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr::case_5", + "original_name": "util::tests::t_parse_large_numstr::case_5", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.654+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr::case_7", + "original_name": "util::tests::t_parse_large_numstr::case_7", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.655+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr::case_6", + "original_name": "util::tests::t_parse_large_numstr::case_6", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.654+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_num::case_2", + "original_name": "util::tests::t_parse_num::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.657+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_numeric_vec::case_1", + "original_name": "util::tests::t_parse_numeric_vec::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.658+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_num::case_1", + "original_name": "util::tests::t_parse_num::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.657+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_numeric_vec::case_3", + "original_name": "util::tests::t_parse_numeric_vec::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.659+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_numeric_vec::case_2", + "original_name": "util::tests::t_parse_numeric_vec::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.658+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_video_length::case_3", + "original_name": "util::tests::t_parse_video_length::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.66+02:00", + "time": 0.01, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_retry_delay::case_2", + "original_name": "util::tests::t_retry_delay::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.663+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_video_length::case_1", + "original_name": "util::tests::t_parse_video_length::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.66+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_retry_delay::case_1", + "original_name": "util::tests::t_retry_delay::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.663+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_retry_delay::case_4", + "original_name": "util::tests::t_retry_delay::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.665+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_sanitize_yt_url::case_1", + "original_name": "util::tests::t_sanitize_yt_url::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.665+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_video_length::case_2", + "original_name": "util::tests::t_parse_video_length::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.66+02:00", + "time": 0.013, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_sanitize_yt_url::case_2", + "original_name": "util::tests::t_sanitize_yt_url::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.666+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_video_length::case_6", + "original_name": "util::tests::t_parse_video_length::case_6", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.662+02:00", + "time": 0.012, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_video_length::case_5", + "original_name": "util::tests::t_parse_video_length::case_5", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.662+02:00", + "time": 0.013, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_sanitize_yt_url::case_3", + "original_name": "util::tests::t_sanitize_yt_url::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.666+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_retry_delay::case_3", + "original_name": "util::tests::t_retry_delay::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.664+02:00", + "time": 0.012, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_video_length::case_4", + "original_name": "util::tests::t_parse_video_length::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.661+02:00", + "time": 0.016, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_vec_try_remove", + "original_name": "util::tests::t_vec_try_remove", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.671+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_vec_try_swap_remove", + "original_name": "util::tests::t_vec_try_swap_remove", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.672+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse::case_1_de", + "original_name": "util::timeago::tests::t_parse::case_1_de", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.672+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse::case_3_nbsp", + "original_name": "util::timeago::tests::t_parse::case_3_nbsp", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.673+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse::case_2_ar", + "original_name": "util::timeago::tests::t_parse::case_2_ar", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.673+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_sanitize_yt_url::case_4", + "original_name": "util::tests::t_sanitize_yt_url::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.671+02:00", + "time": 0.01, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_date::case_1", + "original_name": "util::timeago::tests::t_parse_date::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.674+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_date::case_3", + "original_name": "util::timeago::tests::t_parse_date::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.675+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_date::case_2", + "original_name": "util::timeago::tests::t_parse_date::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.674+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_date::case_4", + "original_name": "util::timeago::tests::t_parse_date::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.675+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_date::case_5", + "original_name": "util::timeago::tests::t_parse_date::case_5", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.675+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_date::case_6", + "original_name": "util::timeago::tests::t_parse_date::case_6", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.676+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_video_duration2::case_1", + "original_name": "util::timeago::tests::t_parse_video_duration2::case_1", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.679+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_video_duration2::case_2", + "original_name": "util::timeago::tests::t_parse_video_duration2::case_2", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.681+02:00", + "time": 0.008, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_video_duration2::case_4", + "original_name": "util::timeago::tests::t_parse_video_duration2::case_4", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.681+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_video_duration2::case_5", + "original_name": "util::timeago::tests::t_parse_video_duration2::case_5", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.681+02:00", + "time": 0.007, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_video_duration2::case_3", + "original_name": "util::timeago::tests::t_parse_video_duration2::case_3", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.681+02:00", + "time": 0.009, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_to_datetime", + "original_name": "util::timeago::tests::t_to_datetime", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.683+02:00", + "time": 0.011, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::client::trends::tests::map_trending::case_1_base", + "original_name": "client::trends::tests::map_trending::case_1_base", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.33+02:00", + "time": 0.367, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_date_samples", + "original_name": "util::timeago::tests::t_parse_date_samples", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.677+02:00", + "time": 0.026, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_timeago_table", + "original_name": "util::timeago::tests::t_timeago_table", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.683+02:00", + "time": 0.039, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_testfile_short", + "original_name": "util::timeago::tests::t_testfile_short", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.683+02:00", + "time": 0.039, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_testfile", + "original_name": "util::timeago::tests::t_testfile", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.682+02:00", + "time": 0.04, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::timeago::tests::t_parse_video_duration", + "original_name": "util::timeago::tests::t_parse_video_duration", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.679+02:00", + "time": 0.07, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::util::tests::t_parse_large_numstr_samples", + "original_name": "util::tests::t_parse_large_numstr_samples", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.656+02:00", + "time": 0.235, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "rustypipe::deobfuscate::tests::t_update", + "original_name": "deobfuscate::tests::t_update", + "classname": "rustypipe", + "timestamp": "2024-05-31T22:35:31.515+02:00", + "time": 1.806, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + } + ], + "time": 0.0, + "tests": 248, + "errors": 0, + "failures": 0, + "skipped": 0, + "flaky": 0 + } + ], + "time": 8.219, + "tests": 418, + "errors": 0, + "failures": 5, + "skipped": 0, + "flaky": 0 +} diff --git a/crates/junit-parser/src/snapshots/junit_parser__tests__parse_vite.snap b/crates/junit-parser/src/snapshots/junit_parser__tests__parse_vite.snap new file mode 100644 index 0000000..e9c5e9a --- /dev/null +++ b/crates/junit-parser/src/snapshots/junit_parser__tests__parse_vite.snap @@ -0,0 +1,824 @@ +--- +source: crates/junit-parser/src/lib.rs +expression: suites +--- +{ + "name": "vitest tests", + "suites": [ + { + "name": "src/lib/server/query/util.test.ts", + "timestamp": "2024-06-04T11:43:17.788Z", + "cases": [ + { + "name": "src/lib/server/query/util.test.ts::query builder", + "original_name": "query builder", + "classname": "src/lib/server/query/util.test.ts", + "timestamp": null, + "time": 0.002, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/server/query/util.test.ts::parse search query", + "original_name": "parse search query", + "classname": "src/lib/server/query/util.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/server/query/util.test.ts::mapSortFields", + "original_name": "mapSortFields", + "classname": "src/lib/server/query/util.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + } + ], + "time": 0.006, + "tests": 3, + "errors": 0, + "failures": 0, + "skipped": 0, + "flaky": 0 + }, + { + "name": "src/lib/shared/model/validation.test.ts", + "timestamp": "2024-06-04T11:43:17.789Z", + "cases": [ + { + "name": "src/lib/shared/model/validation.test.ts::date string", + "original_name": "date string", + "classname": "src/lib/shared/model/validation.test.ts", + "timestamp": null, + "time": 0.002, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/model/validation.test.ts::filter data", + "original_name": "filter data", + "classname": "src/lib/shared/model/validation.test.ts", + "timestamp": null, + "time": 0.002, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + } + ], + "time": 0.004, + "tests": 2, + "errors": 0, + "failures": 0, + "skipped": 0, + "flaky": 0 + }, + { + "name": "src/lib/shared/util/colors.test.ts", + "timestamp": "2024-06-04T11:43:17.79Z", + "cases": [ + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex", + "original_name": "color conversion > colorToHex", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor", + "original_name": "color conversion > hexToColor", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex", + "original_name": "color conversion > colorToHex", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor", + "original_name": "color conversion > hexToColor", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex", + "original_name": "color conversion > colorToHex", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor", + "original_name": "color conversion > hexToColor", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex", + "original_name": "color conversion > colorToHex", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor", + "original_name": "color conversion > hexToColor", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex", + "original_name": "color conversion > colorToHex", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor", + "original_name": "color conversion > hexToColor", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex", + "original_name": "color conversion > colorToHex", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor", + "original_name": "color conversion > hexToColor", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex", + "original_name": "color conversion > colorToHex", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor", + "original_name": "color conversion > hexToColor", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex", + "original_name": "color conversion > colorToHex", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor", + "original_name": "color conversion > hexToColor", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex", + "original_name": "color conversion > colorToHex", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor", + "original_name": "color conversion > hexToColor", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > colorToHex", + "original_name": "color conversion > colorToHex", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/colors.test.ts::color conversion > hexToColor", + "original_name": "color conversion > hexToColor", + "classname": "src/lib/shared/util/colors.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + } + ], + "time": 0.005, + "tests": 20, + "errors": 0, + "failures": 0, + "skipped": 0, + "flaky": 0 + }, + { + "name": "src/lib/shared/util/date.test.ts", + "timestamp": "2024-06-04T11:43:17.792Z", + "cases": [ + { + "name": "src/lib/shared/util/date.test.ts::formatDate", + "original_name": "formatDate", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.014, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::dateFromYMD", + "original_name": "dateFromYMD", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::dateFromYMD", + "original_name": "dateFromYMD", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::utcDateToYMD", + "original_name": "utcDateToYMD", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::dateToYMD", + "original_name": "dateToYMD", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::humanDate", + "original_name": "humanDate", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::humanDate", + "original_name": "humanDate", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::humanDate", + "original_name": "humanDate", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::humanDate", + "original_name": "humanDate", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::humanDate", + "original_name": "humanDate", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::humanDate", + "original_name": "humanDate", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::humanDate", + "original_name": "humanDate", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::humanDate", + "original_name": "humanDate", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.002, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::humanDate", + "original_name": "humanDate", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::humanDate", + "original_name": "humanDate", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::humanDate", + "original_name": "humanDate", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::humanDate", + "original_name": "humanDate", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::humanDate", + "original_name": "humanDate", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::parse daterange ''", + "original_name": "parse daterange ''", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::parse daterange '..'", + "original_name": "parse daterange '..'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::parse daterange 'foo..bar'", + "original_name": "parse daterange 'foo..bar'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::parse daterange '2024-04-15'", + "original_name": "parse daterange '2024-04-15'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::parse daterange '2024-04-13..2024-04-20'", + "original_name": "parse daterange '2024-04-13..2024-04-20'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::parse daterange '2024-04-13..'", + "original_name": "parse daterange '2024-04-13..'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::parse daterange '..2024-04-20'", + "original_name": "parse daterange '..2024-04-20'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::shiftDateRange '..2024-04-14'", + "original_name": "shiftDateRange '..2024-04-14'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::shiftDateRange '..2024-04-14'", + "original_name": "shiftDateRange '..2024-04-14'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-08..'", + "original_name": "shiftDateRange '2024-04-08..'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-08..'", + "original_name": "shiftDateRange '2024-04-08..'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-08..2024-04-14'", + "original_name": "shiftDateRange '2024-04-08..2024-04-14'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-08..2024-04-14'", + "original_name": "shiftDateRange '2024-04-08..2024-04-14'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-13..2024-04-16'", + "original_name": "shiftDateRange '2024-04-13..2024-04-16'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-13..2024-04-16'", + "original_name": "shiftDateRange '2024-04-13..2024-04-16'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-13..2024-04-13'", + "original_name": "shiftDateRange '2024-04-13..2024-04-13'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-13..2024-04-13'", + "original_name": "shiftDateRange '2024-04-13..2024-04-13'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-08..2024-04-14'", + "original_name": "shiftDateRange '2024-04-08..2024-04-14'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::shiftDateRange '2024-04-08..2024-04-14'", + "original_name": "shiftDateRange '2024-04-08..2024-04-14'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::shiftDateRange '..2024-04-14'", + "original_name": "shiftDateRange '..2024-04-14'", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.001, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/date.test.ts::dateFromHuman", + "original_name": "dateFromHuman", + "classname": "src/lib/shared/util/date.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + } + ], + "time": 0.027, + "tests": 39, + "errors": 0, + "failures": 0, + "skipped": 0, + "flaky": 0 + }, + { + "name": "src/lib/shared/util/diff.test.ts", + "timestamp": "2024-06-04T11:43:17.795Z", + "cases": [ + { + "name": "src/lib/shared/util/diff.test.ts::versions diff", + "original_name": "versions diff", + "classname": "src/lib/shared/util/diff.test.ts", + "timestamp": null, + "time": 0.003, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + } + ], + "time": 0.003, + "tests": 1, + "errors": 0, + "failures": 0, + "skipped": 0, + "flaky": 0 + }, + { + "name": "src/lib/shared/util/util.test.ts", + "timestamp": "2024-06-04T11:43:17.795Z", + "cases": [ + { + "name": "src/lib/shared/util/util.test.ts::getQueryUrl", + "original_name": "getQueryUrl", + "classname": "src/lib/shared/util/util.test.ts", + "timestamp": null, + "time": 0.005, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + }, + { + "name": "src/lib/shared/util/util.test.ts::normalizeLineEndings", + "original_name": "normalizeLineEndings", + "classname": "src/lib/shared/util/util.test.ts", + "timestamp": null, + "time": 0.0, + "status": "Success", + "system_out": null, + "system_err": null, + "retries": [] + } + ], + "time": 0.005, + "tests": 2, + "errors": 0, + "failures": 0, + "skipped": 0, + "flaky": 0 + } + ], + "time": 1.371, + "tests": 67, + "errors": 0, + "failures": 0, + "skipped": 0, + "flaky": 0 +} diff --git a/crates/junit-parser/testfiles/retry.junit.xml b/crates/junit-parser/testfiles/retry.junit.xml new file mode 100644 index 0000000..cf2ba5b --- /dev/null +++ b/crates/junit-parser/testfiles/retry.junit.xml @@ -0,0 +1,1859 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + thread 'get_player_from_client::case_4_ios' panicked at + tests/youtube.rs:71:14: + video #247 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + thread + 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14: video + #247 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + running 1 test + test get_player_from_client::case_4_ios ... FAILED + + failures: + + failures: + get_player_from_client::case_4_ios + + test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished + in 0.16s + + + thread + 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14: + video #247 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + thread + 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14: video + #247 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + running 1 test + test get_player_from_client::case_4_ios ... FAILED + + failures: + + failures: + get_player_from_client::case_4_ios + + test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished + in 0.16s + + + thread + 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14: + video #247 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + thread + 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14: video + #247 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + running 1 test + test get_player_from_client::case_4_ios ... FAILED + + failures: + + failures: + get_player_from_client::case_4_ios + + test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished + in 0.17s + + + thread + 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14: + video #247 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + + running 1 test + test get_player_from_client::case_4_ios ... FAILED + + failures: + + failures: + get_player_from_client::case_4_ios + + test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished + in 0.20s + + + thread 'get_player_from_client::case_4_ios' panicked at + tests/youtube.rs:71:14: + video #247 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + thread + 'music_charts::case_2_us' panicked at tests/youtube.rs:2494:9: expected >= 8 + charts playlists, got 0 note: run with `RUST_BACKTRACE=1` environment variable to display a + backtrace + running 1 test + test music_charts::case_2_us ... FAILED + + failures: + + failures: + music_charts::case_2_us + + test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished + in 0.33s + + + thread + 'music_charts::case_2_us' panicked at tests/youtube.rs:2494:9: + expected >= 8 charts playlists, got 0 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + + + + + + + + + + + + + thread 'music_album::case_8_version_no_artist' panicked + at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_version_no_artist' failed in line 1593 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + thread + 'music_album::case_8_version_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_version_no_artist' failed in line 1593 note: + run with `RUST_BACKTRACE=1` environment variable to display a backtrace + running 1 test + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + Snapshot file: tests/snapshots/youtube__music_album_version_no_artist.snap + Snapshot: music_album_version_no_artist + Source: tests/youtube.rs:1593 + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + Expression: album + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + -old snapshot + +new results + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + 122 122 โ”‚ width: 544, + 123 123 โ”‚ height: 544, + 124 124 โ”‚ ), + 125 125 โ”‚ ], + 126 โ”‚- artists: [], + 127 โ”‚- artist_id: None, + 126 โ”‚+ artists: [ + 127 โ”‚+ ArtistId( + 128 โ”‚+ id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 129 โ”‚+ name: "S.p. Kodandapani", + 130 โ”‚+ ), + 131 โ”‚+ ArtistId( + 132 โ”‚+ id: Some("UCbeyiCEAJt5buhxxLAPwVMg"), + 133 โ”‚+ name: "S. P. Kodandapani", + 134 โ”‚+ ), + 135 โ”‚+ ], + 136 โ”‚+ artist_id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 128 137 โ”‚ album_type: Ep, + 129 138 โ”‚ year: None, + 130 โ”‚- by_va: true, + 139 โ”‚+ by_va: false, + 131 140 โ”‚ ), + 132 141 โ”‚ ], + 133 142 โ”‚ ) + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + To update snapshots run `cargo insta review` + Stopped on the first failure. Run `cargo insta test` to run all snapshots. + test music_album::case_8_version_no_artist ... FAILED + + failures: + + failures: + music_album::case_8_version_no_artist + + test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished + in 0.32s + + + stored + new snapshot + /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_version_no_artist.snap.new + thread 'music_album::case_8_version_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_version_no_artist' failed in line 1593 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + thread + 'music_album::case_8_version_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_version_no_artist' failed in line 1593 note: + run with `RUST_BACKTRACE=1` environment variable to display a backtrace + running 1 test + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + Snapshot file: tests/snapshots/youtube__music_album_version_no_artist.snap + Snapshot: music_album_version_no_artist + Source: tests/youtube.rs:1593 + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + Expression: album + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + -old snapshot + +new results + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + 122 122 โ”‚ width: 544, + 123 123 โ”‚ height: 544, + 124 124 โ”‚ ), + 125 125 โ”‚ ], + 126 โ”‚- artists: [], + 127 โ”‚- artist_id: None, + 126 โ”‚+ artists: [ + 127 โ”‚+ ArtistId( + 128 โ”‚+ id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 129 โ”‚+ name: "S.p. Kodandapani", + 130 โ”‚+ ), + 131 โ”‚+ ArtistId( + 132 โ”‚+ id: Some("UCbeyiCEAJt5buhxxLAPwVMg"), + 133 โ”‚+ name: "S. P. Kodandapani", + 134 โ”‚+ ), + 135 โ”‚+ ], + 136 โ”‚+ artist_id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 128 137 โ”‚ album_type: Ep, + 129 138 โ”‚ year: None, + 130 โ”‚- by_va: true, + 139 โ”‚+ by_va: false, + 131 140 โ”‚ ), + 132 141 โ”‚ ], + 133 142 โ”‚ ) + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + To update snapshots run `cargo insta review` + Stopped on the first failure. Run `cargo insta test` to run all snapshots. + test music_album::case_8_version_no_artist ... FAILED + + failures: + + failures: + music_album::case_8_version_no_artist + + test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished + in 0.21s + + + stored + new snapshot + /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_version_no_artist.snap.new + thread 'music_album::case_8_version_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_version_no_artist' failed in line 1593 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + thread + 'music_album::case_8_version_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_version_no_artist' failed in line 1593 note: + run with `RUST_BACKTRACE=1` environment variable to display a backtrace + running 1 test + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + Snapshot file: tests/snapshots/youtube__music_album_version_no_artist.snap + Snapshot: music_album_version_no_artist + Source: tests/youtube.rs:1593 + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + Expression: album + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + -old snapshot + +new results + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + 122 122 โ”‚ width: 544, + 123 123 โ”‚ height: 544, + 124 124 โ”‚ ), + 125 125 โ”‚ ], + 126 โ”‚- artists: [], + 127 โ”‚- artist_id: None, + 126 โ”‚+ artists: [ + 127 โ”‚+ ArtistId( + 128 โ”‚+ id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 129 โ”‚+ name: "S.p. Kodandapani", + 130 โ”‚+ ), + 131 โ”‚+ ArtistId( + 132 โ”‚+ id: Some("UCbeyiCEAJt5buhxxLAPwVMg"), + 133 โ”‚+ name: "S. P. Kodandapani", + 134 โ”‚+ ), + 135 โ”‚+ ], + 136 โ”‚+ artist_id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 128 137 โ”‚ album_type: Ep, + 129 138 โ”‚ year: None, + 130 โ”‚- by_va: true, + 139 โ”‚+ by_va: false, + 131 140 โ”‚ ), + 132 141 โ”‚ ], + 133 142 โ”‚ ) + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + To update snapshots run `cargo insta review` + Stopped on the first failure. Run `cargo insta test` to run all snapshots. + test music_album::case_8_version_no_artist ... FAILED + + failures: + + failures: + music_album::case_8_version_no_artist + + test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished + in 0.27s + + + stored + new snapshot + /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_version_no_artist.snap.new + thread 'music_album::case_8_version_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_version_no_artist' failed in line 1593 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + + running 1 test + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + Snapshot file: tests/snapshots/youtube__music_album_version_no_artist.snap + Snapshot: music_album_version_no_artist + Source: tests/youtube.rs:1593 + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + Expression: album + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + -old snapshot + +new results + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + 122 122 โ”‚ width: 544, + 123 123 โ”‚ height: 544, + 124 124 โ”‚ ), + 125 125 โ”‚ ], + 126 โ”‚- artists: [], + 127 โ”‚- artist_id: None, + 126 โ”‚+ artists: [ + 127 โ”‚+ ArtistId( + 128 โ”‚+ id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 129 โ”‚+ name: "S.p. Kodandapani", + 130 โ”‚+ ), + 131 โ”‚+ ArtistId( + 132 โ”‚+ id: Some("UCbeyiCEAJt5buhxxLAPwVMg"), + 133 โ”‚+ name: "S. P. Kodandapani", + 134 โ”‚+ ), + 135 โ”‚+ ], + 136 โ”‚+ artist_id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 128 137 โ”‚ album_type: Ep, + 129 138 โ”‚ year: None, + 130 โ”‚- by_va: true, + 139 โ”‚+ by_va: false, + 131 140 โ”‚ ), + 132 141 โ”‚ ], + 133 142 โ”‚ ) + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + To update snapshots run `cargo insta review` + Stopped on the first failure. Run `cargo insta test` to run all snapshots. + test music_album::case_8_version_no_artist ... FAILED + + failures: + + failures: + music_album::case_8_version_no_artist + + test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished + in 0.30s + + + stored new snapshot + /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_version_no_artist.snap.new + thread 'music_album::case_8_version_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_version_no_artist' failed in line 1593 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + + + + + + + + + + thread 'music_album::case_9_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_no_artist' failed in line 1593 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + thread + 'music_album::case_9_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_no_artist' failed in line 1593 note: run with + `RUST_BACKTRACE=1` environment variable to display a backtrace + running 1 test + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + Snapshot file: tests/snapshots/youtube__music_album_no_artist.snap + Snapshot: music_album_no_artist + Source: tests/youtube.rs:1593 + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + Expression: album + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + -old snapshot + +new results + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + 1 1 โ”‚ id: "MPREb_bqWA6mAZFWS", + 2 2 โ”‚ playlist_id: Some("OLAK5uy_mUiRbMqeQXFUH6h9KB87RcEmNtm45Qvs0"), + 3 3 โ”‚ name: "Pedha Rasi Peddamma Katha", + 4 4 โ”‚ cover: "[cover]", + 5 โ”‚- artists: [], + 6 โ”‚- artist_id: None, + 5 โ”‚+ artists: [ + 6 โ”‚+ ArtistId( + 7 โ”‚+ id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 8 โ”‚+ name: "S.p. Kodandapani", + 9 โ”‚+ ), + 10 โ”‚+ ArtistId( + 11 โ”‚+ id: Some("UCbeyiCEAJt5buhxxLAPwVMg"), + 12 โ”‚+ name: "S. P. Kodandapani", + 13 โ”‚+ ), + 14 โ”‚+ ], + 15 โ”‚+ artist_id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 7 16 โ”‚ description: None, + 8 17 โ”‚ album_type: Ep, + 9 18 โ”‚ year: Some(1968), + 10 19 โ”‚ by_va: false, + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + To update snapshots run `cargo insta review` + Stopped on the first failure. Run `cargo insta test` to run all snapshots. + test music_album::case_9_no_artist ... FAILED + + failures: + + failures: + music_album::case_9_no_artist + + test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished + in 0.25s + + + stored + new snapshot + /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_no_artist.snap.new + thread 'music_album::case_9_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_no_artist' failed in line 1593 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + thread + 'music_album::case_9_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_no_artist' failed in line 1593 note: run with + `RUST_BACKTRACE=1` environment variable to display a backtrace + running 1 test + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + Snapshot file: tests/snapshots/youtube__music_album_no_artist.snap + Snapshot: music_album_no_artist + Source: tests/youtube.rs:1593 + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + Expression: album + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + -old snapshot + +new results + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + 1 1 โ”‚ id: "MPREb_bqWA6mAZFWS", + 2 2 โ”‚ playlist_id: Some("OLAK5uy_mUiRbMqeQXFUH6h9KB87RcEmNtm45Qvs0"), + 3 3 โ”‚ name: "Pedha Rasi Peddamma Katha", + 4 4 โ”‚ cover: "[cover]", + 5 โ”‚- artists: [], + 6 โ”‚- artist_id: None, + 5 โ”‚+ artists: [ + 6 โ”‚+ ArtistId( + 7 โ”‚+ id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 8 โ”‚+ name: "S.p. Kodandapani", + 9 โ”‚+ ), + 10 โ”‚+ ArtistId( + 11 โ”‚+ id: Some("UCbeyiCEAJt5buhxxLAPwVMg"), + 12 โ”‚+ name: "S. P. Kodandapani", + 13 โ”‚+ ), + 14 โ”‚+ ], + 15 โ”‚+ artist_id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 7 16 โ”‚ description: None, + 8 17 โ”‚ album_type: Ep, + 9 18 โ”‚ year: Some(1968), + 10 19 โ”‚ by_va: false, + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + To update snapshots run `cargo insta review` + Stopped on the first failure. Run `cargo insta test` to run all snapshots. + test music_album::case_9_no_artist ... FAILED + + failures: + + failures: + music_album::case_9_no_artist + + test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished + in 0.25s + + + stored + new snapshot + /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_no_artist.snap.new + thread 'music_album::case_9_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_no_artist' failed in line 1593 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + thread + 'music_album::case_9_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_no_artist' failed in line 1593 note: run with + `RUST_BACKTRACE=1` environment variable to display a backtrace + running 1 test + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + Snapshot file: tests/snapshots/youtube__music_album_no_artist.snap + Snapshot: music_album_no_artist + Source: tests/youtube.rs:1593 + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + Expression: album + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + -old snapshot + +new results + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + 1 1 โ”‚ id: "MPREb_bqWA6mAZFWS", + 2 2 โ”‚ playlist_id: Some("OLAK5uy_mUiRbMqeQXFUH6h9KB87RcEmNtm45Qvs0"), + 3 3 โ”‚ name: "Pedha Rasi Peddamma Katha", + 4 4 โ”‚ cover: "[cover]", + 5 โ”‚- artists: [], + 6 โ”‚- artist_id: None, + 5 โ”‚+ artists: [ + 6 โ”‚+ ArtistId( + 7 โ”‚+ id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 8 โ”‚+ name: "S.p. Kodandapani", + 9 โ”‚+ ), + 10 โ”‚+ ArtistId( + 11 โ”‚+ id: Some("UCbeyiCEAJt5buhxxLAPwVMg"), + 12 โ”‚+ name: "S. P. Kodandapani", + 13 โ”‚+ ), + 14 โ”‚+ ], + 15 โ”‚+ artist_id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 7 16 โ”‚ description: None, + 8 17 โ”‚ album_type: Ep, + 9 18 โ”‚ year: Some(1968), + 10 19 โ”‚ by_va: false, + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + To update snapshots run `cargo insta review` + Stopped on the first failure. Run `cargo insta test` to run all snapshots. + test music_album::case_9_no_artist ... FAILED + + failures: + + failures: + music_album::case_9_no_artist + + test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished + in 0.30s + + + stored + new snapshot + /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_no_artist.snap.new + thread 'music_album::case_9_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_no_artist' failed in line 1593 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + + running 1 test + โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” + Snapshot file: tests/snapshots/youtube__music_album_no_artist.snap + Snapshot: music_album_no_artist + Source: tests/youtube.rs:1593 + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + Expression: album + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + -old snapshot + +new results + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + 1 1 โ”‚ id: "MPREb_bqWA6mAZFWS", + 2 2 โ”‚ playlist_id: Some("OLAK5uy_mUiRbMqeQXFUH6h9KB87RcEmNtm45Qvs0"), + 3 3 โ”‚ name: "Pedha Rasi Peddamma Katha", + 4 4 โ”‚ cover: "[cover]", + 5 โ”‚- artists: [], + 6 โ”‚- artist_id: None, + 5 โ”‚+ artists: [ + 6 โ”‚+ ArtistId( + 7 โ”‚+ id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 8 โ”‚+ name: "S.p. Kodandapani", + 9 โ”‚+ ), + 10 โ”‚+ ArtistId( + 11 โ”‚+ id: Some("UCbeyiCEAJt5buhxxLAPwVMg"), + 12 โ”‚+ name: "S. P. Kodandapani", + 13 โ”‚+ ), + 14 โ”‚+ ], + 15 โ”‚+ artist_id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 7 16 โ”‚ description: None, + 8 17 โ”‚ album_type: Ep, + 9 18 โ”‚ year: Some(1968), + 10 19 โ”‚ by_va: false, + โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + To update snapshots run `cargo insta review` + Stopped on the first failure. Run `cargo insta test` to run all snapshots. + test music_album::case_9_no_artist ... FAILED + + failures: + + failures: + music_album::case_9_no_artist + + test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished + in 0.30s + + + stored new snapshot + /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_no_artist.snap.new + thread 'music_album::case_9_no_artist' panicked at + /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: + snapshot assertion for 'music_album_no_artist' failed in line 1593 + note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crates/junit-parser/testfiles/simple.junit.xml b/crates/junit-parser/testfiles/simple.junit.xml new file mode 100644 index 0000000..03e9e92 --- /dev/null +++ b/crates/junit-parser/testfiles/simple.junit.xml @@ -0,0 +1,1011 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + thread 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14: +video #247 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + +running 1 test +test get_player_from_client::case_4_ios ... FAILED + +failures: + +failures: + get_player_from_client::case_4_ios + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished in 0.24s + + + thread 'get_player_from_client::case_4_ios' panicked at tests/youtube.rs:71:14: +video #247 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + thread 'music_album::case_8_version_no_artist' panicked at /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: +snapshot assertion for 'music_album_version_no_artist' failed in line 1593 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + +running 1 test +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” +Snapshot file: tests/snapshots/youtube__music_album_version_no_artist.snap +Snapshot: music_album_version_no_artist +Source: tests/youtube.rs:1593 +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +Expression: album +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +-old snapshot ++new results +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + 122 122 โ”‚ width: 544, + 123 123 โ”‚ height: 544, + 124 124 โ”‚ ), + 125 125 โ”‚ ], + 126 โ”‚- artists: [], + 127 โ”‚- artist_id: None, + 126 โ”‚+ artists: [ + 127 โ”‚+ ArtistId( + 128 โ”‚+ id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 129 โ”‚+ name: "S.p. Kodandapani", + 130 โ”‚+ ), + 131 โ”‚+ ArtistId( + 132 โ”‚+ id: Some("UCbeyiCEAJt5buhxxLAPwVMg"), + 133 โ”‚+ name: "S. P. Kodandapani", + 134 โ”‚+ ), + 135 โ”‚+ ], + 136 โ”‚+ artist_id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 128 137 โ”‚ album_type: Ep, + 129 138 โ”‚ year: None, + 130 โ”‚- by_va: true, + 139 โ”‚+ by_va: false, + 131 140 โ”‚ ), + 132 141 โ”‚ ], + 133 142 โ”‚ ) +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +To update snapshots run `cargo insta review` +Stopped on the first failure. Run `cargo insta test` to run all snapshots. +test music_album::case_8_version_no_artist ... FAILED + +failures: + +failures: + music_album::case_8_version_no_artist + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished in 0.23s + + + stored new snapshot /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_version_no_artist.snap.new +thread 'music_album::case_8_version_no_artist' panicked at /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: +snapshot assertion for 'music_album_version_no_artist' failed in line 1593 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + + + + thread 'music_album::case_9_no_artist' panicked at /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: +snapshot assertion for 'music_album_no_artist' failed in line 1593 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + +running 1 test +โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Snapshot Summary โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” +Snapshot file: tests/snapshots/youtube__music_album_no_artist.snap +Snapshot: music_album_no_artist +Source: tests/youtube.rs:1593 +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +Expression: album +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +-old snapshot ++new results +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ + 1 1 โ”‚ id: "MPREb_bqWA6mAZFWS", + 2 2 โ”‚ playlist_id: Some("OLAK5uy_mUiRbMqeQXFUH6h9KB87RcEmNtm45Qvs0"), + 3 3 โ”‚ name: "Pedha Rasi Peddamma Katha", + 4 4 โ”‚ cover: "[cover]", + 5 โ”‚- artists: [], + 6 โ”‚- artist_id: None, + 5 โ”‚+ artists: [ + 6 โ”‚+ ArtistId( + 7 โ”‚+ id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 8 โ”‚+ name: "S.p. Kodandapani", + 9 โ”‚+ ), + 10 โ”‚+ ArtistId( + 11 โ”‚+ id: Some("UCbeyiCEAJt5buhxxLAPwVMg"), + 12 โ”‚+ name: "S. P. Kodandapani", + 13 โ”‚+ ), + 14 โ”‚+ ], + 15 โ”‚+ artist_id: Some("UCJ59s7i18Dxj_pKVIGETNow"), + 7 16 โ”‚ description: None, + 8 17 โ”‚ album_type: Ep, + 9 18 โ”‚ year: Some(1968), + 10 19 โ”‚ by_va: false, +โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +To update snapshots run `cargo insta review` +Stopped on the first failure. Run `cargo insta test` to run all snapshots. +test music_album::case_9_no_artist ... FAILED + +failures: + +failures: + music_album::case_9_no_artist + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished in 0.28s + + + stored new snapshot /home/thetadev/Documents/Programmieren/Rust/rustypipe/tests/snapshots/youtube__music_album_no_artist.snap.new +thread 'music_album::case_9_no_artist' panicked at /home/thetadev/.cargo/registry/src/index.crates.io-6f17d22bba15001f/insta-1.38.0/src/runtime.rs:563:9: +snapshot assertion for 'music_album_no_artist' failed in line 1593 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + thread 'music_playlist_cont::case_2_ytm' panicked at tests/youtube.rs:1534:5: +expected >= 227 tracks, got 128 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + +running 1 test +test music_playlist_cont::case_2_ytm ... FAILED + +failures: + +failures: + music_playlist_cont::case_2_ytm + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished in 1.15s + + + thread 'music_playlist_cont::case_2_ytm' panicked at tests/youtube.rs:1534:5: +expected >= 227 tracks, got 128 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + thread 'search' panicked at tests/youtube.rs:2751:9: +expected >= 10 items on page 2, got 4 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + +running 1 test +test search ... FAILED + +failures: + +failures: + search + +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 170 filtered out; finished in 2.17s + + + thread 'search' panicked at tests/youtube.rs:2751:9: +expected >= 10 items on page 2, got 4 +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/crates/junit-parser/testfiles/vite.junit.xml b/crates/junit-parser/testfiles/vite.junit.xml new file mode 100644 index 0000000..7733a52 --- /dev/null +++ b/crates/junit-parser/testfiles/vite.junit.xml @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..5169721 --- /dev/null +++ b/renovate.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:best-practices", + ":semanticCommitTypeAll(chore)", + ":preserveSemverRanges" + ], + "automerge": true, + "automergeStrategy": "squash", + "osvVulnerabilityAlerts": true, + "labels": ["dependency-upgrade"], + "enabledManagers": ["cargo"], + "prHourlyLimit": 5 +} diff --git a/resources/content.css b/resources/content.css new file mode 100644 index 0000000..68f7ffa --- /dev/null +++ b/resources/content.css @@ -0,0 +1,699 @@ +/* Additional stylesheet for artifactview content viewer */ + +.viewer > pre { + padding: 10px 20px; + font-size: 14px; + overflow-x: auto; +} + +pre, +code { + color: #cccccc; + background-color: #1c1c1c; +} + +.prose { + margin: 20px 20px 0 20px; + max-width: 800px; + word-wrap: break-word; + overflow: hidden; + font-size: 16px; + line-height: 1.5 !important; +} +.prose > :first-child { + margin-top: 0 !important; +} +.prose > :last-child { + margin-bottom: 0 !important; +} +.prose h1, +.prose h2, +.prose h3, +.prose h4, +.prose h5, +.prose h6 { + font-weight: 600; + margin-top: 24px; + margin-bottom: 16px; + line-height: 1.25; +} +.prose h1 tt, +.prose h1 code, +.prose h2 tt, +.prose h2 code, +.prose h3 tt, +.prose h3 code, +.prose h4 tt, +.prose h4 code, +.prose h5 tt, +.prose h5 code, +.prose h6 tt, +.prose h6 code { + font-size: inherit; +} +.prose h1 { + border-bottom: 1px solid var(--color-border2); + padding-bottom: 0.3em; + font-size: 2em; +} +.prose h2 { + border-bottom: 1px solid var(--color-border2); + padding-bottom: 0.3em; + font-size: 1.5em; +} +.prose h3 { + font-size: 1.25em; +} +.prose h4 { + font-size: 1em; +} +.prose h5 { + font-size: 0.875em; +} +.prose h6 { + color: var(--color-text-light); + font-size: 0.85em; +} +.prose p, +.prose blockquote, +.prose details, +.prose ul, +.prose ol, +.prose dl, +.prose table, +.prose pre { + margin-top: 0; + margin-bottom: 16px; +} +.prose hr { + background-color: var(--color-secondary); + border: 0; + height: 4px; + margin: 16px 0; + padding: 0; +} +.prose ul, +.prose ol { + padding-left: 2em; +} +.prose ul ul, +.prose ul ol, +.prose ol ol, +.prose ol ul { + margin-top: 0; + margin-bottom: 0; +} +.prose ol ol, +.prose ul ol { + list-style-type: lower-roman; +} +.prose li > p { + margin-top: 16px; +} +.prose li + li { + margin-top: 0.25em; +} +.prose dl { + padding: 0; +} +.prose dl dt { + font-size: 1em; + font-style: italic; + font-weight: 600; + margin-top: 16px; + padding: 0; +} +.prose dl dd { + margin-bottom: 16px; + padding: 0 16px; +} +.prose blockquote { + color: var(--color-text-light); + border-left: 4px solid var(--color-secondary); + margin-left: 0; + padding: 0 15px; +} +.prose blockquote > :first-child { + margin-top: 0; +} +.prose blockquote > :last-child { + margin-bottom: 0; +} +.prose table { + width: max-content; + max-width: 100%; + display: block; + overflow: auto; + border-collapse: collapse; +} +.prose table th { + font-weight: 600; +} +.prose table th, +.prose table td { + border: 1px solid var(--color-secondary) !important; + padding: 6px 13px !important; +} +.prose table tr { + border-top: 1px solid var(--color-secondary); +} +.prose table tr:nth-child(2n) { + background-color: var(--color-secondary); +} +.prose img, +.prose video { + box-sizing: initial; + max-width: 100%; +} +.prose img[align="right"], +.prose video[align="right"] { + padding-left: 20px; +} +.prose img[align="left"], +.prose video[align="left"] { + padding-right: 28px; +} +.prose code { + white-space: break-spaces; + border-radius: 4px; + margin: 0; + padding: 0.2em 0.4em; + font-size: 85%; +} +.prose code br { + display: none; +} +.prose pre { + border-radius: 4px; + padding: 8px; + line-height: 1.45; + margin-bottom: 16px; + word-break: normal; + word-wrap: normal; +} +.prose pre code { + padding: 0; +} +.prose pre code:before, +.prose pre code:after { + content: normal; +} +.prose .ui.list .list, +.prose ol.ui.list ol, +.prose ul.ui.list ul { + padding-left: 2em; +} + +/* theme "Monokai++" generated by syntect */ +.entity.name.function.preprocessor, +.meta.preprocessor.macro, +.storage.modifier.import, +.storage.type.generic, +.variable.parameter, +.punctuation.section.class.begin, +.punctuation.section.class.end { + color: #cccccc; +} +.invalid { + background-color: #e62a19; +} +.comment { + color: #696d70; +} +.string, +.string.quoted, +.punctuation.definition.string.begin, +.punctuation.definition.string.end { + color: #e6db74; +} +.string.regexp { + color: #49e0fd; +} +.constant.language, +.constant.numeric, +.support.variable.magic { + color: #ae81ff; +} +.constant.character, +.constant.other.placeholder, +.support.other.escape.special.regexp { + color: #e62a19; +} +.constant.other { + color: #fd971f; +} +.entity.name.variable.property, +.keyword, +.meta.preprocessor { + color: #f92672; +} +.storage, +.support.constant, +.punctuation.section.class { + color: #49e0fd; +} +.keyword.type, +.storage.type, +.support.class, +.support.type, +.entity.name.type { + color: #2be98a; +} +.variable.language, +.variable.parameter.function.language.special, +.variable.other.member, +.variable.other.readwrite.member, +.entity.other.attribute-name, +.variable.parameter.function-call { + color: #fd971f; +} +.punctuation.accessor, +.punctuation.section.embedded, +.punctuation.separator, +.punctuation.definition.attribute, +.storage.type.function.arrow, +.punctuation.definition.template-expression, +.punctuation.definition.template-expression.begin, +.punctuation.definition.template-expression.end, +.punctuation.template-string.element.begin, +.punctuation.template-string.element.end { + color: #f92672; +} +.punctuation.separator.parameters { + color: #fd971f; +} +.entity.name.tag { + color: #f92672; +} +.entity.name.function, +.support.function, +.variable.function { + color: #b0ec38; +} +.markup.heading { + color: #f92672; + font-weight: bold; +} +.markup.bold { + font-weight: bold; +} +.markup.italic { + font-style: italic; +} +.markup.underline { + text-decoration: underline; +} +.markup.quote { + color: #696d70; +} +.markup.inline, +.markup.raw.inline { + color: #ae81ff; +} +.keyword.operator.dereference.java, +.meta.preprocessor.haskell, +.punctuation.separator.java, +.meta.group.js, +.meta.group.go, +.punctuation.section.class.begin.python, +.support.variable.dom.js, +.constant.character.brace, +.constant.character.end, +.constant.character.paren, +.constant.character.quote, +.support.class.js, +.punctuation.section.group.begin.js, +.punctuation.section.group.end.js, +.meta.template.expression, +.meta.group.braces, +.source.groovy.embedded.source, +.punctuation.section.class.end.groovy, +.variable.other.bracket.shell, +.variable.other.readwrite.shell, +.meta.group.expansion.command.parens.shell, +.variable.other.normal.shell, +.string.interpolated.dollar.shell, +.meta.function.shell .punctuation.section.parens.begin.shell, +.meta.function.shell .punctuation.section.parens.end.shell, +.string.other.math.shell { + color: #cccccc; +} +.constant.other.symbol.prolog, +.support.function.be.latex, +.support.function.general.tex, +.support.function.section.latex, +.punctuation.dollar.js, +.punctuation.separator.parameters.python, +.support.function.definition.latex, +.constant.language.module.events, +.constant.language.module.http, +.constant.language.directive.module.main, +.constant.language.directive.module.events, +.constant.language.directive.module.http, +.variable.language.this.js, +.variable.parameter.option.shell, +.punctuation.definition.variable.shell, +.punctuation.section.expansion.parameter.begin.shell, +.punctuation.section.expansion.parameter.end.shell, +.punctuation.section.parens.begin.shell, +.punctuation.section.parens.end.shell, +.string.interpolated.dollar.shell .punctuation.definition.string.begin.shell, +.string.interpolated.dollar.shell .punctuation.definition.string.end.shell, +.string.other.math.shell .punctuation.definition.string.begin.shell, +.string.other.math.shell .punctuation.definition.string.end.shell, +.variable.language.special.self.python, +.variable.parameter.function.language.special.self.python { + color: #f92672; +} +.entity.name.type.go, +.entity.name.type.namespace.php, +.meta.import.scala, +.punctuation.separator.inheritance.php, +.storage.type.js, +.support.other.module.haskell, +.support.other.namespace.use.php, +.variable.other.constant.ruby, +.entity.name.section.puppet, +.entity.name.function.decorator.python, +.keyword.other.rust { + color: #49e0fd; +} +.keyword.control.def.ruby, +.keyword.declaration.scala, +.keyword.declaration.stable.scala, +.keyword.declaration.volatile.scala, +.keyword.other.fn.rust, +.meta.structure.dictionary.key.json, +.storage.class.std.rust { + color: #2be98a; +} +.meta.function-call.object.php, +.meta.function-call.static.php, +.variable.other.makefile, +.variable.other.prolog, +.variable.other.property.js, +.support.variable.property.dom.js, +.meta.property.object.js, +.support.variable.property.js, +.variable.other.object.property.js, +.variable.other.property.cpp, +.meta.attribute.python { + color: #fd971f; +} +.meta.method.groovy, +.punctuation.definition.logical-expression.shell, +.meta.function-call.generic.python { + color: #b0ec38; +} +.constant.other.boolean.toml { + color: #ae81ff; +} +.string.other.link.title.markdown, +.string.other.link.description.markdown { + color: #49e0fd; +} +.beginning.punctuation.definition.list.markdown, +.punctuation.definition.list_item.markdown, +.punctuation.definition.list.markdown, +.punctuation.definition.heading.markdown, +.punctuation.definition.bold.markdown, +.punctuation.definition.italic.markdown, +.punctuation.definition.string.begin.markdown, +.punctuation.definition.string.end.markdown, +.punctuation.definition.bold.begin.markdown, +.punctuation.definition.bold.end.markdown, +.punctuation.definition.italic.begin.markdown, +.punctuation.definition.italic.end.markdown, +.punctuation.definition.heading.begin.markdown, +.punctuation.definition.heading.end.markdown, +.punctuation.definition.raw.begin.markdown, +.punctuation.definition.raw.end.markdown, +.punctuation.definition.metadata.markdown, +.punctuation.definition.raw.markdown, +.markup.underline.link.image.markdown, +.markup.underline.link.markdown { + color: #696d70; +} +.markup.deleted.diff { + color: #f92672; +} +.markup.inserted.diff { + color: #2be98a; +} +.meta.diff.range.unified { + color: #ae81ff; +} +.markup.deleted.git_gutter { + color: #f92672; +} +.markup.inserted.git_gutter { + color: #2be98a; +} +.markup.changed.git_gutter { + color: #ae81ff; +} +.markup.ignored.git_gutter { + color: #696d70; +} +.markup.untracked.git_gutter { + color: #696d70; +} + +.junit { + display: flex; + overflow: hidden; +} +@media (max-width: 1000px) { + .junit { + flex-wrap: wrap; + } + #preview-margin { + display: none; + } +} +.junit > div:not(:last-child) { + border-right: solid 1px var(--color-border); +} +#junit-suites, +#junit-cases { + min-width: 300px; + width: 300px; +} +#junit-preview { + flex-grow: 1; + margin-bottom: 40px; + overflow-x: auto; +} + +#junit-preview h2 { + border-bottom: 2px solid var(--color-status); +} + +#junit-preview h2 i { + color: var(--color-status); +} + +.junit ul { + list-style-type: none; +} + +.junit ul > li, +.colsubtitle { + border-bottom: 1px dashed var(--color-border2); +} + +.colsubtitle > button { + display: inline-flex; + align-items: center; + gap: 4px; + margin: 2px 0; + padding: 4px 8px; + border: 2px solid var(--color-status, var(--color-btn)); +} +.colsubtitle button.active { + background-color: var(--color-status, var(--color-btn)); + color: #fff; +} + +.coltitle { + font-size: 14px; + margin: 8px; +} + +.colsubtitle { + padding: 0 8px 8px 8px; + margin: 0; +} + +.junit li > button { + width: 100%; + padding: 4px 8px; + text-align: left; + background-color: transparent; + overflow: hidden; + text-overflow: ellipsis; +} +.junit li button.active { + text-decoration: underline; + color: var(--color-a-hov); +} + +#junit-cases.filtered li > button > span { + display: none; +} + +.badges > *:not(:last-child):after { + content: "โ€ข"; + margin: 0 0.4em; +} + +.pvcontent { + display: none; +} + +.junit li[data-status="success"] { + background-color: #00800035; +} +.junit li[data-status="failure"] { + background-color: #a6000035; +} +.junit li[data-status="error"] { + background-color: #67000035; +} +.junit li[data-status="skipped"] { + background-color: #33333335; +} +.junit li[data-status="flaky"] { + background-color: #d3641a35; +} + +[data-status="success"] { + --color-status: #008000; +} +[data-status="failure"] { + --color-status: #a60000; +} +[data-status="error"] { + --color-status: #670000; +} +[data-status="flaky"] { + --color-status: #d3641a; +} +[data-status="skipped"] { + --color-status: #333; +} + +/* Icons from https://css.gg */ +.gg-check-o { + box-sizing: border-box; + position: relative; + display: inline-block; + transform: scale(var(--ggs, 1)); + width: 22px; + height: 22px; + border: 2px solid; + border-radius: 100px; +} +.gg-check-o::after { + content: ""; + display: block; + box-sizing: border-box; + position: absolute; + left: 3px; + top: -1px; + width: 6px; + height: 10px; + border-color: currentColor; + border-width: 0 2px 2px 0; + border-style: solid; + transform-origin: bottom left; + transform: rotate(45deg); +} + +.gg-close-o { + box-sizing: border-box; + position: relative; + display: inline-block; + transform: scale(var(--ggs, 1)); + width: 22px; + height: 22px; + border: 2px solid; + border-radius: 40px; +} + +.gg-close-o::after, +.gg-close-o::before { + content: ""; + display: block; + box-sizing: border-box; + position: absolute; + width: 12px; + height: 2px; + background: currentColor; + transform: rotate(45deg); + border-radius: 5px; + top: 8px; + left: 3px; +} + +.gg-close-o::after { + transform: rotate(-45deg); +} + +.gg-block { + box-sizing: border-box; + position: relative; + display: inline-block; + transform: scale(var(--ggs, 1)); + width: 16px; + height: 16px; + border: 2px solid; + border-radius: 100%; +} + +.gg-block::before { + content: ""; + display: block; + box-sizing: border-box; + position: absolute; + width: 10px; + height: 2px; + background: currentColor; + border-radius: 5px; + transform: rotate(-45deg); + top: 5px; + left: 1px; +} + +.gg-danger { + box-sizing: border-box; + position: relative; + display: inline-block; + transform: scale(var(--ggs, 1)); + width: 20px; + height: 20px; + border: 2px solid; + border-radius: 40px; +} + +.gg-danger::after, +.gg-danger::before { + content: ""; + display: block; + box-sizing: border-box; + position: absolute; + border-radius: 3px; + width: 2px; + background: currentColor; + left: 7px; +} + +.gg-danger::after { + top: 2px; + height: 8px; +} + +.gg-danger::before { + height: 2px; + bottom: 2px; +} diff --git a/resources/content.css.gz b/resources/content.css.gz new file mode 100644 index 0000000..8bfaea9 Binary files /dev/null and b/resources/content.css.gz differ diff --git a/resources/favicon.ico b/resources/favicon.ico index 8525003..6cff4b9 100644 Binary files a/resources/favicon.ico and b/resources/favicon.ico differ diff --git a/resources/favicon.xcf b/resources/favicon.xcf index dbb418a..aa00984 100644 Binary files a/resources/favicon.xcf and b/resources/favicon.xcf differ diff --git a/resources/screenshotCode.png b/resources/screenshotCode.png new file mode 100644 index 0000000..7052628 Binary files /dev/null and b/resources/screenshotCode.png differ diff --git a/resources/screenshotFiles.png b/resources/screenshotFiles.png new file mode 100644 index 0000000..53052e2 Binary files /dev/null and b/resources/screenshotFiles.png differ diff --git a/resources/screenshotJUnit.png b/resources/screenshotJUnit.png new file mode 100644 index 0000000..3a05070 Binary files /dev/null and b/resources/screenshotJUnit.png differ diff --git a/resources/screenshotPrComment.png b/resources/screenshotPrComment.png new file mode 100644 index 0000000..68fcc66 Binary files /dev/null and b/resources/screenshotPrComment.png differ diff --git a/resources/style.css b/resources/style.css new file mode 100644 index 0000000..be5c3d0 --- /dev/null +++ b/resources/style.css @@ -0,0 +1,229 @@ +/* Stylesheet for all artifactview pages */ +* { + padding: 0; + margin: 0; + --color-secondary: #dedede; + --color-text: #000; + --color-text-light: #888; + --color-border: #ccc; + --color-border2: #bbb; + --color-btn: #006ed3; + --color-a: #006ed3; + --color-a-hov: #319cff; +} +body { + font-family: sans-serif; + text-rendering: optimizespeed; + background-color: #f5f5f5; + color: var(--color-text); +} +a { + color: var(--color-a); + text-decoration: none; +} +a:hover, a.selected { + color: var(--color-a-hov); +} +header, #summary, .content { + padding: 0 20px; +} +header { + display: flex; + flex-direction: row; + gap: 1em; + padding-top: 25px; + padding-bottom: 15px; + background-color: #f2f2f2; +} +header h1 { + font-size: 20px; + font-weight: normal; + white-space: nowrap; + overflow-x: hidden; + text-overflow: ellipsis; + color: #999; +} +header h1 a { + color: var(--color-text); +} +header h1 .sep { + margin: 0 0.2em; +} +footer a:hover, +header h1 a:hover, +a.selected { + text-decoration: underline; +} +main { + display: block; +} +#summary, #summary > div { + display: flex; + align-items: center; + gap: 1em; +} +.metadata { + font-size: 12px; + font-family: Verdana, sans-serif; + border-bottom: 1px solid #9c9c9c; + padding-top: 10px; + padding-bottom: 10px; +} +#filter { + padding: 4px; + border: 1px solid #ccc; +} +#list { + width: 100%; + border-collapse: collapse; +} +#list tr { + border-bottom: 1px dashed var(--color-border2); +} +#list tbody tr:hover { + background-color: #ffffec; +} +#list td, +#list th { + text-align: left; + padding: 10px 0; +} +#list th { + padding-top: 15px; + padding-bottom: 15px; + font-size: 16px; + white-space: nowrap; +} +#list th a { + color: var(--color-text); +} +#list th svg { + vertical-align: middle; +} +#list td { + white-space: nowrap; + font-size: 14px; +} +#list td:nth-child(1), +#list th:nth-child(1) { + padding-left: 20px; + width: 80%; +} +#list td:nth-child(2), +#list th:nth-child(2) { + text-align: right; + padding: 0 20px; +} +#list td:nth-child(3), +#list th:nth-child(3) { + text-align: right; + padding-right: 20px; +} +#list td:nth-child(1) svg { + position: absolute; +} +#list td .goup, +#list td .name { + margin-left: 1.75em; + word-break: break-all; + overflow-wrap: break-word; + white-space: pre-wrap; +} +.query-input { + color: inherit; + font-size: 16px; + border: 1px solid var(--color-border); + padding: 4px 8px; +} +button { + border: none; + cursor: pointer; + font-size: 14px; + background-color: unset; + color: unset; +} +.btn { + background-color: var(--color-btn); + padding: 4px 8px; +} +button:hover { + filter: brightness(80%); +} +button:active { + filter: brightness(70%); +} +footer { + padding: 40px 20px; + font-size: 12px; + text-align: center; +} +p { + margin: 16px 0; +} +.card { + display: flex; + flex-direction: column; + width: 90%; + max-width: 500px; + align-items: center; +} +.input-row { + display: flex; + width: 100%; +} +.center { + display: flex; + flex-direction: row; + justify-content: center; +} +.light { + color: var(--color-text-light); +} +@media (max-width: 600px) { + td:nth-child(1) { + width: auto; + } + td:nth-child(2), + th:nth-child(2) { + display: none; + } + #filter { + max-width: 100px; + } +} +.expired { + filter: grayscale(100%); +} +.hidden { + display: none !important; +} +@media (prefers-color-scheme: dark) { + * { + --color-secondary: #082437; + --color-text: #ddd; + --color-border: #212121; + --color-border2: #333; + --color-a: #009dff; + --color-a-hov: #62b2fd; + } + body { + background-color: #101010; + } + header { + background-color: #151515; + } + .query-input { + background-color: #151515; + } + #list tbody tr:hover { + background-color: #252525; + } + #filter { + background-color: #151515; + color: #ffffff; + border: 1px solid #212121; + } + .metadata { + border-bottom: 1px solid #212121; + } +} diff --git a/resources/style.css.gz b/resources/style.css.gz new file mode 100644 index 0000000..0d2e1f3 Binary files /dev/null and b/resources/style.css.gz differ diff --git a/src/app.rs b/src/app.rs index 4db3274..31c1bce 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,15 +1,26 @@ -use std::{net::SocketAddr, ops::Bound, path::PathBuf, str::FromStr, sync::Arc}; +use std::{ + collections::{BTreeMap, HashMap}, + fmt::Write, + net::{IpAddr, SocketAddr}, + ops::Bound, + path::Path, + str::FromStr, + sync::Arc, +}; use async_zip::tokio::read::ZipEntryReader; use axum::{ body::Body, - extract::{Host, Request, State}, + extract::{Query as XQuery, Request, State}, http::{Response, Uri}, response::{IntoResponse, Redirect}, routing::{any, get, post}, - Form, Router, + Json, RequestExt, Router, }; -use headers::HeaderMapExt; +use axum_extra::extract::Host; +use futures_lite::AsyncReadExt as LiteAsyncReadExt; +use governor::{Quota, RateLimiter}; +use headers::{ContentType, HeaderMapExt}; use http::{HeaderMap, StatusCode}; use serde::Deserialize; use tokio::{ @@ -26,19 +37,21 @@ use tower_http::{ }; use crate::{ - artifact_api::ArtifactApi, + artifact_api::{Artifact, ArtifactApi, WorkflowRun}, cache::{Cache, CacheEntry, GetFileResult, GetFileResultFile}, config::Config, error::Error, gzip_reader::{PrecompressedGzipReader, GZIP_EXTRA_LEN}, - query::Query, + query::{ArtifactQuery, Query, RunQuery}, templates::{self, ArtifactItem, LinkItem}, util::{self, ErrorJson, ResponseBuilderExt}, - App, + viewer::Viewers, }; +pub struct App; + #[derive(Clone)] -struct AppState { +pub struct AppState { i: Arc, } @@ -46,6 +59,13 @@ struct AppInner { cfg: Config, cache: Cache, api: ArtifactApi, + viewers: Viewers, + lim_pr_comment: Option< + governor::DefaultKeyedRateLimiter< + IpAddr, + governor::middleware::NoOpMiddleware, + >, + >, } impl Default for App { @@ -54,13 +74,60 @@ impl Default for App { } } -#[derive(Deserialize)] -struct UrlForm { - url: String, +#[derive(Default, Deserialize)] +struct FileQparams { + viewer: Option, } +#[derive(Deserialize)] +struct UrlQuery { + url: Option, +} + +#[derive(Deserialize)] +struct PrCommentReq { + /// Workflow run URL + url: String, + /// Pull request number + pr: u64, + /// If set to true, it will delete the previous PR comment and create a new one instead + /// of updating it + #[serde(default)] + recreate: bool, + /// Comment title + /// + /// Default: "๐Ÿ‘๏ธ Latest build artifacts" + title: Option, + /// Map of custom artifact titles + #[serde(default)] + artifact_titles: HashMap, + /// Map of custom artifact paths + /// + /// If you want the artifact links in the comment to point to a specific file in the + /// artifact (e.g. a test report), you can set a custom path for the artifact + #[serde(default)] + artifact_paths: HashMap, +} + +const DATE_FORMAT: &[time::format_description::FormatItem] = + time::macros::format_description!("[day].[month].[year] [hour]:[minute]:[second]"); + const FAVICON_PATH: &str = "/favicon.ico"; -const FAVICON_BYTES: &[u8; 268] = include_bytes!("../resources/favicon.ico"); +pub(crate) const VERSION: &str = env!("CARGO_PKG_VERSION"); + +// Stylesheets are saved with immutable cache header. If they are changed in the future, +// the number in the path should be incremented +pub(crate) const STYLE_MAIN_PATH: &str = "/style2.css"; +pub(crate) const STYLE_CONTENT_PATH: &str = "/content2.css"; + +const FAVICON_BYTES: &[u8] = include_bytes!("../resources/favicon.ico"); +const STYLE_MAIN_BYTES: &[u8] = include_bytes!("../resources/style.css"); +const STYLE_CONTENT_BYTES: &[u8] = include_bytes!("../resources/content.css"); + +#[allow(unused_variables)] +const STYLE_MAIN_BYTES_GZ: &[u8] = include_bytes!("../resources/style.css.gz"); +#[allow(unused_variables)] +const STYLE_CONTENT_BYTES_GZ: &[u8] = include_bytes!("../resources/content.css.gz"); impl App { pub fn new() -> Self { @@ -72,40 +139,18 @@ impl App { } pub async fn run(&self) -> Result<(), Error> { - let address = "0.0.0.0:3000"; - let listener = tokio::net::TcpListener::bind(address).await?; - tracing::info!("Listening on http://{address}"); - let state = self.new_state()?; - let real_ip_header = state.i.cfg.load().real_ip_header.clone(); - let router = Router::new() - // Prevent search indexing since artifactview serves temporary artifacts - .route( - "/robots.txt", - get(|| async { "# PLEASE dont scrape this website.\n# All of the data here is fetched from the public GitHub/Gitea APIs, this app is open source and it is not running on some Fortune 500 company server. \n\nUser-agent: *\nDisallow: /\n" }), - ) - // Put the API in the .well-known folder, since it is disabled for pages - .route("/.well-known/api/artifacts", get(Self::get_artifacts)) - .route("/.well-known/api/artifact", get(Self::get_artifact)) - .route("/.well-known/api/files", get(Self::get_files)) - // Prevent access to the .well-known folder since it enables abuse - // (e.g. SSL certificate registration by an attacker) - .route("/.well-known/*path", any(|| async { Error::Inaccessible })) - // Serve artifact pages - .route("/", get(Self::get_page)) - .route("/", post(Self::post_homepage)) - .fallback(get(Self::get_page)) - .with_state(state) - // Log requests - .layer( - TraceLayer::new_for_http() - .make_span_with(move |request: &Request| { - let ip = util::get_ip_address(request, real_ip_header.as_deref()).map(|ip| ip.to_string()).unwrap_or_default(); - tracing::error_span!("request", url = util::full_url_from_request(request), ip) - }) - .on_response(DefaultOnResponse::new().level(tracing::Level::INFO)), - ) - .layer(SetResponseHeaderLayer::appending(http::header::X_CONTENT_TYPE_OPTIONS, http::HeaderValue::from_static("nosniff"))); + + let port = state.i.cfg.load().port; + let listener = tokio::net::TcpListener::bind(SocketAddr::new( + std::net::IpAddr::V4(std::net::Ipv4Addr::UNSPECIFIED), + port, + )) + .await?; + tracing::info!("Listening on port {port}"); + + let router = Self::router(state); + axum::serve( listener, router.into_make_service_with_connect_info::(), @@ -114,6 +159,49 @@ impl App { Ok(()) } + pub fn router(state: AppState) -> Router { + let real_ip_header = state.i.cfg.load().real_ip_header.clone(); + Router::new() + // Prevent search indexing since artifactview serves temporary artifacts + .route("/robots.txt", get(|| async { + Response::builder() + .typed_header(headers::ContentType::text_utf8()) + .cache() + .body::("# PLEASE dont scrape this website.\n# All of the data here is fetched from the public GitHub/Gitea APIs, this app is open source and it is not running on some Fortune 500 company server. \n\nUser-agent: *\nDisallow: /\n".into()).unwrap() + })) + // Put the API in the .well-known folder, since it is disabled for pages + .route("/.well-known/api/artifacts", get(Self::get_artifacts)) + .route("/.well-known/api/artifact", get(Self::get_artifact)) + .route("/.well-known/api/files", get(Self::get_files)) + .route("/.well-known/api/prComment", post(Self::pr_comment)) + // Prevent access to the .well-known folder since it enables abuse + // (e.g. SSL certificate registration by an attacker) + .route("/.well-known/{*path}", any(|| async { Error::Inaccessible })) + // Serve artifact pages + .route("/", get(Self::get_page)) + .fallback(get(Self::get_page)) + .with_state(state) + // Log requests + .layer( + TraceLayer::new_for_http() + .make_span_with(move |request: &Request| { + let ip = util::get_ip_address(request, real_ip_header.as_deref()) + .map(|ip| ip.to_string()) + .unwrap_or_default(); + tracing::error_span!( + "request", + url = util::full_url_from_request(request), + ip + ) + }) + .on_response(DefaultOnResponse::new().level(tracing::Level::INFO)), + ) + .layer(SetResponseHeaderLayer::appending( + http::header::X_CONTENT_TYPE_OPTIONS, + http::HeaderValue::from_static("nosniff"), + )) + } + async fn get_page( State(state): State, Host(host): Host, @@ -123,153 +211,285 @@ impl App { let subdomain = util::get_subdomain(&host, &state.i.cfg.load().root_domain)?; if subdomain.is_empty() { - // Main page - if uri.path() == FAVICON_PATH { - return Self::favicon(); - } - if uri.path() != "/" { - return Err(Error::NotFound("path".into())); - } - Ok(Response::builder() - .typed_header(headers::ContentType::html()) - .cache() - .body(templates::Index::default().to_string().into())?) + Self::get_homepage(state, uri, request.headers()).await } else { - let query = Query::from_subdomain(subdomain, &state.i.cfg.load().site_aliases)?; + let query = ArtifactQuery::from_subdomain(subdomain, &state.i.cfg.load().site_aliases)?; state.i.cfg.check_filterlist(&query)?; let path = percent_encoding::percent_decode_str(uri.path()).decode_utf8_lossy(); let hdrs = request.headers(); let ip = util::get_ip_address(&request, state.i.cfg.load().real_ip_header.as_deref())?; - match query { - Query::Artifact(query) => { - let entry_res = state.i.cache.get_entry(&state.i.api, &query, &ip).await?; - let entry = entry_res.entry; - if entry_res.downloaded { - state.garbage_collect(); + let entry_res = state.i.cache.get_entry(&state.i.api, &query, &ip).await?; + let entry = entry_res.entry; + if entry_res.downloaded { + state.garbage_collect(); + } + + match entry.get_file(&path, uri.query().unwrap_or_default()) { + Ok(gfr) => { + if gfr.index() && !path.ends_with('/') { + return Ok(Redirect::permanent(&format!("{path}/")).into_response()); } - match entry.get_file(&path, uri.query().unwrap_or_default()) { - Ok(GetFileResult::File(res)) => { - Self::serve_artifact_file(state, entry, entry_res.zip_path, res, hdrs) + match gfr { + GetFileResult::File(res) => { + let qparams = uri + .query() + .and_then(|q| serde_urlencoded::from_str::(q).ok()) + .unwrap_or_default(); + if res.filename.is_some() { + if let Some(viewer) = qparams.viewer { + match Self::try_view_file( + &state, + &entry, + &entry_res.zip_path, + &query, + &res, + &viewer, + &path, + ) + .await + { + Ok(resp) => return Ok(resp), + Err(e) => { + tracing::error!("{e}") + } + } + } + } + Self::serve_artifact_file(&state, entry, &entry_res.zip_path, res, hdrs) .await } - Ok(GetFileResult::Listing(listing)) => { - if !path.ends_with('/') { - return Ok(Redirect::to(&format!("{path}/")).into_response()); - } - - let mut path_components = vec![ - LinkItem { - name: query.shortid(), - url: state - .i - .cfg - .url_with_subdomain(&query.subdomain_with_artifact(None)?), - }, - LinkItem { - name: entry.name.to_owned(), - url: "/".to_string(), - }, - ]; - let mut buf = String::new(); - for s in path.split('/').filter(|s| !s.is_empty()) { - buf.push('/'); - buf += s; - path_components.push(LinkItem { - name: s.to_owned(), - url: buf.clone(), - }); - } - + GetFileResult::Listing(listing) => { + let run_url = query.forge_url(); let tmpl = templates::Listing { main_url: state.i.cfg.main_url(), - version: templates::Version, - run_url: &query.forge_url(), + run_url: &run_url, artifact_name: &entry.name, - path_components, + path_components: path_components( + &query, + state.i.cfg.main_url(), + &run_url, + &entry.name, + &path, + ), n_dirs: listing.n_dirs, n_files: listing.n_files, has_parent: listing.has_parent, + publisher: query.publisher(), + viewer_max_size: state + .i + .cfg + .load() + .viewer_max_size + .map(u32::from) + .unwrap_or(u32::MAX), entries: listing.entries, }; Ok(Response::builder() .typed_header(headers::ContentType::html()) - .cache_immutable() + .cache() .body(tmpl.to_string().into())?) } - Err(Error::NotFound(e)) => { - if path == FAVICON_PATH { - Self::favicon() - } else { - Err(Error::NotFound(e)) - } - } - Err(e) => Err(e), } } - Query::Run(query) => { - let artifacts = state.i.api.list(&query).await?; - - if uri.path() == FAVICON_PATH { - return Self::favicon(); + Err(Error::NotFound(e)) => { + if path == FAVICON_PATH { + Self::favicon() + } else { + Err(Error::NotFound(e)) } - if uri.path() != "/" { - return Err(Error::NotFound("path".into())); - } - if artifacts.is_empty() { - return Err(Error::NotFound("artifacts".into())); - } - let tmpl = templates::Selection { - main_url: state.i.cfg.main_url(), - version: templates::Version, - run_url: &query.forge_url(), - run_name: &query.shortid(), - publisher: LinkItem { - name: query.user.to_owned(), - url: format!("https://{}/{}", query.host, query.user), - }, - artifacts: artifacts - .into_iter() - .map(|a| ArtifactItem::from_artifact(a, &query, &state.i.cfg)) - .collect::, _>>()?, - }; - Ok(Response::builder() - .typed_header(headers::ContentType::html()) - .cache() - .body(tmpl.to_string().into())?) } + Err(e) => Err(e), } } } - async fn post_homepage( - State(state): State, - Host(host): Host, - Form(url): Form, - ) -> Result { - let subdomain = util::get_subdomain(&host, &state.i.cfg.load().root_domain)?; + async fn get_homepage( + state: AppState, + uri: Uri, + hdrs: &HeaderMap, + ) -> Result, Error> { + if uri.path() != "/" { + match uri.path() { + FAVICON_PATH => return Self::favicon(), + STYLE_MAIN_PATH => { + return Self::stylesheet(hdrs, STYLE_MAIN_BYTES, STYLE_MAIN_BYTES_GZ) + } + STYLE_CONTENT_PATH => { + return Self::stylesheet(hdrs, STYLE_CONTENT_BYTES, STYLE_CONTENT_BYTES_GZ) + } + "/artifactview.user.js" => { + let cfg = state.i.cfg.load(); - if subdomain.is_empty() { - let query = Query::from_forge_url(&url.url, &state.i.cfg.load().site_aliases)?; - let subdomain = query.subdomain()?; - let target = format!( - "{}{}.{}", - state.i.cfg.url_proto(), - subdomain, - state.i.cfg.load().root_domain - ); - Ok(Redirect::to(&target)) + let map_host = |h: &str| { + // Since GitHub uses Javascript for page changes, the userscript needs to be always loaded + if h == "github.com" { + "https://github.com/*".to_owned() + } else { + format!("https://{h}/*/runs/*") + } + }; + + let forge_urls = if cfg.repo_whitelist.is_empty() { + cfg.suggested_sites + .iter() + .map(|itm| map_host(itm)) + .collect::>() + } else { + cfg.repo_whitelist + .hosts() + .iter() + .map(|h| map_host(h)) + .collect::>() + }; + let aliases = cfg + .site_aliases + .iter() + .map(|(k, v)| (v.as_str(), k.as_str())) + .collect::>(); + + let tmpl = templates::Userscript { + main_url: state.i.cfg.main_url(), + root_domain: &cfg.root_domain, + no_https: cfg.no_https, + forge_urls, + aliases: &aliases, + }; + + return Ok(Response::builder() + .typed_header(headers::ContentType::from( + mime::APPLICATION_JAVASCRIPT_UTF_8, + )) + .cache() + .body(tmpl.to_string().into())?); + } + _ => return Err(Error::NotFound("path".into())), + } + } + + #[derive(Deserialize)] + struct Params { + url: String, + name: Option, + } + + if let Some(params) = uri + .query() + .and_then(|q| serde_urlencoded::from_str::(q).ok()) + { + let query = + RunQuery::from_forge_url_alias(¶ms.url, &state.i.cfg.load().site_aliases)?; + let artifacts = state.i.api.list(&query, true).await?; + + if artifacts.is_empty() { + Err(Error::NotFound("artifacts".into())) + } else if let Some(artifact) = params + .name + .and_then(|n| artifacts.iter().find(|a| a.name == n)) + { + Ok(Redirect::to( + &state + .i + .cfg + .url_with_subdomain(&query.subdomain_with_artifact(artifact.id)), + ) + .into_response()) + } else { + let tmpl = templates::Selection { + main_url: state.i.cfg.main_url(), + run_url: &query.forge_url(), + run_name: &query.shortid(), + publisher: query.publisher(), + artifacts: artifacts + .into_iter() + .map(|a| ArtifactItem::from_artifact(a, query.as_ref(), &state.i.cfg)) + .collect::>(), + }; + Ok(Response::builder() + .typed_header(headers::ContentType::html()) + .cache() + .body(tmpl.to_string().into())?) + } } else { - Err(Error::MethodNotAllowed) + Ok(Response::builder() + .typed_header(headers::ContentType::html()) + .cache() + .body( + templates::Index { + main_url: state.i.cfg.main_url(), + example_site: state.i.cfg.example_site(), + } + .to_string() + .into(), + )?) } } + async fn try_view_file( + state: &AppState, + entry: &Arc, + zip_path: &Path, + query: &ArtifactQuery, + res: &GetFileResultFile, + viewer: &str, + path: &str, + ) -> Result, Error> { + let file = &res.file; + let filename = res.filename.as_deref().unwrap_or_default(); + + // Dont try to view files above the configured size limit + let lim = state.i.cfg.load().viewer_max_size; + if lim.is_some_and(|lim| file.uncompressed_size > lim.into()) { + return Err(Error::ViewerNotApplicable); + } + + // Read decompressed file + let zip_file = File::open(&zip_path).await?; + let mut zip_reader = BufReader::new(zip_file); + util::seek_to_data_offset(&mut zip_reader, file.header_offset.into()).await?; + let mut reader = ZipEntryReader::new_with_owned( + zip_reader.compat(), + file.compression, + file.compressed_size.into(), + ); + + let mut contents = String::new(); + reader.read_to_string(&mut contents).await?; + + let render_res = state.i.viewers.try_render(filename, viewer, &contents)?; + let run_url = query.forge_url(); + + let tmpl = templates::Preview { + main_url: state.i.cfg.main_url(), + run_url: &run_url, + filename, + path_components: path_components( + query, + state.i.cfg.main_url(), + &run_url, + &entry.name, + path.rsplit_once('/').map(|x| x.0).unwrap_or_default(), + ), + publisher: query.publisher(), + lines: contents.lines().count(), + size: file.uncompressed_size.into(), + viewers: render_res.tmpl_viewers, + body: &render_res.html, + }; + + Ok(Response::builder() + .typed_header(ContentType::html()) + .typed_header(headers::LastModified::from(entry.last_modified)) + .cache() + .body(tmpl.to_string().into())?) + } + async fn serve_artifact_file( - state: AppState, + state: &AppState, entry: Arc, - zip_path: PathBuf, + zip_path: &Path, res: GetFileResultFile, hdrs: &HeaderMap, ) -> Result, Error> { @@ -374,7 +594,7 @@ impl App { .typed_header(headers::ContentLength(content_length)) .typed_header( headers::ContentRange::bytes(range, total_len) - .map_err(|e| Error::Internal(e.to_string().into()))?, + .map_err(|e| Error::Other(e.to_string().into()))?, ) .body(Body::from_stream(ReaderStream::new( bufreader.take(content_length), @@ -391,11 +611,18 @@ impl App { async fn get_artifacts( State(state): State, Host(host): Host, + url_query: XQuery, ) -> Result, ErrorJson> { - let subdomain = util::get_subdomain(&host, &state.i.cfg.load().root_domain)?; - let query = Query::from_subdomain(subdomain, &state.i.cfg.load().site_aliases)?; + let query = match &url_query.url { + Some(url) => RunQuery::from_forge_url(url)?, + None => { + let subdomain = util::get_subdomain(&host, &state.i.cfg.load().root_domain)?; + ArtifactQuery::from_subdomain(subdomain, &state.i.cfg.load().site_aliases)?.into() + } + }; + state.i.cfg.check_filterlist(&query)?; - let artifacts = state.i.api.list(&query.into_runquery()).await?; + let artifacts = state.i.api.list(&query, true).await?; Ok(Response::builder().cache().json(&artifacts)?) } @@ -405,9 +632,9 @@ impl App { Host(host): Host, ) -> Result, ErrorJson> { let subdomain = util::get_subdomain(&host, &state.i.cfg.load().root_domain)?; - let query = Query::from_subdomain(subdomain, &state.i.cfg.load().site_aliases)?; + let query = ArtifactQuery::from_subdomain(subdomain, &state.i.cfg.load().site_aliases)?; state.i.cfg.check_filterlist(&query)?; - let artifact = state.i.api.fetch(&query.try_into_artifactquery()?).await?; + let artifact = state.i.api.fetch(&query).await?; Ok(Response::builder().cache().json(&artifact)?) } @@ -419,13 +646,9 @@ impl App { ) -> Result, ErrorJson> { let subdomain = util::get_subdomain(&host, &state.i.cfg.load().root_domain)?; let ip = util::get_ip_address(&request, state.i.cfg.load().real_ip_header.as_deref())?; - let query = Query::from_subdomain(subdomain, &state.i.cfg.load().site_aliases)?; + let query = ArtifactQuery::from_subdomain(subdomain, &state.i.cfg.load().site_aliases)?; state.i.cfg.check_filterlist(&query)?; - let entry_res = state - .i - .cache - .get_entry(&state.i.api, &query.try_into_artifactquery()?, &ip) - .await?; + let entry_res = state.i.cache.get_entry(&state.i.api, &query, &ip).await?; if entry_res.downloaded { state.garbage_collect(); } @@ -436,26 +659,137 @@ impl App { .json(&files)?) } + /// Create a comment under a workflow's pull request with links to view the artifacts + /// + /// To prevent abuse/spamming, Artifactview will only create a comment if + /// - The workflow is still running + /// - The workflow was triggered by the given pull request + async fn pr_comment( + State(state): State, + request: Request, + ) -> Result { + let ip = util::get_ip_address(&request, state.i.cfg.load().real_ip_header.as_deref())?; + let req = request + .extract::, _>() + .await + .map_err(|e| Error::BadRequest(e.body_text().into()))?; + let query = RunQuery::from_forge_url_alias(&req.url, &state.i.cfg.load().site_aliases)?; + + if let Some(limiter) = &state.i.lim_pr_comment { + limiter.check_key(&ip).map_err(Error::from)?; + } + + let run = state.i.api.workflow_run(&query).await?; + if !run.from_pr { + return Err( + Error::BadRequest("workflow run not triggered by pull request".into()).into(), + ); + } + if run.done { + return Err(Error::BadRequest("workflow run is not running".into()).into()); + } + if let Some(pr_number) = run.pr_number { + if pr_number != req.pr { + return Err(Error::BadRequest( + format!( + "workflow run was triggered by pr#{}, expected: {}", + pr_number, req.pr + ) + .into(), + ) + .into()); + } + } else { + let pr = state.i.api.get_pr(query.as_ref(), req.pr).await?; + if run.head_sha != pr.head.sha { + return Ok(ErrorJson::ok("head of pr does not match workflow run")); + } + } + + let artifacts = match state.i.api.list(&query, false).await { + Ok(a) => a, + Err(Error::NotFound(_)) => return Ok(ErrorJson::ok("no artifacts")), + Err(e) => return Err(e.into()), + }; + let old_comment = state.i.api.find_comment(query.as_ref(), req.pr).await?; + let content = pr_comment_text(PrCommentTextParams { + query: &query, + old_comment: old_comment.as_ref().map(|c| c.body.as_str()), + run: &run, + artifacts: &artifacts, + title: req.title.as_deref(), + artifact_titles: &req.artifact_titles, + artifact_paths: &req.artifact_paths, + cfg: &state.i.cfg, + }); + + let c_id = state + .i + .api + .add_comment( + query.as_ref(), + req.pr, + &content, + old_comment.map(|c| c.id), + req.recreate, + ) + .await?; + Ok(ErrorJson::ok(format!("created comment #{c_id}"))) + } + fn favicon() -> Result, Error> { Ok(Response::builder() .typed_header(headers::ContentType::from_str("image/x-icon").unwrap()) .cache_immutable() - .body(FAVICON_BYTES.as_slice().into())?) + .body(FAVICON_BYTES.into())?) + } + + #[allow(unused_variables)] + fn stylesheet( + hdrs: &HeaderMap, + content: &'static [u8], + content_gz: &'static [u8], + ) -> Result, Error> { + let resp = Response::builder() + .typed_header(headers::ContentType::from(mime::TEXT_CSS)) + .cache_immutable(); + + // Don't serve compressed stylesheets in debug mode to allow live changes + #[cfg(not(debug_assertions))] + if util::accepts_gzip(hdrs) { + return Ok(resp + .typed_header(headers::ContentEncoding::gzip()) + .body(content_gz.into())?); + } + Ok(resp.body(content.into())?) } } impl AppState { pub fn new() -> Result { let cfg = Config::new()?; + Ok(Self::from_cfg(cfg)) + } + + pub fn from_cfg(cfg: Config) -> Self { let cache = Cache::new(cfg.clone()); let api = ArtifactApi::new(cfg.clone()); - Ok(Self { - i: Arc::new(AppInner { cfg, cache, api }), - }) + Self { + i: Arc::new(AppInner { + cache, + api, + viewers: Viewers::new(), + lim_pr_comment: cfg + .load() + .limit_artifacts_per_min + .map(|lim| RateLimiter::keyed(Quota::per_minute(lim))), + cfg, + }), + } } /// Run garbage collection in the background if necessary - pub fn garbage_collect(&self) { + fn garbage_collect(&self) { let state = self.clone(); tokio::spawn(async move { if let Err(e) = state.i.cache.garbage_collect().await { @@ -464,3 +798,231 @@ impl AppState { }); } } + +fn path_components( + query: &ArtifactQuery, + main_url: &str, + run_url: &str, + entry_name: &str, + path: &str, +) -> Vec { + let mut path_components = vec![ + LinkItem { + name: query.shortid(), + url: format!("{main_url}/?url={run_url}"), + }, + LinkItem { + name: entry_name.to_owned(), + url: "/".to_string(), + }, + ]; + let mut buf = String::new(); + for s in path.split('/').filter(|s| !s.is_empty()) { + buf.push('/'); + buf += s; + path_components.push(LinkItem { + name: s.to_owned(), + url: buf.clone() + "/", + }); + } + path_components +} + +struct PrCommentTextParams<'a> { + query: &'a RunQuery, + old_comment: Option<&'a str>, + run: &'a WorkflowRun, + artifacts: &'a [Artifact], + title: Option<&'a str>, + artifact_titles: &'a HashMap, + artifact_paths: &'a HashMap, + cfg: &'a Config, +} + +/// Build pull request comment text +#[allow(clippy::assigning_clones)] +fn pr_comment_text(p: PrCommentTextParams) -> String { + let query = p.query; + let mut content = "### ".to_owned(); + let mut prevln = "- ".to_owned(); + let a_opts = r#"target="_blank" rel="noopener noreferrer""#; + let date_started = p + .run + .date_started + .and_then(|d| d.to_offset(time::UtcOffset::UTC).format(&DATE_FORMAT).ok()); + + let mut prev_builds = None; + let mut np_content = None; + if let Some(old_comment) = p.old_comment { + prev_builds = util::extract_delim(old_comment, "", ""); + } + + let write_commit = |s: &mut String, sha: &str| { + _ = write!( + s, + "[{}](https://{}/{}/{}/commit/{})", + &sha[..10], + query.host, + query.user, + query.repo, + sha + ); + }; + + let write_link_icon = |s: &mut String, title: &str, href: &str| { + let (title_pfx, title) = util::split_icon_prefix(title); + _ = write!(s, r#"{title_pfx}{title}"#,); + }; + + // Comment title + let run_url = query.forge_url(); + let artifacts_url = format!("{}/?url={}", p.cfg.main_url(), run_url); + write_link_icon( + &mut content, + p.title.unwrap_or("Latest build artifacts"), + &artifacts_url, + ); + _ = write!(&mut content, "\n\n Run [#{}]({}) ยท ", query.run, run_url); + write_commit(&mut content, &p.run.head_sha); + if let Some(date_started) = &date_started { + _ = write!(&mut content, " ยท {date_started} UTC"); + } + _ = content.write_str("\n\n"); + + // Previous run line + _ = write!(&mut prevln, "[#{}]({}) [", query.run, run_url); + write_commit(&mut prevln, &p.run.head_sha); + _ = write!( + &mut prevln, + "] Artifacts: " + ); + + for a in p.artifacts.iter().filter(|a| !a.expired) { + let mut url = p + .cfg + .url_with_subdomain(&query.subdomain_with_artifact(a.id)); + // Do not process the same run twice + if np_content.as_ref().is_some_and(|c| c.contains(&url)) { + np_content = None; + } + + if let Some(path) = p.artifact_paths.get(&a.name) { + url += path; + } + + write_link_icon( + &mut content, + p.artifact_titles.get(&a.name).unwrap_or(&a.name), + &url, + ); + _ = content.write_str("
\n"); + _ = write!( + &mut prevln, + r#" `{}`,"#, + a.name + ); + } + + if prevln.ends_with(',') { + prevln.pop(); + } + + if let Some(date_started) = &date_started { + _ = write!(&mut prevln, " ({date_started} UTC)"); + } + + if np_content.is_some() || prev_builds.is_some() { + _ = write!( + &mut content, + "
\nPrevious builds\n\n" + ); + if let Some(prev_builds) = prev_builds { + _ = writeln!(&mut content, "{prev_builds}"); + } + if let Some(np_content) = np_content { + _ = writeln!(&mut content, "{np_content}"); + } + _ = writeln!(&mut content, "\n
"); + } else { + _ = writeln!(&mut content, ""); + } + + _ = write!(&mut content, "\ngenerated by [Artifactview {VERSION}](https://codeberg.org/ThetaDev/artifactview)"); + content +} + +#[cfg(test)] +mod tests { + use time::macros::datetime; + + use super::*; + + #[test] + fn pr_comment() { + let mut query = RunQuery::from_forge_url( + "https://code.thetadev.de/ThetaDev/test-actions/actions/runs/104", + ) + .unwrap(); + let artifacts: [Artifact; 3] = [ + Artifact { + id: 1, + name: "Hello".to_owned(), + size: 0, + expired: false, + download_url: String::new(), + user_download_url: None, + }, + Artifact { + id: 2, + name: "Test".to_owned(), + size: 0, + expired: false, + download_url: String::new(), + user_download_url: None, + }, + Artifact { + id: 3, + name: "Expired".to_owned(), + size: 0, + expired: true, + download_url: String::new(), + user_download_url: None, + }, + ]; + let mut artifact_titles = HashMap::new(); + artifact_titles.insert("Hello".to_owned(), "๐Ÿ  Hello World ;-)".to_owned()); + let mut artifact_paths = HashMap::new(); + artifact_paths.insert("Test".to_owned(), "/junit.xml?viewer=1".to_owned()); + + let cfg = Config::default(); + + let footer = format!("generated by [Artifactview {VERSION}](https://codeberg.org/ThetaDev/artifactview)"); + + let mut old_comment = None; + for i in 1..=3 { + query.run = i.into(); + let run = WorkflowRun { + head_sha: format!("{i}5eed48a8382513147a949117ef4aa659989d397"), + from_pr: true, + pr_number: None, + date_started: Some(datetime!(2024-06-15 15:30 UTC).replace_hour(i).unwrap()), + done: false, + }; + let comment = pr_comment_text(PrCommentTextParams { + query: &query, + old_comment: old_comment.as_deref(), + run: &run, + artifacts: &artifacts, + title: None, + artifact_titles: &artifact_titles, + artifact_paths: &artifact_paths, + cfg: &cfg, + }); + let res = comment.replace(&footer, ""); // Remove footer since it depends on the version + insta::assert_snapshot!(format!("pr_comment_{i}"), res); + + old_comment = Some(comment); + } + } +} diff --git a/src/artifact_api.rs b/src/artifact_api.rs index 945ab06..e0edb52 100644 --- a/src/artifact_api.rs +++ b/src/artifact_api.rs @@ -1,27 +1,31 @@ //! API-Client to fetch CI artifacts from Github and Forgejo - use std::path::Path; use futures_lite::StreamExt; -use http::header; +use http::{header, Method}; +use once_cell::sync::Lazy; use quick_cache::sync::Cache as QuickCache; +use regex::Regex; use reqwest::{Client, ClientBuilder, IntoUrl, RequestBuilder, Response, Url}; -use serde::{Deserialize, Serialize}; +use secrecy::ExposeSecret; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use time::OffsetDateTime; use tokio::{fs::File, io::AsyncWriteExt}; use crate::{ config::Config, error::{Error, Result}, - query::{ArtifactQuery, QueryData}, + query::{ArtifactQuery, Query, QueryRef, RunQuery}, }; pub struct ArtifactApi { http: Client, cfg: Config, qc: QuickCache>, + user_ids: QuickCache, } -#[derive(Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct Artifact { pub id: u64, pub name: String, @@ -35,7 +39,7 @@ pub struct Artifact { pub user_download_url: Option, } -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] struct GithubArtifact { id: u64, name: String, @@ -44,24 +48,24 @@ struct GithubArtifact { archive_download_url: String, } -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] struct ForgejoArtifact { name: String, size: u64, status: ForgejoArtifactStatus, } -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] struct ApiError { message: String, } -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] struct ArtifactsWrap { artifacts: Vec, } -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "snake_case")] enum ForgejoArtifactStatus { Completed, @@ -69,7 +73,7 @@ enum ForgejoArtifactStatus { } impl GithubArtifact { - fn into_artifact(self, query: &QueryData) -> Artifact { + fn into_artifact(self, query: QueryRef<'_>) -> Artifact { Artifact { id: self.id, name: self.name, @@ -85,7 +89,7 @@ impl GithubArtifact { } impl ForgejoArtifact { - fn into_artifact(self, id: u64, query: &QueryData) -> Artifact { + fn into_artifact(self, id: u64, query: QueryRef<'_>) -> Artifact { Artifact { download_url: format!( "https://{}/{}/{}/actions/runs/{}/artifacts/{}", @@ -100,6 +104,154 @@ impl ForgejoArtifact { } } +#[derive(Debug)] +pub struct WorkflowRun { + pub head_sha: String, + pub from_pr: bool, + pub pr_number: Option, + pub date_started: Option, + pub done: bool, +} + +#[derive(Debug, Deserialize)] +struct ForgejoWorkflowRun { + state: ForgejoWorkflowState, + logs: ForgejoWorkflowLogs, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ForgejoWorkflowState { + run: ForgejoWorkflowStateRun, +} + +#[derive(Debug, Deserialize)] +struct ForgejoWorkflowStateRun { + done: bool, + commit: ForgejoWorkflowCommit, +} + +#[derive(Debug, Deserialize)] +struct ForgejoWorkflowCommit { + link: String, + branch: ForgejoWorkflowBranch, +} + +#[derive(Debug, Deserialize)] +struct ForgejoWorkflowBranch { + link: String, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct ForgejoWorkflowLogs { + steps_log: Vec, +} + +#[derive(Debug, Deserialize)] +struct ForgejoWorkflowLogStep { + started: i64, + lines: Vec, +} + +#[derive(Debug, Deserialize)] +struct LogMessage { + message: String, +} + +#[derive(Debug, Deserialize)] +struct IdEntity { + id: u64, +} + +#[derive(Debug, Deserialize)] +pub struct Comment { + pub id: u64, + pub body: String, + user: IdEntity, +} + +#[derive(Debug, Serialize)] +struct CommentBody<'a> { + body: &'a str, +} + +#[derive(Debug, Deserialize)] +pub struct PullRequest { + pub head: Commit, +} + +#[derive(Debug, Deserialize)] +pub struct Commit { + pub sha: String, +} + +const GITHUB_ACCEPT: &str = "application/vnd.github+json"; +const COMMENT_TAG_PATTERN: &str = ""; + +impl TryFrom for WorkflowRun { + type Error = Error; + + fn try_from(value: ForgejoWorkflowRun) -> Result { + static RE_COMMIT_SHA: Lazy = + Lazy::new(|| Regex::new(r#"^/[\w\-\.]+/[\w\-\.]+/commit/([a-f\d]+)$"#).unwrap()); + static RE_PULL_ID: Lazy = + Lazy::new(|| Regex::new(r#"^/[\w\-\.]+/[\w\-\.]+/pulls/(\d+)$"#).unwrap()); + + let from_pr = value + .logs + .steps_log + .first() + .and_then(|l| l.lines.first()) + .map(|l| l.message.contains("be triggered by event: pull_request")) + .unwrap_or(true); + + Ok(Self { + head_sha: RE_COMMIT_SHA + .captures(&value.state.run.commit.link) + .map(|cap| cap[1].to_string()) + .ok_or(Error::Other( + "could not parse workflow run commit sha".into(), + ))?, + from_pr, + pr_number: if from_pr { + RE_PULL_ID + .captures(&value.state.run.commit.branch.link) + .and_then(|cap| cap[1].parse().ok()) + } else { + None + }, + date_started: value + .logs + .steps_log + .first() + .and_then(|l| OffsetDateTime::from_unix_timestamp(l.started).ok()), + done: value.state.run.done, + }) + } +} + +#[derive(Deserialize)] +struct GitHubWorkflowRun { + head_sha: String, + event: String, + conclusion: Option, + #[serde(with = "time::serde::rfc3339::option")] + run_started_at: Option, +} + +impl From for WorkflowRun { + fn from(value: GitHubWorkflowRun) -> Self { + Self { + head_sha: value.head_sha, + from_pr: value.event == "pull_request", + pr_number: None, + date_started: value.run_started_at, + done: value.conclusion.is_some(), + } + } +} + impl ArtifactApi { pub fn new(cfg: Config) -> Self { Self { @@ -112,29 +264,40 @@ impl ArtifactApi { .build() .unwrap(), qc: QuickCache::new(cfg.load().mem_cache_size), + user_ids: QuickCache::new(50), cfg, } } - pub async fn list(&self, query: &QueryData) -> Result> { - let subdomain = query.subdomain_with_artifact(None)?; - self.qc - .get_or_insert_async(&subdomain, async { - if query.is_github() { - self.list_github(query).await - } else { - self.list_forgejo(query).await - } - }) - .await + #[tracing::instrument(level = "error", skip_all)] + pub async fn list(&self, query: &RunQuery, cached: bool) -> Result> { + let cache_key = query.cache_key(); + let fut = async { + let res = if query.is_github() { + self.list_github(query.as_ref()).await + } else { + self.list_forgejo(query.as_ref()).await + }; + if res.as_ref().is_ok_and(|v| v.is_empty()) { + Err(Error::NotFound("artifact".into())) + } else { + res + } + }; + if cached { + self.qc.get_or_insert_async(&cache_key, fut).await + } else { + fut.await + } } + #[tracing::instrument(level = "error", skip_all)] pub async fn fetch(&self, query: &ArtifactQuery) -> Result { if query.is_github() { self.fetch_github(query).await } else { // Forgejo currently has no API for fetching single artifacts - let mut artifacts = self.list_forgejo(query).await?; + let mut artifacts = self.list_forgejo(query.as_ref()).await?; let i = usize::try_from(query.artifact)?; if i == 0 || i > artifacts.len() { @@ -144,6 +307,7 @@ impl ArtifactApi { } } + #[tracing::instrument(level = "error", skip_all)] pub async fn download(&self, artifact: &Artifact, path: &Path) -> Result<()> { if artifact.expired { return Err(Error::Expired); @@ -172,7 +336,7 @@ impl ArtifactApi { let url = Url::parse(&artifact.download_url)?; let req = if url.domain() == Some("api.github.com") { - self.get_github(url) + self.get_github_any(url) } else { self.http.get(url) }; @@ -200,15 +364,14 @@ impl ArtifactApi { Ok(()) } - async fn list_forgejo(&self, query: &QueryData) -> Result> { + async fn list_forgejo(&self, query: QueryRef<'_>) -> Result> { let url = format!( "https://{}/{}/{}/actions/runs/{}/artifacts", query.host, query.user, query.repo, query.run ); let resp = self - .http - .get(url) + .get_forgejo(url) .send() .await? .error_for_status()? @@ -225,16 +388,14 @@ impl ArtifactApi { Ok(artifacts) } - async fn list_github(&self, query: &QueryData) -> Result> { + async fn list_github(&self, query: QueryRef<'_>) -> Result> { let url = format!( "https://api.github.com/repos/{}/{}/actions/runs/{}/artifacts", query.user, query.repo, query.run ); - let resp = Self::handle_github_error(self.get_github(url).send().await?) - .await? - .json::>() - .await?; + let resp = + Self::send_api_req::>(self.get_github(url)).await?; Ok(resp .artifacts @@ -249,52 +410,362 @@ impl ArtifactApi { query.user, query.repo, query.artifact ); - let artifact = Self::handle_github_error(self.get_github(url).send().await?) - .await? - .json::() - .await?; - Ok(artifact.into_artifact(query)) + let artifact = Self::send_api_req::(self.get_github(url)).await?; + Ok(artifact.into_artifact(query.as_ref())) } - async fn handle_github_error(resp: Response) -> Result { + async fn send_api_req_empty(req: RequestBuilder) -> Result { + let resp = req.send().await?; if let Err(e) = resp.error_for_status_ref() { let status = resp.status(); let msg = resp.json::().await.ok(); - Err(Error::HttpClient( - msg.map(|msg| msg.message).unwrap_or(e.to_string()).into(), - status, - )) + let msg_str = msg.map(|msg| msg.message).unwrap_or(e.to_string()).into(); + tracing::error!("API error: {msg_str}"); + Err(Error::HttpClient(msg_str, status)) } else { Ok(resp) } } - fn get_github(&self, url: U) -> RequestBuilder { + async fn send_api_req(req: RequestBuilder) -> Result { + Ok(Self::send_api_req_empty(req).await?.json().await?) + } + + fn get_github_any(&self, url: U) -> RequestBuilder { let mut builder = self.http.get(url); if let Some(github_token) = &self.cfg.load().github_token { - builder = builder.header(header::AUTHORIZATION, format!("Bearer {github_token}")); + builder = builder.header( + header::AUTHORIZATION, + format!("Bearer {}", github_token.expose_secret()), + ); } builder } + + fn get_github(&self, url: U) -> RequestBuilder { + self.get_github_any(url) + .header(header::ACCEPT, GITHUB_ACCEPT) + } + + /// Authorized GitHub request + fn req_github(&self, method: Method, url: U) -> Result { + Ok(self + .http + .request(method, url) + .header(header::ACCEPT, GITHUB_ACCEPT) + .header(header::CONTENT_TYPE, GITHUB_ACCEPT) + .header( + header::AUTHORIZATION, + format!( + "Bearer {}", + self.cfg + .load() + .github_token + .as_ref() + .map(ExposeSecret::expose_secret) + .ok_or(Error::Other("GitHub token required".into()))? + ), + )) + } + + fn get_forgejo(&self, url: U) -> RequestBuilder { + self.http + .get(url) + .header(header::ACCEPT, mime::APPLICATION_JSON.essence_str()) + } + + /// Authorized Forgejo request + fn req_forgejo(&self, method: Method, url: U) -> Result { + let u = url.into_url()?; + let host = u.host_str().ok_or(Error::InvalidUrl)?; + let token = self + .cfg + .load() + .forgejo_tokens + .get(host) + .ok_or_else(|| Error::Other(format!("Forgejo token for {host} required").into()))? + .expose_secret(); + Ok(self + .http + .request(method, u) + .header(header::ACCEPT, mime::APPLICATION_JSON.essence_str()) + .header(header::CONTENT_TYPE, mime::APPLICATION_JSON.essence_str()) + .header(header::AUTHORIZATION, format!("token {token}"))) + } + + #[tracing::instrument(level = "error", skip_all)] + pub async fn workflow_run(&self, query: &RunQuery) -> Result { + if query.is_github() { + self.workflow_run_github(query).await + } else { + self.workflow_run_forgejo(query).await + } + } + + async fn workflow_run_forgejo(&self, query: &RunQuery) -> Result { + // Since the workflow needs to be fetched with a POST request, we need a CSRF token + let resp = self + .http + .get(format!("https://{}", query.host)) + .send() + .await? + .error_for_status()?; + let mut i_like_gitea = None; + let mut csrf = None; + for (k, v) in resp + .headers() + .get_all(header::SET_COOKIE) + .into_iter() + .filter_map(|v| v.to_str().ok()) + .filter_map(|v| v.split(';').next()) + .filter_map(|v| v.split_once('=')) + { + match k { + "i_like_gitea" => i_like_gitea = Some(v), + "_csrf" => csrf = Some(v), + _ => {} + } + } + let i_like_gitea = + i_like_gitea.ok_or(Error::Other("missing header: i_like_gitea".into()))?; + let csrf = csrf.ok_or(Error::Other("missing header: _csrf".into()))?; + + let resp = self + .http + .post(format!( + "https://{}/{}/{}/actions/runs/{}/jobs/0", + query.host, query.user, query.repo, query.run + )) + .header(header::CONTENT_TYPE, mime::APPLICATION_JSON.essence_str()) + .header(header::COOKIE, format!("i_like_gitea={i_like_gitea}")) + .header("x-csrf-token", csrf) + .body(r#"{"logCursors":[{"step":0,"cursor":null,"expanded":true}]}"#) + .send() + .await? + .error_for_status()?; + let run: WorkflowRun = resp.json::().await?.try_into()?; + Ok(run) + } + + async fn workflow_run_github(&self, query: &RunQuery) -> Result { + let run = Self::send_api_req::(self.get_github(format!( + "https://api.github.com/repos/{}/{}/actions/runs/{}", + query.user, query.repo, query.run + ))) + .await?; + Ok(run.into()) + } + + #[tracing::instrument(level = "error", skip_all)] + pub async fn add_comment( + &self, + query: QueryRef<'_>, + issue_id: u64, + content: &str, + old_comment_id: Option, + recreate: bool, + ) -> Result { + let body = format!("{COMMENT_TAG_PATTERN}\n{content}"); + if query.is_github() { + self.add_comment_github(query, issue_id, &body, old_comment_id, recreate) + .await + } else { + self.add_comment_forgejo(query, issue_id, &body, old_comment_id, recreate) + .await + } + } + + async fn add_comment_forgejo( + &self, + query: QueryRef<'_>, + issue_id: u64, + body: &str, + old_comment_id: Option, + recreate: bool, + ) -> Result { + if let Some(old_comment_id) = old_comment_id { + let url = format!( + "https://{}/api/v1/repos/{}/{}/issues/comments/{}", + query.host, query.user, query.repo, old_comment_id + ); + if recreate { + Self::send_api_req_empty(self.req_forgejo(Method::DELETE, url)?).await?; + } else { + Self::send_api_req_empty( + self.req_forgejo(Method::PATCH, url)? + .json(&CommentBody { body }), + ) + .await?; + return Ok(old_comment_id); + } + } + + let new_c = Self::send_api_req::( + self.req_forgejo( + Method::POST, + format!( + "https://{}/api/v1/repos/{}/{}/issues/{}/comments", + query.host, query.user, query.repo, issue_id + ), + )? + .json(&CommentBody { body }), + ) + .await?; + Ok(new_c.id) + } + + async fn add_comment_github( + &self, + query: QueryRef<'_>, + issue_id: u64, + body: &str, + old_comment_id: Option, + recreate: bool, + ) -> Result { + if let Some(old_comment_id) = old_comment_id { + let url = format!( + "https://api.github.com/repos/{}/{}/issues/comments/{}", + query.user, query.repo, old_comment_id + ); + if recreate { + Self::send_api_req_empty(self.req_github(Method::DELETE, url)?).await?; + } else { + Self::send_api_req_empty( + self.req_github(Method::PATCH, url)? + .json(&CommentBody { body }), + ) + .await?; + return Ok(old_comment_id); + } + } + + let new_c = Self::send_api_req::( + self.req_github( + Method::POST, + format!( + "https://api.github.com/repos/{}/{}/issues/{}/comments", + query.user, query.repo, issue_id + ), + )? + .json(&CommentBody { body }), + ) + .await?; + Ok(new_c.id) + } + + #[tracing::instrument(level = "error", skip_all)] + pub async fn find_comment( + &self, + query: QueryRef<'_>, + issue_id: u64, + ) -> Result> { + let user_id = self.get_user_id(query).await?; + if query.is_github() { + self.find_comment_github(query, issue_id, user_id).await + } else { + self.find_comment_forgejo(query, issue_id, user_id).await + } + } + + async fn find_comment_forgejo( + &self, + query: QueryRef<'_>, + issue_id: u64, + user_id: u64, + ) -> Result> { + let comments = Self::send_api_req::>(self.get_forgejo(format!( + "https://{}/api/v1/repos/{}/{}/issues/{}/comments", + query.host, query.user, query.repo, issue_id + ))) + .await?; + + Ok(comments + .into_iter() + .find(|c| c.user.id == user_id && c.body.starts_with(COMMENT_TAG_PATTERN))) + } + + async fn find_comment_github( + &self, + query: QueryRef<'_>, + issue_id: u64, + user_id: u64, + ) -> Result> { + for page in 1..=5 { + let comments = Self::send_api_req::>(self.get_github(format!( + "https://api.github.com/repos/{}/{}/issues/{}/comments?page={}", + query.user, query.repo, issue_id, page + ))) + .await?; + if let Some(comment) = comments + .into_iter() + .find(|c| c.user.id == user_id && c.body.starts_with(COMMENT_TAG_PATTERN)) + { + return Ok(Some(comment)); + } + } + Ok(None) + } + + #[tracing::instrument(level = "error", skip_all)] + pub async fn get_pr(&self, query: QueryRef<'_>, pr_id: u64) -> Result { + let req = if query.is_github() { + self.get_github(format!( + "https://api.github.com/repos/{}/{}/pulls/{}", + query.user, query.repo, pr_id + )) + } else { + self.get_forgejo(format!( + "https://{}/api/v1/repos/{}/{}/pulls/{}", + query.host, query.user, query.repo, pr_id + )) + }; + Self::send_api_req(req).await + } + + async fn get_user_id(&self, query: QueryRef<'_>) -> Result { + self.user_ids + .get_or_insert_async(query.host, async { + let user = + if query.is_github() { + Self::send_api_req::( + self.req_github(Method::GET, "https://api.github.com/user")?, + ) + .await? + } else { + Self::send_api_req::(self.req_forgejo( + Method::GET, + format!("https://{}/api/v1/user", query.host), + )?) + .await? + }; + Ok::<_, Error>(user.id) + }) + .await + } } #[cfg(test)] mod tests { - use crate::{config::Config, query::ArtifactQuery}; + use std::collections::HashMap; + + use time::macros::datetime; + + use crate::{ + config::Config, + query::{ArtifactQuery, RunQuery}, + }; use super::ArtifactApi; #[tokio::test] + #[ignore] async fn fetch_forgejo() { - let query = ArtifactQuery { - host: "code.thetadev.de".to_owned(), - host_alias: None, - user: "HSA".to_owned(), - repo: "Visitenbuch".to_owned(), - run: 32, - artifact: 1, - }; + let query = ArtifactQuery::from_subdomain( + "code-thetadev-de--hsa--visitenbuch--32-1", + &HashMap::new(), + ) + .unwrap(); let api = ArtifactApi::new(Config::default()); let res = api.fetch(&query).await.unwrap(); @@ -303,19 +774,44 @@ mod tests { } #[tokio::test] + #[ignore] async fn fetch_github() { - let query = ArtifactQuery { - host: "github.com".to_owned(), - host_alias: None, - user: "actions".to_owned(), - repo: "upload-artifact".to_owned(), - run: 8805345396, - artifact: 1440556464, - }; + let query = ArtifactQuery::from_subdomain( + "github-com--actions--upload-artifact--8805345396-1440556464", + &HashMap::new(), + ) + .unwrap(); let api = ArtifactApi::new(Config::default()); let res = api.fetch(&query).await.unwrap(); assert_eq!(res.id, 1440556464); assert_eq!(res.size, 334); } + + #[tokio::test] + #[ignore] + async fn workflow_run_forgejo() { + let query = + RunQuery::from_forge_url("https://codeberg.org/forgejo/forgejo/actions/runs/20471") + .unwrap(); + let api = ArtifactApi::new(Config::default()); + let res = api.workflow_run(&query).await.unwrap(); + assert_eq!(res.head_sha, "03581511024aca9b56bc6083565bdcebeacb9d05"); + assert!(res.from_pr); + assert_eq!(res.date_started, Some(datetime!(2024-06-21 9:13:23 UTC))); + } + + #[tokio::test] + #[ignore] + async fn workflow_run_github() { + let query = + RunQuery::from_forge_url("https://github.com/orhun/git-cliff/actions/runs/9588266559") + .unwrap(); + let api = ArtifactApi::new(Config::default()); + let res = api.workflow_run(&query).await.unwrap(); + dbg!(&res); + assert_eq!(res.head_sha, "0500cb2c5c5ec225e109584236940ee068be2372"); + assert!(res.from_pr); + assert_eq!(res.date_started, Some(datetime!(2024-06-21 9:13:23 UTC))); + } } diff --git a/src/cache.rs b/src/cache.rs index f3db3ec..f349558 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -16,7 +16,7 @@ use mime::Mime; use path_macro::path; use quick_cache::sync::Cache as QuickCache; use serde::Serialize; -use serde_hex::{SerHex, Strict}; +use serde_hex::{SerHex, SerHexOpt}; use crate::{ artifact_api::ArtifactApi, @@ -54,8 +54,11 @@ pub struct FileEntry { } pub struct GetEntryResult { + /// Cached zip file metadata pub entry: Arc, + /// Path to the cached zip file pub zip_path: PathBuf, + /// True if the entry was just downloaded pub downloaded: bool, } @@ -65,19 +68,22 @@ pub enum GetFileResult { } pub struct GetFileResultFile { + pub filename: Option, pub file: FileEntry, pub mime: Option, pub status: StatusCode, + pub index: bool, } #[derive(Serialize)] pub struct IndexEntry { pub name: String, pub size: u32, - #[serde(with = "SerHex::")] + #[serde(with = "SerHex::")] pub crc32: u32, } +#[derive(Serialize)] pub struct Listing { pub entries: Vec, pub n_files: usize, @@ -85,16 +91,32 @@ pub struct Listing { pub has_parent: bool, } +#[derive(Serialize)] pub struct ListingEntry { pub name: String, pub url: String, pub size: Size, - pub crc32: String, + pub crc32: Crc32, pub is_dir: bool, } +#[derive(Serialize)] pub struct Size(pub u32); +#[derive(Serialize)] +pub struct Crc32(#[serde(with = "SerHexOpt::")] pub Option); + +impl GetFileResult { + /// Return true if the result represents a directory index, so the client has to be redirected to the directory path + /// if the requested path does not end with a slash (otherwise resources on the index.html may not resolve properly) + pub fn index(&self) -> bool { + match self { + GetFileResult::File(f) => f.index, + GetFileResult::Listing(_) => true, + } + } +} + impl Cache { pub fn new(cfg: Config) -> Self { Self { @@ -114,7 +136,7 @@ impl Cache { query: &ArtifactQuery, ip: &IpAddr, ) -> Result { - let subdomain = query.subdomain_noalias(); + let subdomain = query.cache_key(); let zip_path = path!(self.cfg.load().cache_dir / format!("{subdomain}.zip")); let downloaded = !zip_path.is_file(); if downloaded { @@ -144,10 +166,10 @@ impl Cache { let metadata = tokio::fs::metadata(&zip_path).await?; let modified = metadata .modified() - .map_err(|_| Error::Internal("no file modified time".into()))?; + .map_err(|_| Error::Other("no file modified time".into()))?; let accessed = metadata .accessed() - .map_err(|_| Error::Internal("no file accessed time".into()))?; + .map_err(|_| Error::Other("no file accessed time".into()))?; if modified != entry.last_modified { tracing::info!("cached file {zip_path:?} changed"); entry = Arc::new( @@ -160,7 +182,7 @@ impl Cache { let now = SystemTime::now(); if now .duration_since(accessed) - .map_err(|e| Error::Internal(e.to_string().into()))? + .map_err(|e| Error::Other(e.to_string().into()))? > Duration::from_secs(1800) { let file = std::fs::File::open(&zip_path)?; @@ -193,10 +215,10 @@ impl Cache { .metadata() .await? .accessed() - .map_err(|_| Error::Internal("no file accessed time".into()))?; + .map_err(|_| Error::Other("no file accessed time".into()))?; if now .duration_since(accessed) - .map_err(|e| Error::Internal(e.to_string().into()))? + .map_err(|e| Error::Other(e.to_string().into()))? > max_age { let path = entry.path(); @@ -248,8 +270,12 @@ impl CacheEntry { .entries() .iter() .filter_map(|entry| { + let name = entry.filename().as_str().ok()?; + if name.ends_with('/') { + return None; + } Some(( - entry.filename().as_str().ok()?.to_owned(), + name.to_owned(), FileEntry { header_offset: entry.header_offset().try_into().ok()?, uncompressed_size: entry.uncompressed_size().try_into().ok()?, @@ -263,12 +289,12 @@ impl CacheEntry { name, last_modified: meta .modified() - .map_err(|_| Error::Internal("no file modified time".into()))?, + .map_err(|_| Error::Other("no file modified time".into()))?, }) } pub fn get_file(&self, path: &str, url_query: &str) -> Result { - let path = path.trim_start_matches('/'); + let path = path.trim_matches('/'); let mut index_path: Option> = None; if path.is_empty() { @@ -280,28 +306,45 @@ impl CacheEntry { // 2. Site path + `/index.html` else if let Some(file) = self.files.get(path) { return Ok(GetFileResult::File(GetFileResultFile { + filename: path.rsplit('/').next().map(str::to_owned), file: file.clone(), mime: util::path_mime(path), status: StatusCode::OK, + index: false, })); } else if util::site_path_ext(path).is_none() { index_path = Some(format!("{path}/index.html").into()); } - if let Some(file) = index_path - .and_then(|p: Cow| self.files.get(p.as_ref())) - .or_else(|| self.files.get("200.html")) - { - // index.html or SPA entrypoint + if let Some(file) = index_path.and_then(|p: Cow| self.files.get(p.as_ref())) { + // index.html return Ok(GetFileResult::File(GetFileResultFile { + filename: None, file: file.clone(), mime: Some(mime::TEXT_HTML), status: StatusCode::OK, + index: true, + })); + } + + // Do not show fallback pages for favicon + if path == "favicon.ico" { + return Err(Error::NotFound("requested file".into())); + } + + // SPA entrypoint + if let Some(file) = self.files.get("200.html") { + return Ok(GetFileResult::File(GetFileResultFile { + filename: None, + file: file.clone(), + mime: Some(mime::TEXT_HTML), + status: StatusCode::OK, + index: false, })); } // Directory listing - let path_as_dir: Cow = if path.is_empty() || path.ends_with('/') { + let path_as_dir: Cow = if path.is_empty() { path.into() } else { format!("{path}/").into() @@ -328,9 +371,11 @@ impl CacheEntry { } else if let Some(file) = self.files.get("404.html") { // Custom 404 error page return Ok(GetFileResult::File(GetFileResultFile { + filename: None, file: file.clone(), mime: Some(mime::TEXT_HTML), status: StatusCode::NOT_FOUND, + index: false, })); } @@ -375,16 +420,16 @@ impl CacheEntry { directories.push(ListingEntry { name: n.to_owned(), url: format!("{n}{path}"), - size: Size(0), - crc32: "-".to_string(), + size: 0.into(), + crc32: Crc32(None), is_dir: true, }); } else { files.push(ListingEntry { name: n.to_owned(), url: format!("{n}{path}"), - size: Size(entry.uncompressed_size), - crc32: hex::encode(entry.crc32.to_le_bytes()), + size: entry.uncompressed_size.into(), + crc32: Crc32(Some(entry.crc32)), is_dir: false, }); } @@ -411,3 +456,205 @@ impl CacheEntry { } } } + +impl From for Size { + fn from(value: u32) -> Self { + Self(value) + } +} + +#[cfg(test)] +mod tests { + use std::{net::Ipv4Addr, str::FromStr}; + + use rstest::{fixture, rstest}; + use temp_testdir::TempDir; + + use super::*; + + struct TdCache { + cache: Cache, + api: ArtifactApi, + td: TempDir, + } + + impl TdCache { + async fn get_entry(&self, subdomain: &str) -> Result { + self.cache + .get_entry( + &self.api, + &ArtifactQuery::from_subdomain(subdomain, &HashMap::new()).unwrap(), + &IpAddr::V4(Ipv4Addr::LOCALHOST), + ) + .await + } + } + + #[fixture] + fn cache() -> TdCache { + let td = TempDir::default(); + util::tests::setup_cache_dir(&td); + let cfg = Config::from_data(crate::ConfigData { + cache_dir: td.to_path_buf(), + ..Default::default() + }) + .unwrap(); + let cache = Cache::new(cfg.clone()); + let api = ArtifactApi::new(cfg); + TdCache { cache, api, td } + } + + const S1: &str = "codeberg-org--thetadev--artifactview-test--1-1"; + const Z1: &str = "codeberg-org--thetadev--artifactview-test--1-1.zip"; + const S2: &str = "codeberg-org--thetadev--artifactview-test--1-2"; + const Z2: &str = "codeberg-org--thetadev--artifactview-test--1-2.zip"; + const S3: &str = "codeberg-org--thetadev--artifactview-test--1-3"; + const Z3: &str = "codeberg-org--thetadev--artifactview-test--1-3.zip"; + + #[rstest] + #[tokio::test] + async fn get_entry(cache: TdCache) { + let entry = cache.get_entry(S1).await.unwrap(); + + assert_eq!(entry.entry.name, "view"); + assert_eq!(entry.zip_path, path!(cache.td / Z1)); + assert!(!entry.downloaded); + + let files = entry.entry.get_files(); + let mut filenames = files.iter().map(|f| f.name.as_str()).collect::>(); + filenames.sort(); + assert_eq!( + filenames, + [ + ".well-known/test.txt", + "README.md", + "example.rs", + "junit/hello.junit.xml", + "junit/retry.junit.xml", + "junit/simple.junit.xml", + "robots.txt", + "sites/index.html", + "sites/style.css" + ] + ); + } + + #[rstest] + #[tokio::test] + async fn garbage_collect(cache: TdCache) { + let ago = SystemTime::now() - Duration::from_secs(13 * 3600); + let file = std::fs::File::open(path!(cache.td / Z1)).unwrap(); + file.set_times(FileTimes::new().set_accessed(ago)).unwrap(); + let file = std::fs::File::open(path!(cache.td / Z2)).unwrap(); + file.set_times(FileTimes::new().set_accessed(ago)).unwrap(); + + // Access artifact 1, artifact 2 should be deleted + cache.get_entry(S1).await.unwrap(); + + cache.cache.garbage_collect().await.unwrap(); + + assert!(path!(cache.td / Z1).is_file()); + assert!(path!(cache.td / format!("{S1}.name")).is_file()); + assert!(path!(cache.td / Z3).is_file()); + assert!(path!(cache.td / format!("{S3}.name")).is_file()); + assert!(!path!(cache.td / Z2).is_file()); + assert!(!path!(cache.td / format!("{S2}.name")).is_file()); + } + + #[rstest] + #[tokio::test] + async fn get_file(cache: TdCache) { + let entry = cache.get_entry(S1).await.unwrap(); + let res = entry.entry.get_file("example.rs", "").unwrap(); + if let GetFileResult::File(file) = res { + assert_eq!(file.filename, Some("example.rs".to_string())); + assert_eq!(file.file.crc32, 0x2013120c); + assert_eq!(file.status, StatusCode::OK); + assert_eq!(file.mime, Some(Mime::from_str("text/x-rust").unwrap())); + } else { + panic!("no file") + } + } + + #[rstest] + #[tokio::test] + async fn get_file_spa(cache: TdCache) { + let entry = cache.get_entry(S3).await.unwrap(); + let res = entry.entry.get_file("foo/bar", "").unwrap(); + if let GetFileResult::File(file) = res { + assert_eq!(file.filename, None); + assert_eq!(file.file.crc32, 0xBE336584); + assert_eq!(file.status, StatusCode::OK); + assert_eq!(file.mime, Some(Mime::from_str("text/html").unwrap())); + } else { + panic!("no file") + } + } + + #[rstest] + #[tokio::test] + async fn get_file_404(cache: TdCache) { + let entry = cache.get_entry(S2).await.unwrap(); + let res = entry.entry.get_file("foo/bar", "").unwrap(); + if let GetFileResult::File(file) = res { + assert_eq!(file.filename, None); + assert_eq!(file.file.crc32, 0x69F73F18); + assert_eq!(file.status, StatusCode::NOT_FOUND); + assert_eq!(file.mime, Some(Mime::from_str("text/html").unwrap())); + } else { + panic!("no file") + } + } + + #[rstest] + #[case("", &[ + ".well-known/", + "junit/", + "sites/", + "README.md", + "example.rs", + "robots.txt" + ])] + #[case("C=N&O=D", &[ + "sites/", + "junit/", + ".well-known/", + "robots.txt", + "example.rs", + "README.md", + ])] + #[case("C=S&O=A", &[ + ".well-known/", + "junit/", + "sites/", + "robots.txt", + "example.rs", + "README.md", + ])] + #[case("C=S&O=D", &[ + ".well-known/", + "junit/", + "sites/", + "README.md", + "example.rs", + "robots.txt" + ])] + #[tokio::test] + async fn get_file_listing(cache: TdCache, #[case] query: &str, #[case] expect: &[&str]) { + let entry = cache.get_entry(S1).await.unwrap(); + let res = entry.entry.get_file("", query).unwrap(); + if let GetFileResult::Listing(listing) = res { + let filenames = listing + .entries + .iter() + .map(|e| e.name.as_str()) + .collect::>(); + assert_eq!(filenames, expect); + assert_eq!(listing.n_dirs, 3); + assert_eq!(listing.n_files, 3); + assert!(!listing.has_parent); + } else { + panic!("no listing") + } + } +} diff --git a/src/config.rs b/src/config.rs index 8047516..050a89b 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,6 +5,7 @@ use std::{ sync::Arc, }; +use secrecy::SecretString; use serde::Deserialize; use crate::{ @@ -27,6 +28,8 @@ struct ConfigInner { pub struct ConfigData { /// Folder where the downloaded artifacts are stored pub cache_dir: PathBuf, + /// Port number of the web server + pub port: u16, /// Root domain under which the server is available /// /// The individual artifacts are served under `.` @@ -46,7 +49,9 @@ pub struct ConfigData { /// GitHub API token for downloading GitHub artifacts /// /// Using a fine-grained token with public read permissions is recommended. - pub github_token: Option, + pub github_token: Option, + /// Forgejo/Gitea API tokens by host + pub forgejo_tokens: HashMap, /// Number of artifact indexes to keep in memory pub mem_cache_size: usize, /// Get the client IP address from a HTTP request header @@ -59,18 +64,28 @@ pub struct ConfigData { pub real_ip_header: Option, /// Limit the amount of downloaded artifacts per IP address and minute pub limit_artifacts_per_min: Option, + /// Limit the amount of PR comment API requests per IP address and minute + pub limit_pr_comments_per_min: Option, /// List of sites/users/repos that can NOT be accessed pub repo_blacklist: QueryFilterList, /// List of sites/users/repos that can ONLY be accessed pub repo_whitelist: QueryFilterList, + /// List of suggested code forges (host only, without https://) + /// + /// If repo_whitelist is empty, this value is used for the matched sites in the userscript + /// as well as the url placeholder on the home page + pub suggested_sites: Vec, /// Aliases for sites (Example: `gh => github.com`) pub site_aliases: HashMap, + /// Maximum file size for the viewer + pub viewer_max_size: Option, } impl Default for ConfigData { fn default() -> Self { Self { cache_dir: Path::new("/tmp/artifactview").into(), + port: 3000, root_domain: "localhost:3000".to_string(), no_https: false, max_artifact_size: Some(NonZeroU32::new(100_000_000).unwrap()), @@ -79,12 +94,20 @@ impl Default for ConfigData { max_age_h: NonZeroU32::new(12).unwrap(), zip_timeout_ms: Some(NonZeroU32::new(1000).unwrap()), github_token: None, + forgejo_tokens: HashMap::new(), mem_cache_size: 50, real_ip_header: None, limit_artifacts_per_min: Some(NonZeroU32::new(5).unwrap()), + limit_pr_comments_per_min: Some(NonZeroU32::new(5).unwrap()), repo_blacklist: QueryFilterList::default(), repo_whitelist: QueryFilterList::default(), + suggested_sites: vec![ + String::from("codeberg.org"), + String::from("github.com"), + String::from("gitea.com"), + ], site_aliases: HashMap::new(), + viewer_max_size: Some(NonZeroU32::new(500_000).unwrap()), } } } @@ -108,7 +131,7 @@ impl ConfigData { impl Config { pub fn new() -> Result { let data = - envy::from_env::().map_err(|e| Error::Internal(e.to_string().into()))?; + envy::from_env::().map_err(|e| Error::Other(e.to_string().into()))?; Self::from_data(data) } @@ -148,7 +171,16 @@ impl Config { &self.i.main_url } - pub fn check_filterlist(&self, query: &Query) -> Result<()> { + pub fn example_site(&self) -> &str { + self.i + .data + .repo_whitelist + .first_host() + .or_else(|| self.i.data.suggested_sites.first().map(|s| s.as_str())) + .unwrap_or("codeberg.org") + } + + pub fn check_filterlist(&self, query: &Q) -> Result<()> { if !self.i.data.repo_blacklist.passes(query, true) { Err(Error::Forbidden("repository is blacklisted".into())) } else if !self.i.data.repo_whitelist.passes(query, false) { diff --git a/src/error.rs b/src/error.rs index 336cc47..0c6d30a 100644 --- a/src/error.rs +++ b/src/error.rs @@ -20,8 +20,8 @@ pub enum Error { Io(#[from] std::io::Error), #[error("Zip: {0}")] Zip(#[from] async_zip::error::ZipError), - #[error("Internal error: {0}")] - Internal(Cow<'static, str>), + #[error("Error: {0}")] + Other(Cow<'static, str>), #[error("Invalid request: {0}")] BadRequest(Cow<'static, str>), @@ -35,12 +35,16 @@ pub enum Error { Inaccessible, #[error("This artifact has already expired")] Expired, - #[error("timeout")] + #[error("This action took too long")] Timeout(#[from] tokio::time::error::Elapsed), #[error("Method not allowed")] MethodNotAllowed, #[error("You are fetching new artifacts too fast, please wait a minute and try again")] Ratelimit, + #[error("viewer: {0}")] + Viewer(Cow<'static, str>), + #[error("viewer not applicable")] + ViewerNotApplicable, } impl From for Error { @@ -54,13 +58,13 @@ impl From for Error { impl From for Error { fn from(value: std::num::TryFromIntError) -> Self { - Self::Internal(value.to_string().into()) + Self::Other(value.to_string().into()) } } impl From for Error { fn from(value: url::ParseError) -> Self { - Self::Internal(value.to_string().into()) + Self::Other(value.to_string().into()) } } diff --git a/src/lib.rs b/src/lib.rs index c3adf4a..78fd696 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,5 +7,8 @@ mod gzip_reader; mod query; mod templates; mod util; +mod viewer; -pub struct App; +pub use app::{App, AppState}; +pub use config::{Config, ConfigData}; +pub use error::Error; diff --git a/src/query.rs b/src/query.rs index 5e9d872..51106cd 100644 --- a/src/query.rs +++ b/src/query.rs @@ -1,4 +1,7 @@ -use std::{collections::HashMap, fmt::Write, str::FromStr}; +use std::{ + collections::{BTreeSet, HashMap}, + str::FromStr, +}; use once_cell::sync::Lazy; use regex::{Captures, Regex}; @@ -6,53 +9,115 @@ use serde::{de::Visitor, Deserialize}; use crate::{ error::{Error, Result}, + templates::LinkItem, util, }; +/// Query to select an artifact #[derive(Debug, PartialEq, Eq)] -pub enum Query { - Artifact(ArtifactQuery), - Run(RunQuery), -} - -pub type RunQuery = QueryData<()>; -pub type ArtifactQuery = QueryData; - -#[derive(Debug, PartialEq, Eq)] -pub struct QueryData { +pub struct ArtifactQuery { /// Forge host pub host: String, /// Host alias if the query was constructed using one - pub host_alias: Option, + host_alias: Option, /// User/org name (case-insensitive) pub user: String, /// Repository name (case-insensitive) pub repo: String, /// CI run id pub run: u64, - // Optional selected artifact - pub artifact: T, + /// CI artifact id + pub artifact: u64, +} + +/// Query to select a CI run (set of artifacts) +#[derive(Debug, PartialEq, Eq)] +pub struct RunQuery { + /// Forge host + pub host: String, + /// Host alias if the query was constructed using one + host_alias: Option, + /// User/org name (case-insensitive) + pub user: String, + /// Repository name (case-insensitive) + pub repo: String, + /// CI run id + pub run: u64, +} + +#[derive(Copy, Clone)] +pub struct QueryRef<'a> { + /// Forge host + pub host: &'a str, + /// Host alias if the query was constructed using one + host_alias: Option<&'a str>, + /// User/org name (case-insensitive) + pub user: &'a str, + /// Repository name (case-insensitive) + pub repo: &'a str, + /// CI run id + pub run: u64, +} + +pub trait Query { + fn as_ref(&self) -> QueryRef<'_>; + + fn shortid(&self) -> String { + let q = self.as_ref(); + format!("{}/{}#{}", q.user, q.repo, q.run) + } + + fn forge_url(&self) -> String { + let q = self.as_ref(); + format!( + "https://{}/{}/{}/actions/runs/{}", + q.host, q.user, q.repo, q.run + ) + } + + fn is_github(&self) -> bool { + self.as_ref().host == "github.com" + } + + fn subdomain_with_artifact(&self, artifact: u64) -> String { + let q = self.as_ref(); + let host = q.host_alias.unwrap_or(q.host); + + format!( + "{}--{}--{}--{}-{}", + encode_domain(host, '.'), + encode_domain(q.user, '-'), + encode_domain(q.repo, '-'), + q.run, + artifact, + ) + } + + fn publisher(&self) -> LinkItem { + let q = self.as_ref(); + LinkItem { + name: q.user.to_owned(), + url: format!("https://{}/{}", q.host, q.user), + } + } } static RE_REPO_NAME: Lazy = Lazy::new(|| Regex::new("^[a-z0-9\\-_\\.]+$").unwrap()); -impl Query { +impl ArtifactQuery { pub fn from_subdomain(subdomain: &str, aliases: &HashMap) -> Result { let segments = subdomain.split("--").collect::>(); if segments.len() != 4 { return Err(Error::InvalidUrl); } - let run_and_artifact = segments[3].split('-').collect::>(); - if run_and_artifact.is_empty() || run_and_artifact.len() > 2 { - return Err(Error::InvalidUrl); - } - let mut host = decode_domain(segments[0], '.'); let mut host_alias = None; let user = decode_domain(segments[1], '-'); let repo = decode_domain(segments[2], '-'); - let run = run_and_artifact[0].parse().ok().ok_or(Error::InvalidUrl)?; + let run_and_artifact = segments[3].split_once('-').ok_or(Error::InvalidUrl)?; + let run = run_and_artifact.0.parse().ok().ok_or(Error::InvalidUrl)?; + let artifact = run_and_artifact.1.parse().ok().ok_or(Error::InvalidUrl)?; #[allow(clippy::assigning_clones)] if let Some(alias) = aliases.get(&host) { @@ -60,27 +125,34 @@ impl Query { host = alias.clone(); } - Ok(match run_and_artifact.get(1) { - Some(x) => Self::Artifact(QueryData { - host, - host_alias, - user, - repo, - run, - artifact: x.parse().ok().ok_or(Error::InvalidUrl)?, - }), - None => Self::Run(QueryData { - host, - host_alias, - user, - repo, - run, - artifact: (), - }), + Ok(ArtifactQuery { + host, + host_alias, + user, + repo, + run, + artifact, }) } - pub fn from_forge_url(url: &str, aliases: &HashMap) -> Result { + pub fn cache_key(&self) -> String { + format!( + "{}--{}--{}--{}-{}", + encode_domain(&self.host, '.'), + encode_domain(&self.user, '-'), + encode_domain(&self.repo, '-'), + self.run, + self.artifact, + ) + } +} + +impl RunQuery { + pub fn from_forge_url(url: &str) -> Result { + Self::from_forge_url_alias(url, &HashMap::new()) + } + + pub fn from_forge_url_alias(url: &str, aliases: &HashMap) -> Result { let (host, mut path_segs) = util::parse_url(url)?; let user = path_segs @@ -92,8 +164,8 @@ impl Query { .ok_or(Error::BadRequest("no repository".into()))? .to_ascii_lowercase(); - if !path_segs.next().is_some_and(|s| s == "actions") - || !path_segs.next().is_some_and(|s| s == "runs") + if path_segs.next().is_none_or(|s| s != "actions") + || path_segs.next().is_none_or(|s| s != "runs") { return Err(Error::BadRequest("invalid Actions URL".into())); } @@ -104,118 +176,74 @@ impl Query { return Err(Error::BadRequest("invalid repository name".into())); } - let host = aliases + let host_alias = aliases .iter() .find(|(_, v)| *v == host) - .map(|(k, _)| k.to_owned()) - .unwrap_or_else(|| host.to_owned()); + .map(|(k, _)| k.to_owned()); let run = path_segs .next() .and_then(|s| s.parse::().ok()) .ok_or(Error::BadRequest("no run ID".into()))?; - Ok(Self::Run(RunQuery { - host, - host_alias: None, + Ok(Self { + host: host.to_owned(), + host_alias, user, repo, run, - artifact: (), - })) + }) } - pub fn subdomain(&self) -> Result { - match self { - Query::Artifact(q) => q.subdomain(), - Query::Run(q) => q.subdomain(), - } - } - - pub fn into_runquery(self) -> RunQuery { - match self { - Query::Artifact(q) => q.into_runquery(), - Query::Run(q) => q, - } - } - - pub fn try_into_artifactquery(self) -> Result { - match self { - Query::Artifact(q) => Ok(q), - Query::Run(_) => Err(Error::BadRequest("no artifact specified".into())), - } - } -} - -impl ArtifactQuery { - pub fn subdomain(&self) -> Result { - self.subdomain_with_artifact(Some(self.artifact)) - } - - /// Non-shortened subdomain (used for cache storage) - pub fn subdomain_noalias(&self) -> String { - self._subdomain(Some(self.artifact), false) - } -} - -impl RunQuery { - pub fn subdomain(&self) -> Result { - self.subdomain_with_artifact(None) - } -} - -impl QueryData { - pub fn _subdomain(&self, artifact: Option, use_alias: bool) -> String { - let host = if use_alias { - self.host_alias.as_deref().unwrap_or(&self.host) - } else { - &self.host - }; - - let mut res = format!( + pub fn cache_key(&self) -> String { + format!( "{}--{}--{}--{}", - encode_domain(host, '.'), + encode_domain(&self.host, '.'), encode_domain(&self.user, '-'), encode_domain(&self.repo, '-'), self.run, - ); - if let Some(artifact) = artifact { - write!(res, "-{artifact}").unwrap(); - } - res - } - - pub fn subdomain_with_artifact(&self, artifact: Option) -> Result { - let res = self._subdomain(artifact, true); - if res.len() > 63 { - return Err(Error::BadRequest("subdomain too long".into())); - } - Ok(res) - } - - pub fn shortid(&self) -> String { - format!("{}/{}#{}", self.user, self.repo, self.run) - } - - pub fn forge_url(&self) -> String { - format!( - "https://{}/{}/{}/actions/runs/{}", - self.host, self.user, self.repo, self.run ) } +} - pub fn is_github(&self) -> bool { - self.host == "github.com" - } - - pub fn into_runquery(self) -> RunQuery { - RunQuery { - host: self.host, - host_alias: self.host_alias, - user: self.user, - repo: self.repo, +impl Query for ArtifactQuery { + fn as_ref(&self) -> QueryRef<'_> { + QueryRef { + host: &self.host, + host_alias: self.host_alias.as_deref(), + user: &self.user, + repo: &self.repo, run: self.run, - artifact: (), + } + } +} + +impl Query for RunQuery { + fn as_ref(&self) -> QueryRef<'_> { + QueryRef { + host: &self.host, + host_alias: self.host_alias.as_deref(), + user: &self.user, + repo: &self.repo, + run: self.run, + } + } +} + +impl Query for QueryRef<'_> { + fn as_ref(&self) -> QueryRef<'_> { + *self + } +} + +impl From for RunQuery { + fn from(value: ArtifactQuery) -> Self { + Self { + host: value.host, + host_alias: value.host_alias, + user: value.user, + repo: value.repo, + run: value.run, } } } @@ -223,11 +251,11 @@ impl QueryData { fn encode_domain(s: &str, bias: char) -> String { // Check if the character at the given position is in the middle of the string // and it is not followed by escape seq numbers or further escapable characters - let is_mid_single = |pos: usize| -> bool { - if pos == 0 || pos >= (s.len() - 1) { + let is_mid_single = |str: &str, pos: usize| -> bool { + if pos == 0 || pos >= (str.len() - 1) { return false; } - let next_char = s[pos..].chars().nth(1).unwrap(); + let next_char = str[pos..].chars().nth(1).unwrap(); !('0'..='2').contains(&next_char) && !matches!(next_char, '-' | '.' | '_') }; @@ -236,7 +264,7 @@ fn encode_domain(s: &str, bias: char) -> String { let mut last_pos = 0; for (pos, c) in s.match_indices('-') { buf += &s[last_pos..pos]; - if bias == '-' && is_mid_single(pos) { + if bias == '-' && is_mid_single(s, pos) { buf.push('-'); } else { buf += "-1"; @@ -251,7 +279,7 @@ fn encode_domain(s: &str, bias: char) -> String { for (pos, c) in buf.match_indices(['.', '_']) { buf2 += &buf[last_pos..pos]; let cchar = c.chars().next().unwrap(); - if cchar == bias && is_mid_single(pos) { + if cchar == bias && is_mid_single(&buf, pos) { buf2.push('-'); } else if cchar == '.' { buf2 += "-0" @@ -307,12 +335,12 @@ impl FromStr for QueryFilter { if let Some(user) = &user { if !RE_REPO_NAME.is_match(user) { - return Err(Error::Internal("invalid username".into())); + return Err(Error::Other("invalid username".into())); } } if let Some(repo) = &repo { if !RE_REPO_NAME.is_match(repo) { - return Err(Error::Internal("invalid repository name".into())); + return Err(Error::Other("invalid repository name".into())); } } @@ -325,14 +353,11 @@ impl FromStr for QueryFilter { } impl QueryFilter { - pub fn passes(&self, query: &Query) -> bool { - let (host, user, repo) = match query { - Query::Artifact(q) => (&q.host, &q.user, &q.repo), - Query::Run(q) => (&q.host, &q.user, &q.repo), - }; - &self.host == host - && self.user.as_deref().map(|u| u == user).unwrap_or(true) - && self.repo.as_deref().map(|r| r == repo).unwrap_or(true) + pub fn passes(&self, query: &Q) -> bool { + let q = query.as_ref(); + self.host == q.host + && self.user.as_deref().map(|u| u == q.user).unwrap_or(true) + && self.repo.as_deref().map(|r| r == q.repo).unwrap_or(true) } } @@ -349,13 +374,25 @@ impl FromStr for QueryFilterList { } impl QueryFilterList { - pub fn passes(&self, query: &Query, blacklist: bool) -> bool { + pub fn passes(&self, query: &Q, blacklist: bool) -> bool { if self.0.is_empty() { true } else { self.0.iter().any(|itm| itm.passes(query)) ^ blacklist } } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn first_host(&self) -> Option<&str> { + self.0.first().map(|f| f.host.as_str()) + } + + pub fn hosts(&self) -> BTreeSet<&str> { + self.0.iter().map(|f| f.host.as_str()).collect() + } } impl<'de> Deserialize<'de> for QueryFilterList { @@ -365,7 +402,7 @@ impl<'de> Deserialize<'de> for QueryFilterList { { struct QueryFilterListVisitor; - impl<'de> Visitor<'de> for QueryFilterListVisitor { + impl Visitor<'_> for QueryFilterListVisitor { type Value = QueryFilterList; fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { @@ -388,9 +425,9 @@ impl<'de> Deserialize<'de> for QueryFilterList { mod tests { use std::{collections::HashMap, str::FromStr}; - use crate::query::{QueryFilter, QueryFilterList}; + use crate::query::{Query, QueryFilter, QueryFilterList}; - use super::{ArtifactQuery, Query}; + use super::ArtifactQuery; use proptest::prelude::*; use rstest::rstest; @@ -426,19 +463,19 @@ mod tests { #[test] fn query_from_subdomain() { let d1 = "github-com--thetadev--newpipe-extractor--14-123"; - let query = Query::from_subdomain(d1, &HashMap::new()).unwrap(); + let query = ArtifactQuery::from_subdomain(d1, &HashMap::new()).unwrap(); assert_eq!( query, - Query::Artifact(ArtifactQuery { + ArtifactQuery { host: "github.com".to_owned(), host_alias: None, user: "thetadev".to_owned(), repo: "newpipe-extractor".to_owned(), run: 14, artifact: 123 - }) + } ); - assert_eq!(query.subdomain().unwrap(), d1); + assert_eq!(query.subdomain_with_artifact(query.artifact), d1); } #[rstest] diff --git a/src/snapshots/artifactview__app__tests__pr_comment_1.snap b/src/snapshots/artifactview__app__tests__pr_comment_1.snap new file mode 100644 index 0000000..6491c49 --- /dev/null +++ b/src/snapshots/artifactview__app__tests__pr_comment_1.snap @@ -0,0 +1,11 @@ +--- +source: src/app.rs +expression: res +--- +### Latest build artifacts + + Run [#1](https://code.thetadev.de/thetadev/test-actions/actions/runs/1) ยท [15eed48a83](https://code.thetadev.de/thetadev/test-actions/commit/15eed48a8382513147a949117ef4aa659989d397) ยท 15.06.2024 01:30:00 UTC + +๐Ÿ  Hello World ;-)
+Test
+ diff --git a/src/snapshots/artifactview__app__tests__pr_comment_2.snap b/src/snapshots/artifactview__app__tests__pr_comment_2.snap new file mode 100644 index 0000000..3046f07 --- /dev/null +++ b/src/snapshots/artifactview__app__tests__pr_comment_2.snap @@ -0,0 +1,16 @@ +--- +source: src/app.rs +expression: res +--- +### Latest build artifacts + + Run [#2](https://code.thetadev.de/thetadev/test-actions/actions/runs/2) ยท [25eed48a83](https://code.thetadev.de/thetadev/test-actions/commit/25eed48a8382513147a949117ef4aa659989d397) ยท 15.06.2024 02:30:00 UTC + +๐Ÿ  Hello World ;-)
+Test
+
+Previous builds + +- [#1](https://code.thetadev.de/thetadev/test-actions/actions/runs/1) [[15eed48a83](https://code.thetadev.de/thetadev/test-actions/commit/15eed48a8382513147a949117ef4aa659989d397)] Artifacts: `Hello`, `Test` (15.06.2024 01:30:00 UTC) + +
diff --git a/src/snapshots/artifactview__app__tests__pr_comment_3.snap b/src/snapshots/artifactview__app__tests__pr_comment_3.snap new file mode 100644 index 0000000..02b7544 --- /dev/null +++ b/src/snapshots/artifactview__app__tests__pr_comment_3.snap @@ -0,0 +1,17 @@ +--- +source: src/app.rs +expression: res +--- +### Latest build artifacts + + Run [#3](https://code.thetadev.de/thetadev/test-actions/actions/runs/3) ยท [35eed48a83](https://code.thetadev.de/thetadev/test-actions/commit/35eed48a8382513147a949117ef4aa659989d397) ยท 15.06.2024 03:30:00 UTC + +๐Ÿ  Hello World ;-)
+Test
+
+Previous builds + +- [#1](https://code.thetadev.de/thetadev/test-actions/actions/runs/1) [[15eed48a83](https://code.thetadev.de/thetadev/test-actions/commit/15eed48a8382513147a949117ef4aa659989d397)] Artifacts: `Hello`, `Test` (15.06.2024 01:30:00 UTC) +- [#2](https://code.thetadev.de/thetadev/test-actions/actions/runs/2) [[25eed48a83](https://code.thetadev.de/thetadev/test-actions/commit/25eed48a8382513147a949117ef4aa659989d397)] Artifacts: `Hello`, `Test` (15.06.2024 02:30:00 UTC) + +
diff --git a/src/templates.rs b/src/templates.rs index bc36b21..7af5f0e 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -1,19 +1,19 @@ +use std::collections::BTreeMap; + use crate::{ artifact_api::Artifact, - cache::{ListingEntry, Size}, + cache::{Crc32, ListingEntry, Size}, config::Config, - error::Result, - query::QueryData, + query::{Query, QueryRef}, }; +use junit_parser::TestSuites; use yarte::{Render, Template}; -#[derive(Default)] -pub struct Version; - -#[derive(Template, Default)] +#[derive(Template)] #[template(path = "index")] -pub struct Index { - pub version: Version, +pub struct Index<'a> { + pub main_url: &'a str, + pub example_site: &'a str, } #[derive(Template)] @@ -27,7 +27,6 @@ pub struct Error<'a> { #[template(path = "selection")] pub struct Selection<'a> { pub main_url: &'a str, - pub version: Version, pub run_url: &'a str, pub run_name: &'a str, pub publisher: LinkItem, @@ -38,16 +37,53 @@ pub struct Selection<'a> { #[template(path = "listing")] pub struct Listing<'a> { pub main_url: &'a str, - pub version: Version, pub run_url: &'a str, pub artifact_name: &'a str, pub path_components: Vec, pub n_dirs: usize, pub n_files: usize, pub has_parent: bool, + pub publisher: LinkItem, + pub viewer_max_size: u32, pub entries: Vec, } +#[derive(Template)] +#[template(path = "preview")] +pub struct Preview<'a> { + pub main_url: &'a str, + pub run_url: &'a str, + pub filename: &'a str, + pub path_components: Vec, + pub publisher: LinkItem, + pub lines: usize, + pub size: Size, + pub viewers: Vec, + pub body: &'a str, +} + +#[derive(Template)] +#[template(path = "junit")] +pub struct Junit { + pub suites: TestSuites, +} + +#[derive(Template)] +#[template(path = "userscript")] +pub struct Userscript<'a> { + pub main_url: &'a str, + pub root_domain: &'a str, + pub no_https: bool, + pub forge_urls: Vec, + pub aliases: &'a BTreeMap<&'a str, &'a str>, +} + +pub struct ViewerLink { + pub id: &'static str, + pub name: &'static str, + pub selected: bool, +} + pub struct LinkItem { pub name: String, pub url: String, @@ -62,24 +98,14 @@ pub struct ArtifactItem { } impl ArtifactItem { - pub fn from_artifact( - artifact: Artifact, - query: &QueryData, - cfg: &Config, - ) -> Result { - Ok(Self { + pub fn from_artifact(artifact: Artifact, query: QueryRef<'_>, cfg: &Config) -> Self { + Self { name: artifact.name, - url: cfg.url_with_subdomain(&query.subdomain_with_artifact(Some(artifact.id))?), + url: cfg.url_with_subdomain(&query.subdomain_with_artifact(artifact.id)), size: Size(artifact.size as u32), expired: artifact.expired, download_url: artifact.user_download_url.unwrap_or(artifact.download_url), - }) - } -} - -impl Render for Version { - fn render(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str(env!("CARGO_PKG_VERSION")) + } } } @@ -92,3 +118,44 @@ impl Render for Size { ) } } + +impl Render for Crc32 { + fn render(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self.0 { + Some(crc) => write!(f, "{crc:08x}"), + None => f.write_str("—"), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Template)] + #[template(src = "{{ rendered }}")] + struct RenderTemplate { + rendered: T, + } + + #[test] + fn crc32() { + let tmpl = RenderTemplate { + rendered: Crc32(Some(0xc538cf99)), + }; + assert_eq!(tmpl.to_string(), "c538cf99"); + + let tmpl = RenderTemplate { + rendered: Crc32(None), + }; + assert_eq!(tmpl.to_string(), "—"); + } + + #[test] + fn size() { + let tmpl = RenderTemplate { + rendered: Size(1000), + }; + assert_eq!(tmpl.to_string(), "1 kB"); + } +} diff --git a/src/util.rs b/src/util.rs index 2a0d3f7..d5d43c0 100644 --- a/src/util.rs +++ b/src/util.rs @@ -194,7 +194,7 @@ pub fn get_ip_address(request: &Request, real_ip_header: Option<&str>) -> Result let socket_addr = request .extensions() .get::>() - .ok_or(Error::Internal("could get request ip address".into()))? + .ok_or(Error::Other("could get request ip address".into()))? .0; Ok(socket_addr.ip()) } @@ -241,12 +241,37 @@ pub fn parse_url(input: &str) -> Result<(&str, std::str::Split)> { Ok((host, parts)) } +pub fn time_to_ms(time: f64) -> u64 { + (time * 1000.0) as u64 +} + +/// Get the extension from a filename for selecting a viewer +pub fn filename_ext(filename: &str) -> &str { + let mut rsplit = filename.rsplit('.'); + let ext = rsplit.next().unwrap(); + if filename.starts_with('.') && rsplit.next().map(str::is_empty).unwrap_or(true) { + // Dotfile without extension (e.g. .bashrc) + filename + } else { + ext + } +} + #[derive(Serialize)] pub struct ErrorJson { status: u16, msg: String, } +impl ErrorJson { + pub fn ok>(msg: S) -> Self { + Self { + status: 200, + msg: msg.into(), + } + } +} + impl From for ErrorJson { fn from(value: Error) -> Self { Self { @@ -264,15 +289,62 @@ impl From for ErrorJson { impl IntoResponse for ErrorJson { fn into_response(self) -> Response { - Response::builder().json(&self).unwrap() + Response::builder().status(self.status).json(&self).unwrap() } } +pub fn extract_delim<'a>(s: &'a str, start: &str, end: &str) -> Option<&'a str> { + if let Some(np) = s.find(start) { + if let Some(np_end) = s[np + start.len()..].find(end) { + return Some(s[np + start.len()..np + start.len() + np_end].trim()); + } + } + None +} + +pub fn split_icon_prefix(s: &str) -> (&str, &str) { + if let Some((i, c)) = s + .char_indices() + .find(|(_, c)| c.is_ascii() || c.is_alphanumeric()) + { + if i > 0 && c == ' ' && s.get(i + 1..).is_some() { + return (&s[..i + 1], &s[i + 1..]); + } + } + ("", s) +} + #[cfg(test)] -mod tests { +pub(crate) mod tests { + use std::path::{Path, PathBuf}; + use http::{header, HeaderMap}; + use once_cell::sync::Lazy; + use path_macro::path; use rstest::rstest; + pub static TESTFILES: Lazy = + Lazy::new(|| path!(env!("CARGO_MANIFEST_DIR") / "tests" / "testfiles")); + + static SITEDIR: Lazy = Lazy::new(|| { + let sitedir = path!(*TESTFILES / "sites_data"); + if !sitedir.is_dir() { + std::process::Command::new(path!(*TESTFILES / "sites" / "make_zip.sh")) + .output() + .unwrap(); + } + sitedir + }); + + pub fn setup_cache_dir(dir: &Path) { + for entry in std::fs::read_dir(SITEDIR.as_path()).unwrap() { + let entry = entry.unwrap(); + if entry.file_type().unwrap().is_file() { + std::fs::copy(entry.path(), path!(dir / entry.file_name())).unwrap(); + } + } + } + #[rstest] #[case("", false)] #[case("br", false)] @@ -320,4 +392,26 @@ mod tests { } } } + + #[rstest] + #[case("hello.txt", "txt")] + #[case(".bashrc", ".bashrc")] + #[case("Makefile", "Makefile")] + #[case("", "")] + fn filename_ext(#[case] filename: &str, #[case] expect: &str) { + let res = super::filename_ext(filename); + assert_eq!(res, expect); + } + + #[rstest] + #[case("๐Ÿงช Test", ("๐Ÿงช ", "Test"))] + #[case("๐Ÿงช๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ Test", ("๐Ÿงช๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ ", "Test"))] + #[case("๐Ÿงช ๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ Test", ("๐Ÿงช ", "๐Ÿ‘จโ€๐Ÿ‘ฉโ€๐Ÿ‘ฆ Test"))] + #[case("", ("", ""))] + #[case("Test", ("", "Test"))] + #[case("้‹ๅ‘ฝ Test", ("", "้‹ๅ‘ฝ Test"))] + fn split_icon_prefix(#[case] s: &str, #[case] expect: (&str, &str)) { + let res = super::split_icon_prefix(s); + assert_eq!(res, expect); + } } diff --git a/src/viewer/code.rs b/src/viewer/code.rs new file mode 100644 index 0000000..e8bdf99 --- /dev/null +++ b/src/viewer/code.rs @@ -0,0 +1,91 @@ +use std::{collections::HashMap, sync::Arc}; + +use syntect::{ + html::{ClassStyle, ClassedHTMLGenerator}, + parsing::SyntaxSet, + util::LinesWithEndings, +}; + +use crate::error::Error; + +use super::Viewer; + +pub struct CodeViewer { + ss: Arc, + smap: HashMap, +} + +impl CodeViewer { + pub fn new(ss: Arc) -> Self { + let smap = ss + .syntaxes() + .iter() + .enumerate() + .flat_map(|(i, s)| s.file_extensions.iter().map(move |ext| (ext.to_owned(), i))) + .collect::>(); + + Self { ss, smap } + } +} + +impl Viewer for CodeViewer { + fn id(&self) -> &'static str { + "code" + } + + fn name(&self) -> &'static str { + "Code" + } + + fn is_applicable(&self, _filename: &str, ext: &str) -> bool { + self.smap.contains_key(ext) + } + + fn try_render(&self, _filename: &str, ext: &str, data: &str) -> Result { + let i = self.smap.get(ext).ok_or(Error::ViewerNotApplicable)?; + let syntax = &self.ss.syntaxes()[*i]; + + let mut html_generator = + ClassedHTMLGenerator::new_with_class_style(syntax, &self.ss, ClassStyle::Spaced); + LinesWithEndings::from(data) + .try_for_each(|line| html_generator.parse_html_for_line_which_includes_newline(line)) + .map_err(|e| Error::Viewer(e.to_string().into()))?; + + Ok(format!( + "
{}
", + html_generator.finalize() + )) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn is_applicable() { + let ss = Arc::new(SyntaxSet::load_defaults_newlines()); + let cv = CodeViewer::new(ss); + assert!(cv.is_applicable("hello.txt", "txt")); + assert!(cv.is_applicable(".bashrc", ".bashrc")); + assert!(!cv.is_applicable("image.jpg", "jpg")); + } + + #[test] + fn render() { + let ss = Arc::new(SyntaxSet::load_defaults_newlines()); + let cv = CodeViewer::new(ss); + let res = cv + .try_render( + "hello.rs", + "rs", + r#"fn test() { + let x = "World"; + println!("Hello {x}"); +} +"#, + ) + .unwrap(); + insta::assert_snapshot!(res); + } +} diff --git a/src/viewer/junit.rs b/src/viewer/junit.rs new file mode 100644 index 0000000..a7f4834 --- /dev/null +++ b/src/viewer/junit.rs @@ -0,0 +1,56 @@ +use crate::{error::Error, templates}; + +use super::Viewer; + +/// JUnit format documentation: https://llg.cubic.org/docs/junit/ +pub struct JunitViewer; + +impl Viewer for JunitViewer { + fn id(&self) -> &'static str { + "junit" + } + + fn name(&self) -> &'static str { + "JUnit" + } + + fn is_applicable(&self, filename: &str, ext: &str) -> bool { + ext == "xml" && filename.contains("junit") + } + + fn try_render(&self, filename: &str, _ext: &str, data: &str) -> Result { + let suites = junit_parser::from_str(data).map_err(|e| { + tracing::error!("could not parse junit report {filename}: {e}"); + Error::ViewerNotApplicable + })?; + + let tmpl = templates::Junit { suites }; + Ok(tmpl.to_string()) + } +} + +#[cfg(test)] +mod tests { + use path_macro::path; + + use crate::util::tests::TESTFILES; + + use super::*; + + #[test] + fn is_applicable() { + let ju = JunitViewer; + assert!(ju.is_applicable("junit.xml", "xml")); + assert!(ju.is_applicable("hello.junit.xml", "xml")); + assert!(!ju.is_applicable("hello.xml", "xml")); + } + + #[test] + fn render() { + let ju = JunitViewer; + let data = + std::fs::read_to_string(path!(*TESTFILES / "junit" / "hello.junit.xml")).unwrap(); + let res = ju.try_render("hello.junit.xml", "xml", &data).unwrap(); + insta::assert_snapshot!(res); + } +} diff --git a/src/viewer/markdown.rs b/src/viewer/markdown.rs new file mode 100644 index 0000000..c0ed600 --- /dev/null +++ b/src/viewer/markdown.rs @@ -0,0 +1,140 @@ +use std::{collections::HashMap, io::Write, sync::Arc}; + +use comrak::adapters::SyntaxHighlighterAdapter; +use syntect::{ + html::{ClassStyle, ClassedHTMLGenerator}, + parsing::SyntaxSet, + util::LinesWithEndings, +}; + +use crate::error::Error; + +use super::Viewer; + +pub struct MarkdownViewer { + adapter: SyntectAdapter, +} + +impl MarkdownViewer { + pub fn new(ss: Arc) -> Self { + Self { + adapter: SyntectAdapter { ss }, + } + } +} + +impl Viewer for MarkdownViewer { + fn id(&self) -> &'static str { + "md" + } + + fn name(&self) -> &'static str { + "Markdown" + } + + fn is_applicable(&self, _filename: &str, ext: &str) -> bool { + ext == "md" + } + + fn try_render(&self, _filename: &str, _ext: &str, data: &str) -> Result { + let mut options = comrak::Options::default(); + options.extension.autolink = true; + options.extension.table = true; + options.extension.tasklist = true; + options.extension.strikethrough = true; + options.extension.multiline_block_quotes = true; + options.extension.superscript = true; + + let mut plugins = comrak::Plugins::default(); + plugins.render.codefence_syntax_highlighter = Some(&self.adapter); + + let html = comrak::markdown_to_html_with_plugins(data, &options, &plugins); + + Ok(format!("
{html}
")) + } +} + +struct SyntectAdapter { + ss: Arc, +} + +impl SyntaxHighlighterAdapter for SyntectAdapter { + fn write_highlighted( + &self, + output: &mut dyn Write, + lang: Option<&str>, + code: &str, + ) -> std::io::Result<()> { + let fallback_syntax = "Plain Text"; + + let lang: &str = match lang { + Some(l) if !l.is_empty() => l, + _ => fallback_syntax, + }; + + let syntax = self.ss.find_syntax_by_token(lang).unwrap_or_else(|| { + self.ss + .find_syntax_by_first_line(code) + .unwrap_or_else(|| self.ss.find_syntax_plain_text()) + }); + + let mut html_generator = + ClassedHTMLGenerator::new_with_class_style(syntax, &self.ss, ClassStyle::Spaced); + + if let Err(e) = LinesWithEndings::from(code) + .try_for_each(|line| html_generator.parse_html_for_line_which_includes_newline(line)) + { + tracing::error!("rendering md code: {e}"); + return output.write_all(code.as_bytes()); + } + + let html = html_generator.finalize(); + output.write_all(html.as_bytes()) + } + + fn write_pre_tag( + &self, + output: &mut dyn Write, + _attributes: HashMap, + ) -> std::io::Result<()> { + output.write_all(b"
")
+    }
+
+    fn write_code_tag(
+        &self,
+        output: &mut dyn Write,
+        _attributes: HashMap,
+    ) -> std::io::Result<()> {
+        output.write_all(b"")
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn is_applicable() {
+        let ss = Arc::new(SyntaxSet::load_defaults_newlines());
+        let mv = MarkdownViewer::new(ss);
+        assert!(mv.is_applicable("hello.md", "md"));
+        assert!(!mv.is_applicable("hello.txt", "txt"));
+    }
+
+    #[test]
+    fn render() {
+        let ss = Arc::new(SyntaxSet::load_defaults_newlines());
+        let mv = MarkdownViewer::new(ss);
+        let res = mv
+            .try_render(
+                "hello.md",
+                "md",
+                r#"# Hello World
+
+this is a small paragraph for *testing*.
+"#,
+            )
+            .unwrap();
+        insta::assert_snapshot!(res);
+    }
+}
diff --git a/src/viewer/mod.rs b/src/viewer/mod.rs
new file mode 100644
index 0000000..632b99c
--- /dev/null
+++ b/src/viewer/mod.rs
@@ -0,0 +1,118 @@
+use std::sync::Arc;
+
+use syntect::parsing::SyntaxSet;
+
+use crate::{error::Error, templates::ViewerLink, util};
+
+mod code;
+mod junit;
+mod markdown;
+
+pub trait Viewer: Sync + Send {
+    fn id(&self) -> &'static str;
+    fn name(&self) -> &'static str;
+
+    fn is_applicable(&self, filename: &str, ext: &str) -> bool;
+    fn try_render(&self, filename: &str, ext: &str, data: &str) -> Result;
+}
+
+pub struct Viewers {
+    viewers: [Box; 3],
+}
+
+pub struct RenderRes {
+    /// Body html
+    pub html: String,
+    /// List of applicable viewers to be inserted into the top bar
+    pub tmpl_viewers: Vec,
+}
+
+impl Viewers {
+    pub fn new() -> Self {
+        let ss = Arc::new(SyntaxSet::load_defaults_newlines());
+        Self {
+            viewers: [
+                Box::new(junit::JunitViewer),
+                Box::new(markdown::MarkdownViewer::new(ss.clone())),
+                Box::new(code::CodeViewer::new(ss)),
+            ],
+        }
+    }
+
+    pub fn try_render(&self, filename: &str, viewer: &str, data: &str) -> Result {
+        let ext = util::filename_ext(filename);
+
+        if !viewer.is_empty() && viewer != "1" {
+            if let Some(viewer) = self.viewers.iter().find(|v| v.id() == viewer) {
+                if viewer.is_applicable(filename, ext) {
+                    return viewer
+                        .try_render(filename, ext, data)
+                        .map(|html| RenderRes {
+                            html,
+                            tmpl_viewers: self.tmpl_viewers(viewer.id(), filename, ext),
+                        });
+                } else {
+                    return Err(Error::ViewerNotApplicable);
+                }
+            }
+        }
+
+        for viewer in self
+            .viewers
+            .iter()
+            .filter(|v| v.is_applicable(filename, ext))
+        {
+            match viewer.try_render(filename, ext, data) {
+                Ok(html) => {
+                    return Ok(RenderRes {
+                        html,
+                        tmpl_viewers: self.tmpl_viewers(viewer.id(), filename, ext),
+                    })
+                }
+                Err(Error::ViewerNotApplicable) => {}
+                Err(e) => {
+                    tracing::error!("could not render {filename}: {e}");
+                }
+            }
+        }
+        Err(Error::ViewerNotApplicable)
+    }
+
+    fn tmpl_viewers(&self, viewer: &str, filename: &str, ext: &str) -> Vec {
+        self.viewers
+            .iter()
+            .filter(|v| v.is_applicable(filename, ext))
+            .map(|v| ViewerLink {
+                id: v.id(),
+                name: v.name(),
+                selected: v.id() == viewer,
+            })
+            .collect()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use rstest::rstest;
+
+    #[rstest]
+    #[case("test.txt", "", &["code"])]
+    #[case("hello.md", "", &["md", "code"])]
+    #[case("junit.xml", r#" "#, &["junit", "code"])]
+    #[case("img.png", "", &[])]
+    fn render(#[case] filename: &str, #[case] data: &str, #[case] applicable: &[&str]) {
+        let viewers = Viewers::new();
+        let res = viewers.try_render(filename, "1", data);
+
+        if applicable.is_empty() {
+            assert!(matches!(res, Err(Error::ViewerNotApplicable)));
+        } else {
+            let res = res.unwrap();
+            assert!(res.tmpl_viewers[0].selected);
+            let renderers = res.tmpl_viewers.iter().map(|v| v.id).collect::>();
+            assert_eq!(renderers, applicable)
+        }
+    }
+}
diff --git a/src/viewer/snapshots/artifactview__viewer__code__tests__render.snap b/src/viewer/snapshots/artifactview__viewer__code__tests__render.snap
new file mode 100644
index 0000000..3edec2c
--- /dev/null
+++ b/src/viewer/snapshots/artifactview__viewer__code__tests__render.snap
@@ -0,0 +1,10 @@
+---
+source: src/viewer/code.rs
+assertion_line: 89
+expression: res
+---
+
fn test() {
+    let x = "World";
+    println!("Hello {x}");
+}
+
diff --git a/src/viewer/snapshots/artifactview__viewer__junit__tests__render.snap b/src/viewer/snapshots/artifactview__viewer__junit__tests__render.snap new file mode 100644 index 0000000..1daafd5 --- /dev/null +++ b/src/viewer/snapshots/artifactview__viewer__junit__tests__render.snap @@ -0,0 +1,197 @@ +--- +source: src/viewer/junit.rs +assertion_line: 54 +expression: res +--- +
+
+

Test suites:

+
    +
  • + +
  • + +
+
+
+

Test cases:

+

+ + + + + + +

+
    +
  • + +
    +

    lib1::tests::it_works

    +

    Success3ms

    
    +running 1 test
    +test tests::it_works ... ok
    +
    +test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
    +
    +
    +
    +
  • + +
  • + +
    +

    lib1::tests::pippi_langstrumpf

    +

    Failure3ms

    thread 'tests::pippi_langstrumpf' panicked at src/lib.rs:18:9:
    +assertion `left == right` failed
    +  left: 7
    + right: 9
    +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    
    +running 1 test
    +test tests::pippi_langstrumpf ... FAILED
    +
    +failures:
    +
    +failures:
    +    tests::pippi_langstrumpf
    +
    +test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 2 filtered out; finished in 0.00s
    +
    +
    thread 'tests::pippi_langstrumpf' panicked at src/lib.rs:18:9:
    +assertion `left == right` failed
    +  left: 7
    + right: 9
    +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
    +
    +
    +
  • + +
+
+
+
+
+

Select a test case to show details

+
+
+
+ + diff --git a/src/viewer/snapshots/artifactview__viewer__markdown__tests__render.snap b/src/viewer/snapshots/artifactview__viewer__markdown__tests__render.snap new file mode 100644 index 0000000..db772bd --- /dev/null +++ b/src/viewer/snapshots/artifactview__viewer__markdown__tests__render.snap @@ -0,0 +1,8 @@ +--- +source: src/viewer/markdown.rs +assertion_line: 138 +expression: res +--- +

Hello World

+

this is a small paragraph for testing.

+
diff --git a/templates/error.hbs b/templates/error.hbs index 2e42d4c..17af881 100644 --- a/templates/error.hbs +++ b/templates/error.hbs @@ -1,21 +1,19 @@ + - Artifactview diff --git a/templates/index.hbs b/templates/index.hbs index 94580f7..ba12e5b 100644 --- a/templates/index.hbs +++ b/templates/index.hbs @@ -1,28 +1,6 @@ - - - - - - Artifactview - - +{{#> partial/header ~}} + Artifactview +{{~/partial/header }}

Enter a GitHub/Gitea/Forgejo Actions run url to browse CI artifacts

-
+ - +