diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index 8ef502636..75fe40a6c 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -56,6 +56,7 @@ blargh bools bpozdena brp +bsftc Btrfs btw buflen @@ -373,6 +374,7 @@ pastebin permessage pfa PFN +pgrep phlibi phobos pidx @@ -444,6 +446,7 @@ rsnapshot rsv rtud rul +ruleset runstatedir runsvdir Ruppe @@ -463,6 +466,7 @@ settime setxattr sev sfn +sfptc sharedstatedir sharepoint shortnames diff --git a/.github/workflows/e2e-business-shared-folders.yaml b/.github/workflows/e2e-business-shared-folders.yaml new file mode 100644 index 000000000..f0203fff9 --- /dev/null +++ b/.github/workflows/e2e-business-shared-folders.yaml @@ -0,0 +1,194 @@ +name: E2E Testing - Business Account Shared Folders + +on: + push: + branches-ignore: + - master + - main + paths: + - 'src/**' + - 'ci/**' + - '.github/workflows/e2e-*.yaml' + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + e2e_business_shared_folders: + runs-on: ubuntu-latest + container: fedora:latest + environment: + name: e2e-business-shared-folders + deployment: false + + steps: + - uses: actions/checkout@v4 + + - name: Install Dependencies + run: | + dnf -y update + dnf -y group install development-tools + dnf -y install python3 ldc libcurl-devel sqlite-devel dbus-devel jq + + - name: Build + local install prefix + run: | + ./configure --prefix="$PWD/.ci/prefix" + make -j"$(nproc)" + make install + "$PWD/.ci/prefix/bin/onedrive" --version + + - name: Prepare isolated HOME + run: | + set -euo pipefail + export HOME="$RUNNER_TEMP/home-business-shared-folders" + echo "HOME=$HOME" >> "$GITHUB_ENV" + echo "XDG_CONFIG_HOME=$HOME/.config" >> "$GITHUB_ENV" + echo "XDG_CACHE_HOME=$HOME/.cache" >> "$GITHUB_ENV" + mkdir -p "$HOME" + + - name: Inject refresh token into onedrive config + env: + BUSINESS_SHARED_REFRESH_TOKEN: ${{ secrets.BUSINESS_SHARED_REFRESH_TOKEN }} + run: | + set -euo pipefail + mkdir -p "$XDG_CONFIG_HOME/onedrive" + umask 077 + printf "%s" "$BUSINESS_SHARED_REFRESH_TOKEN" > "$XDG_CONFIG_HOME/onedrive/refresh_token" + chmod 600 "$XDG_CONFIG_HOME/onedrive/refresh_token" + + - name: Run Business Shared Folder E2E harness + env: + ONEDRIVE_BIN: ${{ github.workspace }}/.ci/prefix/bin/onedrive + E2E_TARGET: business-shared-folders + RUN_ID: ${{ github.run_id }} + PYTHONUNBUFFERED: "1" + run: | + python3 -u ci/e2e/run_business_shared_folders.py + + - name: Debug rerun failed Business Shared Folder E2E cases + if: always() + env: + ONEDRIVE_BIN: ${{ github.workspace }}/.ci/prefix/bin/onedrive + E2E_TARGET: business-shared-folders + RUN_ID: ${{ github.run_id }} + PYTHONUNBUFFERED: "1" + run: | + set -euo pipefail + if [ ! -f ci/e2e/out/results.json ]; then + echo "No primary results.json found; skipping debug rerun" + exit 0 + fi + + failed_cases="$(jq -r '[.cases[] | select(.status=="fail") | .id] | unique | join(",")' ci/e2e/out/results.json)" + if [ -z "$failed_cases" ]; then + echo "No failed Business Shared Folder cases; skipping debug rerun" + exit 0 + fi + + python3 -u ci/e2e/run_business_shared_folders.py \ + --debug \ + --output-subdir debug-rerun \ + --run-label debug-rerun \ + --case-id "$failed_cases" + + - name: Upload Business Shared Folder E2E artefacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: e2e-business-shared-folders + path: ci/e2e/out/** + + pr_comment: + name: Post PR summary comment + needs: [ e2e_business_shared_folders ] + runs-on: ubuntu-latest + if: always() + + steps: + - uses: actions/checkout@v4 + + - name: Download artefact + uses: actions/download-artifact@v4 + with: + name: e2e-business-shared-folders + path: artifacts/e2e-business-shared-folders + + - name: Build markdown summary + id: summary + run: | + set -euo pipefail + + primary_results="artifacts/e2e-business-shared-folders/results.json" + if [ -f "$primary_results" ]; then + f="$primary_results" + else + f="$(find artifacts/e2e-business-shared-folders \ + -path '*/debug-rerun/*' -prune -o \ + -name results.json -type f -print | head -n 1 || true)" + fi + if [ -z "$f" ] || [ ! -f "$f" ]; then + echo "md=⚠️ Business Shared Folder E2E ran but results.json was not found." >> "$GITHUB_OUTPUT" + exit 0 + fi + + total=$(jq -r '.cases | length' "$f") + passed=$(jq -r '[.cases[] | select(.status=="pass")] | length' "$f") + failed=$(jq -r '[.cases[] | select(.status=="fail")] | length' "$f") + + failures=$(jq -r '.cases[] + | select(.status=="fail") + | "- Test Case \(.id // "????"): \(.name) — \(.reason // "no reason provided")"' "$f" || true) + + md="## Business Account Shared Folder Testing\n" + md+="**${total}** Test Cases Run \n" + md+="**${passed}** Test Cases Passed \n" + md+="**${failed}** Test Cases Failed \n\n" + + if [ "$failed" -gt 0 ] && [ -n "$failures" ]; then + md+="### Business Shared Folder Test Failures\n" + md+="$failures\n" + fi + + echo "md<> "$GITHUB_OUTPUT" + echo -e "$md" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + - name: Find PR associated with this commit + id: pr + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo; + const sha = context.sha; + + const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner, repo, commit_sha: sha + }); + + if (!prs.data.length) { + core.setOutput("found", "false"); + return; + } + + core.setOutput("found", "true"); + core.setOutput("number", String(prs.data[0].number)); + + - name: Post PR comment + if: steps.pr.outputs.found == 'true' + uses: actions/github-script@v7 + env: + COMMENT_MD: ${{ steps.summary.outputs.md }} + with: + script: | + const { owner, repo } = context.repo; + const issue_number = Number("${{ steps.pr.outputs.number }}"); + const md = process.env.COMMENT_MD || "⚠️ No summary text produced."; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body: md + }); diff --git a/.github/workflows/e2e-business.yaml b/.github/workflows/e2e-business.yaml new file mode 100644 index 000000000..a72c14101 --- /dev/null +++ b/.github/workflows/e2e-business.yaml @@ -0,0 +1,189 @@ +name: E2E Testing - Business Account + +on: + push: + branches-ignore: + - master + - main + paths: + - 'src/**' + - 'ci/**' + - '.github/workflows/e2e-*.yaml' + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + e2e_business: + runs-on: ubuntu-latest + container: fedora:latest + environment: + name: e2e-business + deployment: false + + steps: + - uses: actions/checkout@v4 + + - name: Install Dependencies + run: | + dnf -y update + dnf -y group install development-tools + dnf -y install python3 ldc libcurl-devel sqlite-devel dbus-devel jq + + - name: Build + local install prefix + run: | + ./configure --prefix="$PWD/.ci/prefix" + make -j"$(nproc)" + make install + "$PWD/.ci/prefix/bin/onedrive" --version + + - name: Prepare isolated HOME + run: | + set -euo pipefail + export HOME="$RUNNER_TEMP/home-business" + echo "HOME=$HOME" >> "$GITHUB_ENV" + echo "XDG_CONFIG_HOME=$HOME/.config" >> "$GITHUB_ENV" + echo "XDG_CACHE_HOME=$HOME/.cache" >> "$GITHUB_ENV" + mkdir -p "$HOME" + + - name: Inject refresh token into onedrive config + env: + REFRESH_TOKEN_BUSINESS: ${{ secrets.REFRESH_TOKEN_BUSINESS }} + run: | + set -euo pipefail + mkdir -p "$XDG_CONFIG_HOME/onedrive" + umask 077 + printf "%s" "$REFRESH_TOKEN_BUSINESS" > "$XDG_CONFIG_HOME/onedrive/refresh_token" + chmod 600 "$XDG_CONFIG_HOME/onedrive/refresh_token" + + - name: Run E2E harness + env: + ONEDRIVE_BIN: ${{ github.workspace }}/.ci/prefix/bin/onedrive + E2E_TARGET: business + RUN_ID: ${{ github.run_id }} + PYTHONUNBUFFERED: "1" + run: | + python3 -u ci/e2e/run.py + + - name: Debug rerun failed E2E cases + if: always() + env: + ONEDRIVE_BIN: ${{ github.workspace }}/.ci/prefix/bin/onedrive + E2E_TARGET: business + RUN_ID: ${{ github.run_id }} + PYTHONUNBUFFERED: "1" + run: | + set -euo pipefail + if [ -f ci/e2e/out/results.json ]; then + python3 -u ci/e2e/rerun_failures.py \ + --results ci/e2e/out/results.json \ + --output-subdir debug-rerun \ + --run-label debug-rerun \ + --skip-suite-cleanup + else + echo "No primary results.json found; skipping debug rerun" + fi + + - name: Upload E2E artefacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: e2e-business + path: ci/e2e/out/** + + pr_comment: + name: Post PR summary comment + needs: [ e2e_business ] + runs-on: ubuntu-latest + if: always() + + steps: + - uses: actions/checkout@v4 + + - name: Download artefact + uses: actions/download-artifact@v4 + with: + name: e2e-business + path: artifacts/e2e-business + + - name: Build markdown summary + id: summary + run: | + set -euo pipefail + + primary_results="artifacts/e2e-business/results.json" + if [ -f "$primary_results" ]; then + f="$primary_results" + else + f="$(find artifacts/e2e-business \ + -path '*/debug-rerun/*' -prune -o \ + -name results.json -type f -print | head -n 1 || true)" + fi + if [ -z "$f" ] || [ ! -f "$f" ]; then + echo "md=⚠️ E2E ran but results.json was not found." >> "$GITHUB_OUTPUT" + exit 0 + fi + + target=$(jq -r '.target // "business"' "$f") + total=$(jq -r '.cases | length' "$f") + passed=$(jq -r '[.cases[] | select(.status=="pass")] | length' "$f") + failed=$(jq -r '[.cases[] | select(.status=="fail")] | length' "$f") + + failures=$(jq -r '.cases[] + | select(.status=="fail") + | "- Test Case \(.id // "????"): \(.name) — \(.reason // "no reason provided")"' "$f" || true) + + md="## ${target^} Account Testing\n" + md+="**${total}** Test Cases Run \n" + md+="**${passed}** Test Cases Passed \n" + md+="**${failed}** Test Cases Failed \n\n" + + if [ "$failed" -gt 0 ] && [ -n "$failures" ]; then + md+="### ${target^} Account Test Failures\n" + md+="$failures\n" + fi + + echo "md<> "$GITHUB_OUTPUT" + echo -e "$md" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + - name: Find PR associated with this commit + id: pr + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo; + const sha = context.sha; + + const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner, repo, commit_sha: sha + }); + + if (!prs.data.length) { + core.setOutput("found", "false"); + return; + } + + core.setOutput("found", "true"); + core.setOutput("number", String(prs.data[0].number)); + + - name: Post PR comment + if: steps.pr.outputs.found == 'true' + uses: actions/github-script@v7 + env: + COMMENT_MD: ${{ steps.summary.outputs.md }} + with: + script: | + const { owner, repo } = context.repo; + const issue_number = Number("${{ steps.pr.outputs.number }}"); + + const md = process.env.COMMENT_MD || "⚠️ No summary text produced."; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body: md + }); diff --git a/.github/workflows/e2e-personal-15char-check.yaml b/.github/workflows/e2e-personal-15char-check.yaml new file mode 100644 index 000000000..3a0cc3cc4 --- /dev/null +++ b/.github/workflows/e2e-personal-15char-check.yaml @@ -0,0 +1,189 @@ +name: E2E Testing - Personal Account with 15 Character driveId Check + +on: + push: + branches-ignore: + - master + - main + paths: + - 'src/**' + - 'ci/**' + - '.github/workflows/e2e-*.yaml' + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + e2e_personal: + runs-on: ubuntu-latest + container: fedora:latest + environment: + name: e2e-personal-15char-check + deployment: false + + steps: + - uses: actions/checkout@v4 + + - name: Install Dependencies + run: | + dnf -y update + dnf -y group install development-tools + dnf -y install python3 ldc libcurl-devel sqlite-devel dbus-devel jq + + - name: Build + local install prefix + run: | + ./configure --prefix="$PWD/.ci/prefix" + make -j"$(nproc)" + make install + "$PWD/.ci/prefix/bin/onedrive" --version + + - name: Prepare isolated HOME + run: | + set -euo pipefail + export HOME="$RUNNER_TEMP/home-personal" + echo "HOME=$HOME" >> "$GITHUB_ENV" + echo "XDG_CONFIG_HOME=$HOME/.config" >> "$GITHUB_ENV" + echo "XDG_CACHE_HOME=$HOME/.cache" >> "$GITHUB_ENV" + mkdir -p "$HOME" + + - name: Inject refresh token into onedrive config + env: + REFRESH_TOKEN_PERSONAL: ${{ secrets.REFRESH_TOKEN_PERSONAL }} + run: | + set -euo pipefail + mkdir -p "$XDG_CONFIG_HOME/onedrive" + umask 077 + printf "%s" "$REFRESH_TOKEN_PERSONAL" > "$XDG_CONFIG_HOME/onedrive/refresh_token" + chmod 600 "$XDG_CONFIG_HOME/onedrive/refresh_token" + + - name: Run E2E harness + env: + ONEDRIVE_BIN: ${{ github.workspace }}/.ci/prefix/bin/onedrive + E2E_TARGET: personal + RUN_ID: ${{ github.run_id }} + PYTHONUNBUFFERED: "1" + run: | + python3 -u ci/e2e/run.py + + - name: Debug rerun failed E2E cases + if: always() + env: + ONEDRIVE_BIN: ${{ github.workspace }}/.ci/prefix/bin/onedrive + E2E_TARGET: personal + RUN_ID: ${{ github.run_id }} + PYTHONUNBUFFERED: "1" + run: | + set -euo pipefail + if [ -f ci/e2e/out/results.json ]; then + python3 -u ci/e2e/rerun_failures.py \ + --results ci/e2e/out/results.json \ + --output-subdir debug-rerun \ + --run-label debug-rerun \ + --skip-suite-cleanup + else + echo "No primary results.json found; skipping debug rerun" + fi + + - name: Upload E2E artefacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: e2e-personal-15char-check + path: ci/e2e/out/** + + pr_comment: + name: Post PR summary comment + needs: [ e2e_personal ] + runs-on: ubuntu-latest + if: always() + + steps: + - uses: actions/checkout@v4 + + - name: Download artefact + uses: actions/download-artifact@v4 + with: + name: e2e-personal-15char-check + path: artifacts/e2e-personal-15char-check + + - name: Build markdown summary + id: summary + run: | + set -euo pipefail + + primary_results="artifacts/e2e-personal-15char-check/results.json" + if [ -f "$primary_results" ]; then + f="$primary_results" + else + f="$(find artifacts/e2e-personal-15char-check \ + -path '*/debug-rerun/*' -prune -o \ + -name results.json -type f -print | head -n 1 || true)" + fi + if [ -z "$f" ] || [ ! -f "$f" ]; then + echo "md=⚠️ E2E ran but results.json was not found." >> "$GITHUB_OUTPUT" + exit 0 + fi + + target=$(jq -r '.target // "personal"' "$f") + total=$(jq -r '.cases | length' "$f") + passed=$(jq -r '[.cases[] | select(.status=="pass")] | length' "$f") + failed=$(jq -r '[.cases[] | select(.status=="fail")] | length' "$f") + + failures=$(jq -r '.cases[] + | select(.status=="fail") + | "- Test Case \(.id // "????"): \(.name) — \(.reason // "no reason provided")"' "$f" || true) + + md="## ${target^} Account Testing - 15 Character 'driveId'\n" + md+="**${total}** Test Cases Run \n" + md+="**${passed}** Test Cases Passed \n" + md+="**${failed}** Test Cases Failed \n\n" + + if [ "$failed" -gt 0 ] && [ -n "$failures" ]; then + md+="### ${target^} Account Test Failures\n" + md+="$failures\n" + fi + + echo "md<> "$GITHUB_OUTPUT" + echo -e "$md" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + - name: Find PR associated with this commit + id: pr + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo; + const sha = context.sha; + + const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner, repo, commit_sha: sha + }); + + if (!prs.data.length) { + core.setOutput("found", "false"); + return; + } + + core.setOutput("found", "true"); + core.setOutput("number", String(prs.data[0].number)); + + - name: Post PR comment + if: steps.pr.outputs.found == 'true' + uses: actions/github-script@v7 + env: + COMMENT_MD: ${{ steps.summary.outputs.md }} + with: + script: | + const { owner, repo } = context.repo; + const issue_number = Number("${{ steps.pr.outputs.number }}"); + + const md = process.env.COMMENT_MD || "⚠️ No summary text produced."; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body: md + }); diff --git a/.github/workflows/e2e-personal-shared-folders.yaml b/.github/workflows/e2e-personal-shared-folders.yaml new file mode 100644 index 000000000..abd614de7 --- /dev/null +++ b/.github/workflows/e2e-personal-shared-folders.yaml @@ -0,0 +1,194 @@ +name: E2E Testing - Personal Account Shared Folders + +on: + push: + branches-ignore: + - master + - main + paths: + - 'src/**' + - 'ci/**' + - '.github/workflows/e2e-*.yaml' + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + e2e_personal_shared_folders: + runs-on: ubuntu-latest + container: fedora:latest + environment: + name: e2e-personal-shared-folders + deployment: false + + steps: + - uses: actions/checkout@v4 + + - name: Install Dependencies + run: | + dnf -y update + dnf -y group install development-tools + dnf -y install python3 ldc libcurl-devel sqlite-devel dbus-devel jq + + - name: Build + local install prefix + run: | + ./configure --prefix="$PWD/.ci/prefix" + make -j"$(nproc)" + make install + "$PWD/.ci/prefix/bin/onedrive" --version + + - name: Prepare isolated HOME + run: | + set -euo pipefail + export HOME="$RUNNER_TEMP/home-personal-shared-folders" + echo "HOME=$HOME" >> "$GITHUB_ENV" + echo "XDG_CONFIG_HOME=$HOME/.config" >> "$GITHUB_ENV" + echo "XDG_CACHE_HOME=$HOME/.cache" >> "$GITHUB_ENV" + mkdir -p "$HOME" + + - name: Inject refresh token into onedrive config + env: + REFRESH_TOKEN_PERSONAL_SHARED_FOLDERS: ${{ secrets.REFRESH_TOKEN_PERSONAL_SHARED_FOLDERS }} + run: | + set -euo pipefail + mkdir -p "$XDG_CONFIG_HOME/onedrive" + umask 077 + printf "%s" "$REFRESH_TOKEN_PERSONAL_SHARED_FOLDERS" > "$XDG_CONFIG_HOME/onedrive/refresh_token" + chmod 600 "$XDG_CONFIG_HOME/onedrive/refresh_token" + + - name: Run Personal Shared Folder E2E harness + env: + ONEDRIVE_BIN: ${{ github.workspace }}/.ci/prefix/bin/onedrive + E2E_TARGET: personal-shared-folders + RUN_ID: ${{ github.run_id }} + PYTHONUNBUFFERED: "1" + run: | + python3 -u ci/e2e/run_personal_shared_folders.py + + - name: Debug rerun failed Personal Shared Folder E2E cases + if: always() + env: + ONEDRIVE_BIN: ${{ github.workspace }}/.ci/prefix/bin/onedrive + E2E_TARGET: personal-shared-folders + RUN_ID: ${{ github.run_id }} + PYTHONUNBUFFERED: "1" + run: | + set -euo pipefail + if [ ! -f ci/e2e/out/results.json ]; then + echo "No primary results.json found; skipping debug rerun" + exit 0 + fi + + failed_cases="$(jq -r '[.cases[] | select(.status=="fail") | .id] | unique | join(",")' ci/e2e/out/results.json)" + if [ -z "$failed_cases" ]; then + echo "No failed Personal Shared Folder cases; skipping debug rerun" + exit 0 + fi + + python3 -u ci/e2e/run_personal_shared_folders.py \ + --debug \ + --output-subdir debug-rerun \ + --run-label debug-rerun \ + --case-id "$failed_cases" + + - name: Upload Personal Shared Folder E2E artefacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: e2e-personal-shared-folders + path: ci/e2e/out/** + + pr_comment: + name: Post PR summary comment + needs: [ e2e_personal_shared_folders ] + runs-on: ubuntu-latest + if: always() + + steps: + - uses: actions/checkout@v4 + + - name: Download artefact + uses: actions/download-artifact@v4 + with: + name: e2e-personal-shared-folders + path: artifacts/e2e-personal-shared-folders + + - name: Build markdown summary + id: summary + run: | + set -euo pipefail + + primary_results="artifacts/e2e-personal-shared-folders/results.json" + if [ -f "$primary_results" ]; then + f="$primary_results" + else + f="$(find artifacts/e2e-personal-shared-folders \ + -path '*/debug-rerun/*' -prune -o \ + -name results.json -type f -print | head -n 1 || true)" + fi + if [ -z "$f" ] || [ ! -f "$f" ]; then + echo "md=⚠️ Personal Shared Folder E2E ran but results.json was not found." >> "$GITHUB_OUTPUT" + exit 0 + fi + + total=$(jq -r '.cases | length' "$f") + passed=$(jq -r '[.cases[] | select(.status=="pass")] | length' "$f") + failed=$(jq -r '[.cases[] | select(.status=="fail")] | length' "$f") + + failures=$(jq -r '.cases[] + | select(.status=="fail") + | "- Test Case \(.id // "????"): \(.name) — \(.reason // "no reason provided")"' "$f" || true) + + md="## Personal Account Shared Folder Testing\n" + md+="**${total}** Test Cases Run \n" + md+="**${passed}** Test Cases Passed \n" + md+="**${failed}** Test Cases Failed \n\n" + + if [ "$failed" -gt 0 ] && [ -n "$failures" ]; then + md+="### Personal Shared Folder Test Failures\n" + md+="$failures\n" + fi + + echo "md<> "$GITHUB_OUTPUT" + echo -e "$md" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + - name: Find PR associated with this commit + id: pr + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo; + const sha = context.sha; + + const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner, repo, commit_sha: sha + }); + + if (!prs.data.length) { + core.setOutput("found", "false"); + return; + } + + core.setOutput("found", "true"); + core.setOutput("number", String(prs.data[0].number)); + + - name: Post PR comment + if: steps.pr.outputs.found == 'true' + uses: actions/github-script@v7 + env: + COMMENT_MD: ${{ steps.summary.outputs.md }} + with: + script: | + const { owner, repo } = context.repo; + const issue_number = Number("${{ steps.pr.outputs.number }}"); + const md = process.env.COMMENT_MD || "⚠️ No summary text produced."; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body: md + }); diff --git a/.github/workflows/e2e-personal.yaml b/.github/workflows/e2e-personal.yaml new file mode 100644 index 000000000..3a7e66dd2 --- /dev/null +++ b/.github/workflows/e2e-personal.yaml @@ -0,0 +1,189 @@ +name: E2E Testing - Personal Account + +on: + push: + branches-ignore: + - master + - main + paths: + - 'src/**' + - 'ci/**' + - '.github/workflows/e2e-*.yaml' + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + e2e_personal: + runs-on: ubuntu-latest + container: fedora:latest + environment: + name: e2e-personal + deployment: false + + steps: + - uses: actions/checkout@v4 + + - name: Install Dependencies + run: | + dnf -y update + dnf -y group install development-tools + dnf -y install python3 ldc libcurl-devel sqlite-devel dbus-devel jq + + - name: Build + local install prefix + run: | + ./configure --prefix="$PWD/.ci/prefix" + make -j"$(nproc)" + make install + "$PWD/.ci/prefix/bin/onedrive" --version + + - name: Prepare isolated HOME + run: | + set -euo pipefail + export HOME="$RUNNER_TEMP/home-personal" + echo "HOME=$HOME" >> "$GITHUB_ENV" + echo "XDG_CONFIG_HOME=$HOME/.config" >> "$GITHUB_ENV" + echo "XDG_CACHE_HOME=$HOME/.cache" >> "$GITHUB_ENV" + mkdir -p "$HOME" + + - name: Inject refresh token into onedrive config + env: + REFRESH_TOKEN_PERSONAL: ${{ secrets.REFRESH_TOKEN_PERSONAL }} + run: | + set -euo pipefail + mkdir -p "$XDG_CONFIG_HOME/onedrive" + umask 077 + printf "%s" "$REFRESH_TOKEN_PERSONAL" > "$XDG_CONFIG_HOME/onedrive/refresh_token" + chmod 600 "$XDG_CONFIG_HOME/onedrive/refresh_token" + + - name: Run E2E harness + env: + ONEDRIVE_BIN: ${{ github.workspace }}/.ci/prefix/bin/onedrive + E2E_TARGET: personal + RUN_ID: ${{ github.run_id }} + PYTHONUNBUFFERED: "1" + run: | + python3 -u ci/e2e/run.py + + - name: Debug rerun failed E2E cases + if: always() + env: + ONEDRIVE_BIN: ${{ github.workspace }}/.ci/prefix/bin/onedrive + E2E_TARGET: personal + RUN_ID: ${{ github.run_id }} + PYTHONUNBUFFERED: "1" + run: | + set -euo pipefail + if [ -f ci/e2e/out/results.json ]; then + python3 -u ci/e2e/rerun_failures.py \ + --results ci/e2e/out/results.json \ + --output-subdir debug-rerun \ + --run-label debug-rerun \ + --skip-suite-cleanup + else + echo "No primary results.json found; skipping debug rerun" + fi + + - name: Upload E2E artefacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: e2e-personal + path: ci/e2e/out/** + + pr_comment: + name: Post PR summary comment + needs: [ e2e_personal ] + runs-on: ubuntu-latest + if: always() + + steps: + - uses: actions/checkout@v4 + + - name: Download artefact + uses: actions/download-artifact@v4 + with: + name: e2e-personal + path: artifacts/e2e-personal + + - name: Build markdown summary + id: summary + run: | + set -euo pipefail + + primary_results="artifacts/e2e-personal/results.json" + if [ -f "$primary_results" ]; then + f="$primary_results" + else + f="$(find artifacts/e2e-personal \ + -path '*/debug-rerun/*' -prune -o \ + -name results.json -type f -print | head -n 1 || true)" + fi + if [ -z "$f" ] || [ ! -f "$f" ]; then + echo "md=⚠️ E2E ran but results.json was not found." >> "$GITHUB_OUTPUT" + exit 0 + fi + + target=$(jq -r '.target // "personal"' "$f") + total=$(jq -r '.cases | length' "$f") + passed=$(jq -r '[.cases[] | select(.status=="pass")] | length' "$f") + failed=$(jq -r '[.cases[] | select(.status=="fail")] | length' "$f") + + failures=$(jq -r '.cases[] + | select(.status=="fail") + | "- Test Case \(.id // "????"): \(.name) — \(.reason // "no reason provided")"' "$f" || true) + + md="## ${target^} Account Testing\n" + md+="**${total}** Test Cases Run \n" + md+="**${passed}** Test Cases Passed \n" + md+="**${failed}** Test Cases Failed \n\n" + + if [ "$failed" -gt 0 ] && [ -n "$failures" ]; then + md+="### ${target^} Account Test Failures\n" + md+="$failures\n" + fi + + echo "md<> "$GITHUB_OUTPUT" + echo -e "$md" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + - name: Find PR associated with this commit + id: pr + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo; + const sha = context.sha; + + const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner, repo, commit_sha: sha + }); + + if (!prs.data.length) { + core.setOutput("found", "false"); + return; + } + + core.setOutput("found", "true"); + core.setOutput("number", String(prs.data[0].number)); + + - name: Post PR comment + if: steps.pr.outputs.found == 'true' + uses: actions/github-script@v7 + env: + COMMENT_MD: ${{ steps.summary.outputs.md }} + with: + script: | + const { owner, repo } = context.repo; + const issue_number = Number("${{ steps.pr.outputs.number }}"); + + const md = process.env.COMMENT_MD || "⚠️ No summary text produced."; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body: md + }); diff --git a/.github/workflows/e2e-sharepoint.yaml b/.github/workflows/e2e-sharepoint.yaml new file mode 100644 index 000000000..0dc3ac022 --- /dev/null +++ b/.github/workflows/e2e-sharepoint.yaml @@ -0,0 +1,192 @@ +name: E2E Testing - SharePoint documentLibrary Configuration + +on: + push: + branches-ignore: + - master + - main + paths: + - 'src/**' + - 'ci/**' + - '.github/workflows/e2e-*.yaml' + +permissions: + contents: read + pull-requests: write + issues: write + +jobs: + e2e_sharepoint: + runs-on: ubuntu-latest + container: fedora:latest + environment: + name: e2e-sharepoint + deployment: false + + steps: + - uses: actions/checkout@v4 + + - name: Install Dependencies + run: | + dnf -y update + dnf -y group install development-tools + dnf -y install python3 ldc libcurl-devel sqlite-devel dbus-devel jq + + - name: Build + local install prefix + run: | + ./configure --prefix="$PWD/.ci/prefix" + make -j"$(nproc)" + make install + "$PWD/.ci/prefix/bin/onedrive" --version + + - name: Prepare isolated HOME + run: | + set -euo pipefail + export HOME="$RUNNER_TEMP/home-sharepoint" + echo "HOME=$HOME" >> "$GITHUB_ENV" + echo "XDG_CONFIG_HOME=$HOME/.config" >> "$GITHUB_ENV" + echo "XDG_CACHE_HOME=$HOME/.cache" >> "$GITHUB_ENV" + mkdir -p "$HOME" + + - name: Inject refresh token and SharePoint drive_id into onedrive config + env: + REFRESH_TOKEN_BUSINESS: ${{ secrets.REFRESH_TOKEN_BUSINESS }} + SHAREPOINT_DRIVEID: ${{ secrets.SHAREPOINT_DRIVEID }} + run: | + set -euo pipefail + mkdir -p "$XDG_CONFIG_HOME/onedrive" + umask 077 + printf "%s" "$REFRESH_TOKEN_BUSINESS" > "$XDG_CONFIG_HOME/onedrive/refresh_token" + chmod 600 "$XDG_CONFIG_HOME/onedrive/refresh_token" + printf 'drive_id = "%s"\n' "$SHAREPOINT_DRIVEID" > "$XDG_CONFIG_HOME/onedrive/config.sharepoint" + chmod 600 "$XDG_CONFIG_HOME/onedrive/config.sharepoint" + + - name: Run E2E harness + env: + ONEDRIVE_BIN: ${{ github.workspace }}/.ci/prefix/bin/onedrive + E2E_TARGET: sharepoint + RUN_ID: ${{ github.run_id }} + PYTHONUNBUFFERED: "1" + run: | + python3 -u ci/e2e/run.py + + - name: Debug rerun failed E2E cases + if: always() + env: + ONEDRIVE_BIN: ${{ github.workspace }}/.ci/prefix/bin/onedrive + E2E_TARGET: sharepoint + RUN_ID: ${{ github.run_id }} + PYTHONUNBUFFERED: "1" + run: | + set -euo pipefail + if [ -f ci/e2e/out/results.json ]; then + python3 -u ci/e2e/rerun_failures.py \ + --results ci/e2e/out/results.json \ + --output-subdir debug-rerun \ + --run-label debug-rerun \ + --skip-suite-cleanup + else + echo "No primary results.json found; skipping debug rerun" + fi + + - name: Upload E2E artefacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: e2e-sharepoint + path: ci/e2e/out/** + + pr_comment: + name: Post PR summary comment + needs: [ e2e_sharepoint ] + runs-on: ubuntu-latest + if: always() + + steps: + - uses: actions/checkout@v4 + + - name: Download artefact + uses: actions/download-artifact@v4 + with: + name: e2e-sharepoint + path: artifacts/e2e-sharepoint + + - name: Build markdown summary + id: summary + run: | + set -euo pipefail + + primary_results="artifacts/e2e-sharepoint/results.json" + if [ -f "$primary_results" ]; then + f="$primary_results" + else + f="$(find artifacts/e2e-sharepoint \ + -path '*/debug-rerun/*' -prune -o \ + -name results.json -type f -print | head -n 1 || true)" + fi + if [ -z "$f" ] || [ ! -f "$f" ]; then + echo "md=⚠️ E2E ran but results.json was not found." >> "$GITHUB_OUTPUT" + exit 0 + fi + + target=$(jq -r '.target // "sharepoint"' "$f") + total=$(jq -r '.cases | length' "$f") + passed=$(jq -r '[.cases[] | select(.status=="pass")] | length' "$f") + failed=$(jq -r '[.cases[] | select(.status=="fail")] | length' "$f") + + failures=$(jq -r '.cases[] + | select(.status=="fail") + | "- Test Case \(.id // "????"): \(.name) — \(.reason // "no reason provided")"' "$f" || true) + + md="## ${target^} Account Testing\n" + md+="**${total}** Test Cases Run \n" + md+="**${passed}** Test Cases Passed \n" + md+="**${failed}** Test Cases Failed \n\n" + + if [ "$failed" -gt 0 ] && [ -n "$failures" ]; then + md+="### ${target^} Account Test Failures\n" + md+="$failures\n" + fi + + echo "md<> "$GITHUB_OUTPUT" + echo -e "$md" >> "$GITHUB_OUTPUT" + echo "EOF" >> "$GITHUB_OUTPUT" + + - name: Find PR associated with this commit + id: pr + uses: actions/github-script@v7 + with: + script: | + const { owner, repo } = context.repo; + const sha = context.sha; + + const prs = await github.rest.repos.listPullRequestsAssociatedWithCommit({ + owner, repo, commit_sha: sha + }); + + if (!prs.data.length) { + core.setOutput("found", "false"); + return; + } + + core.setOutput("found", "true"); + core.setOutput("number", String(prs.data[0].number)); + + - name: Post PR comment + if: steps.pr.outputs.found == 'true' + uses: actions/github-script@v7 + env: + COMMENT_MD: ${{ steps.summary.outputs.md }} + with: + script: | + const { owner, repo } = context.repo; + const issue_number = Number("${{ steps.pr.outputs.number }}"); + + const md = process.env.COMMENT_MD || "⚠️ No summary text produced."; + + await github.rest.issues.createComment({ + owner, + repo, + issue_number, + body: md + }); diff --git a/.github/workflows/smoke-test.yaml b/.github/workflows/smoke-test.yaml index 61e1263ed..f087df872 100644 --- a/.github/workflows/smoke-test.yaml +++ b/.github/workflows/smoke-test.yaml @@ -5,7 +5,15 @@ on: branches: - master - feature/** + paths: + - 'src/**' + - '.github/workflows/smoke-test.yaml' + - '.github/gdb/**' pull_request: + paths: + - 'src/**' + - '.github/workflows/smoke-test.yaml' + - '.github/gdb/**' workflow_dispatch: permissions: @@ -538,4 +546,4 @@ jobs: with: name: smoke-monitor-fedora-${{ matrix.fedora }}-logs path: /tmp/onedrive-smoke/ - if-no-files-found: warn \ No newline at end of file + if-no-files-found: warn diff --git a/.github/workflows/testbuild.yaml b/.github/workflows/testbuild.yaml index 91c51691c..b64ca7d01 100644 --- a/.github/workflows/testbuild.yaml +++ b/.github/workflows/testbuild.yaml @@ -3,9 +3,14 @@ name: Test Build on: push: branches: [ "master" ] + paths: + - 'src/**' + - '.github/workflows/testbuild.yaml' pull_request: branches: [ "master" ] - + paths: + - 'src/**' + - '.github/workflows/testbuild.yaml' jobs: build: runs-on: ubuntu-latest @@ -38,4 +43,4 @@ jobs: run: sudo make install - name: Run - run: onedrive --version \ No newline at end of file + run: onedrive --version diff --git a/Makefile.in b/Makefile.in index 6e39b0a16..653687588 100644 --- a/Makefile.in +++ b/Makefile.in @@ -58,7 +58,7 @@ endif system_unit_files = contrib/systemd/onedrive@.service user_unit_files = contrib/systemd/onedrive.service -DOCFILES = readme.md config LICENSE changelog.md docs/advanced-usage.md docs/application-config-options.md docs/application-security.md docs/business-shared-items.md docs/client-architecture.md docs/contributing.md docs/docker.md docs/install.md docs/national-cloud-deployments.md docs/podman.md docs/privacy-policy.md docs/sharepoint-libraries.md docs/terms-of-service.md docs/ubuntu-package-install.md docs/usage.md docs/known-issues.md docs/webhooks.md docs/server-side-filtering-limitations.md +DOCFILES = readme.md config LICENSE changelog.md docs/advanced-usage.md docs/application-config-options.md docs/application-security.md docs/business-shared-items.md docs/client-architecture.md docs/contributing.md docs/docker.md docs/install.md docs/national-cloud-deployments.md docs/podman.md docs/privacy-policy.md docs/sharepoint-libraries.md docs/terms-of-service.md docs/ubuntu-package-install.md docs/usage.md docs/known-issues.md docs/webhooks.md docs/server-side-filtering-limitations.md docs/end_to_end_testing.md ifneq ("$(wildcard /etc/redhat-release)","") RHEL = $(shell cat /etc/redhat-release | grep -E "(Red Hat Enterprise Linux|CentOS|AlmaLinux)" | wc -l) diff --git a/ci/e2e/README-rerun.md b/ci/e2e/README-rerun.md new file mode 100644 index 000000000..1331b0c5f --- /dev/null +++ b/ci/e2e/README-rerun.md @@ -0,0 +1,55 @@ +# E2E automatic failure rerun support + +This folder now supports a two-pass CI model: + +1. Run the primary E2E suite normally using `ci/e2e/run.py` +2. Parse `results.json` and automatically rerun only failed cases/scenarios with debug enabled using `ci/e2e/rerun_failures.py` + +## Primary run + +```bash +python3 -u ci/e2e/run.py +``` + +## Automatic debug rerun + +```bash +python3 -u ci/e2e/rerun_failures.py --results ci/e2e/out/results.json --output-subdir debug-rerun --run-label debug-rerun +``` + +This will: +- read the failed cases from the primary `results.json` +- narrow the rerun to only failed case IDs +- narrow scenario-based cases to only failed scenario IDs when available +- rerun with debug verbosity enabled +- write rerun outputs into `ci/e2e/out/debug-rerun/` + +## Direct filtered reruns + +### Rerun one case + +```bash +python3 -u ci/e2e/run.py --case-id 0024 --debug --output-subdir tc0024-debug +``` + +### Rerun multiple cases + +```bash +python3 -u ci/e2e/run.py --case-id 0002,0021,0024 --debug --output-subdir targeted-debug +``` + +### Rerun only selected scenarios + +```bash +python3 -u ci/e2e/run.py --case-id 0002,0021 --scenario 0002:SL-0004,SL-0018 --scenario 0021:RT-0001 --debug --output-subdir targeted-scenarios-debug +``` + +## Environment-driven controls + +These are also supported if you prefer using workflow env vars: +- `E2E_DEBUG=1` +- `E2E_OUTPUT_SUBDIR=` +- `E2E_RUN_LABEL=