diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fa9dd64c..80236cf0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI +name: CI Pipeline on: pull_request: @@ -7,324 +7,1330 @@ on: - 'v*' branches: - 'master' + schedule: + # Run security checks daily + - cron: '0 2 * * *' + +env: + GOLANGCI_LINT_VERSION: v1.64.5 defaults: run: - # see: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell shell: bash --noprofile --norc -eo pipefail {0} -env: - DEBIAN_FRONTEND: noninteractive - jobs: - test: - name: "Test (Ubuntu 22.04)" - runs-on: ubuntu-22.04 - continue-on-error: false - timeout-minutes: 30 - - env: - NO_FTP_ACCESS: yes - BOTO_CONFIG: /dev/null - GO111MODULE: "on" - GOPROXY: "https://proxy.golang.org" - + # Quick checks that should fail fast + quick-checks: + name: Quick Checks + runs-on: ubuntu-latest steps: - - name: "Install Test Packages" + - uses: actions/checkout@v4 + + - name: Read Go Version run: | - sudo apt-get update - sudo apt-get install -y --no-install-recommends graphviz gnupg2 gpgv2 git gcc make devscripts python3 python3-requests-unixsocket python3-termcolor python3-swiftclient python3-boto python3-azure-storage python3-etcd3 python3-plyvel flake8 faketime - - - name: "Checkout Repository" - uses: actions/checkout@v4 + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 with: - # fetch the whole repo for `git describe` to work - fetch-depth: 0 - - - name: "Run flake8" + go-version: ${{ steps.goversion.outputs.GOVER }} + cache: true + + - name: Check formatting run: | + if [ -n "$(gofmt -l .)" ]; then + echo "Go code is not formatted:" + gofmt -d . + exit 1 + fi + + - name: Run go vet + run: go vet ./... + + - name: Check go mod tidy + run: | + go mod tidy + git diff --exit-code go.mod go.sum + + - name: Run flake8 + run: | + sudo apt-get update && sudo apt-get install -y flake8 make flake8 - - name: "Read Go Version" - run: | - gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) - echo "Go Version: $gover" - echo "GOVER=$gover" >> $GITHUB_OUTPUT - id: goversion - - - name: "Setup Go" - uses: actions/setup-go@v3 - with: - go-version: ${{ steps.goversion.outputs.GOVER }} - - - name: "Install Azurite" - id: azuright - uses: potatoqualitee/azuright@v1.1 - with: - directory: ${{ runner.temp }} - - - name: "Run Unit Tests" - env: - RUN_LONG_TESTS: 'yes' - AZURE_STORAGE_ENDPOINT: "http://127.0.0.1:10000/devstoreaccount1" - AZURE_STORAGE_ACCOUNT: "devstoreaccount1" - AZURE_STORAGE_ACCESS_KEY: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - run: | - sudo mkdir -p /srv ; sudo chown runner /srv - COVERAGE_DIR=${{ runner.temp }} make test - - - name: "Run Benchmark" - run: | - COVERAGE_DIR=${{ runner.temp }} make bench - - - name: "Run System Tests" - env: - RUN_LONG_TESTS: 'yes' - AZURE_STORAGE_ENDPOINT: "http://127.0.0.1:10000/devstoreaccount1" - AZURE_STORAGE_ACCOUNT: "devstoreaccount1" - AZURE_STORAGE_ACCESS_KEY: "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" - AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} - AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - run: | - sudo mkdir -p /srv ; sudo chown runner /srv - COVERAGE_DIR=${{ runner.temp }} make system-test - - - name: "Merge Code Coverage" - run: | - go install github.com/wadey/gocovmerge@v0.0.0-20160331181800-b5bfa59ec0ad - ~/go/bin/gocovmerge unit.out ${{ runner.temp }}/*.out > coverage.txt - - - name: "Upload Code Coverage" - uses: codecov/codecov-action@v2 - with: - token: ${{ secrets.CODECOV_TOKEN }} - files: coverage.txt - - ci-debian-build: - name: "Build" - needs: test + # Security scanning + security: + name: Security Scan runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - name: ["Debian 13/testing", "Debian 12/bookworm", "Debian 11/bullseye", "Debian 10/buster", "Ubuntu 24.04", "Ubuntu 22.04", "Ubuntu 20.04"] - arch: ["amd64", "i386" , "arm64" , "armhf"] - include: - - name: "Debian 13/testing" - suite: trixie - image: debian:trixie-slim - - name: "Debian 12/bookworm" - suite: bookworm - image: debian:bookworm-slim - - name: "Debian 11/bullseye" - suite: bullseye - image: debian:bullseye-slim - - name: "Debian 10/buster" - suite: buster - image: debian:buster-slim - - name: "Ubuntu 24.04" - suite: noble - image: ubuntu:24.04 - - name: "Ubuntu 22.04" - suite: jammy - image: ubuntu:22.04 - - name: "Ubuntu 20.04" - suite: focal - image: ubuntu:20.04 - container: - image: ${{ matrix.image }} - env: - APT_LISTCHANGES_FRONTEND: none - DEBIAN_FRONTEND: noninteractive steps: - - name: "Install Build Packages" - run: | - apt-get update - apt-get install -y --no-install-recommends make ca-certificates git curl build-essential devscripts dh-golang jq bash-completion lintian \ - binutils-i686-linux-gnu binutils-aarch64-linux-gnu binutils-arm-linux-gnueabihf \ - libc6-dev-i386-cross libc6-dev-armhf-cross libc6-dev-arm64-cross \ - gcc-i686-linux-gnu gcc-arm-linux-gnueabihf gcc-aarch64-linux-gnu - git config --global --add safe.directory "$GITHUB_WORKSPACE" - - - name: "Checkout Repository" - uses: actions/checkout@v4 - with: - # fetch the whole repo for `git describe` to work - fetch-depth: 0 - - - name: "Read Go Version" + - uses: actions/checkout@v4 + + - name: Read Go Version run: | gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) echo "Go Version: $gover" echo "GOVER=$gover" >> $GITHUB_OUTPUT id: goversion - - - name: "Setup Go" - uses: actions/setup-go@v3 + + - name: Set up Go + uses: actions/setup-go@v5 with: go-version: ${{ steps.goversion.outputs.GOVER }} - - - name: "Ensure CI build" - if: github.ref == 'refs/heads/master' + cache: true + + - name: Run govulncheck run: | - echo "FORCE_CI=true" >> $GITHUB_OUTPUT - id: force_ci + go install golang.org/x/vuln/cmd/govulncheck@latest + govulncheck ./... + + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + scan-ref: '.' + format: 'sarif' + output: 'trivy-results.sarif' + + - name: Upload Trivy scan results + uses: github/codeql-action/upload-sarif@v3 + if: always() + with: + sarif_file: 'trivy-results.sarif' - - name: "Build Debian packages" + # Linting + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Read Go Version + run: | + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + cache: true + + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: ${{ env.GOLANGCI_LINT_VERSION }} + args: --timeout=5m + + # Unit tests with coverage + test-unit: + name: Unit Tests + runs-on: ubuntu-latest + needs: quick-checks + strategy: + matrix: + go: ['1.23', '1.24'] + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go }} + cache: true + + - name: Install etcd + run: | + sudo apt-get update + sudo apt-get install -y etcd + + - name: Run tests + run: | + go test -v -race -coverprofile=coverage.out -covermode=atomic ./... env: - FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }} - run: | - make dpkg DEBARCH=${{ matrix.arch }} + RUN_LONG_TESTS: yes + + - name: Upload coverage + uses: codecov/codecov-action@v4 + if: matrix.go == '1.24' + with: + file: ./coverage.out + flags: unittests + name: codecov-umbrella - - name: "Check aptly credentials" - env: - APTLY_USER: ${{ secrets.APTLY_USER }} - APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }} + # Integration tests + test-integration: + name: Integration Tests + runs-on: ubuntu-latest + needs: quick-checks + steps: + - uses: actions/checkout@v4 + + - name: Read Go Version run: | - found=no - if [ -n "$APTLY_USER" ] && [ -n "$APTLY_PASSWORD" ]; then - found=yes + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + cache: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install -y \ + bzip2 \ + xz-utils \ + graphviz \ + gnupg2 \ + gpgv2 \ + etcd \ + azurite \ + git \ + gcc \ + make \ + devscripts \ + python3 \ + python3-requests-unixsocket \ + python3-termcolor \ + python3-swiftclient \ + python3-boto \ + python3-azure-storage \ + python3-etcd3 \ + python3-plyvel \ + faketime + + - name: Install Python dependencies + run: | + pip install -r system/requirements.txt + + - name: Download test fixtures + run: | + git clone https://github.com/aptly-dev/aptly-fixture-db.git ~/aptly-fixture-db/ + git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/ + + - name: Run system tests + run: | + make system-test + env: + RUN_LONG_TESTS: yes + AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AZURE_STORAGE_ACCOUNT: ${{ secrets.AZURE_STORAGE_ACCOUNT }} + AZURE_STORAGE_KEY: ${{ secrets.AZURE_STORAGE_KEY }} + + # Benchmarks + benchmarks: + name: Benchmarks + runs-on: ubuntu-latest + needs: quick-checks + steps: + - uses: actions/checkout@v4 + + - name: Read Go Version + run: | + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + cache: true + + - name: Install etcd + run: | + sudo apt-get update + sudo apt-get install -y etcd + + - name: Run benchmarks + run: | + make bench + + # Extended test job with coverage merging + test-extended: + name: Extended Tests with Coverage + runs-on: ubuntu-latest + needs: quick-checks + steps: + - uses: actions/checkout@v4 + + - name: Read Go Version + run: | + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + cache: true + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y etcd + go install github.com/wadey/gocovmerge@latest + + - name: Run unit tests with coverage + run: | + go test -v -coverprofile=unit.coverage -covermode=count ./... + env: + RUN_LONG_TESTS: yes + + - name: Run benchmarks with coverage + run: | + go test -v -bench=. -benchmem -coverprofile=bench.coverage -covermode=count ./... + + - name: Merge coverage files + run: | + gocovmerge unit.coverage bench.coverage > coverage.out + + - name: Upload merged coverage + uses: codecov/codecov-action@v4 + with: + file: ./coverage.out + flags: extended + name: codecov-extended + + # Advanced coverage analysis with PR comments + advanced-coverage: + name: Advanced Coverage Analysis + runs-on: ubuntu-latest + needs: quick-checks + if: github.event_name == 'pull_request' + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Checkout base branch + run: | + git fetch origin ${{ github.base_ref }}:base + + - name: Read Go Version + run: | + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + cache: true + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y etcd + go install github.com/jandelgado/gcov2lcov@latest + go install github.com/nikolaydubina/go-cover-treemap@latest + + - name: Run tests with coverage on PR branch + run: | + go test -v -race -coverprofile=coverage.out -covermode=atomic ./... + go tool cover -func=coverage.out > coverage-summary.txt + go tool cover -html=coverage.out -o coverage.html + + # Generate treemap + go-cover-treemap -coverprofile coverage.out > coverage-treemap.svg + + # Calculate total coverage + TOTAL_COVERAGE=$(go tool cover -func=coverage.out | grep total | awk '{print $3}' | sed 's/%//') + echo "TOTAL_COVERAGE=$TOTAL_COVERAGE" >> $GITHUB_ENV + + - name: Run tests with coverage on base branch + run: | + git checkout base + go test -coverprofile=base-coverage.out -covermode=atomic ./... || true + BASE_COVERAGE=$(go tool cover -func=base-coverage.out | grep total | awk '{print $3}' | sed 's/%//' || echo "0") + echo "BASE_COVERAGE=$BASE_COVERAGE" >> $GITHUB_ENV + git checkout - + + - name: Generate coverage report + run: | + # Generate detailed package coverage + echo "# Coverage Report" > coverage-report.md + echo "" >> coverage-report.md + echo "## Summary" >> coverage-report.md + echo "- **Total Coverage**: ${{ env.TOTAL_COVERAGE }}%" >> coverage-report.md + echo "- **Base Coverage**: ${{ env.BASE_COVERAGE }}%" >> coverage-report.md + COVERAGE_DIFF=$(echo "${{ env.TOTAL_COVERAGE }} - ${{ env.BASE_COVERAGE }}" | bc) + echo "- **Change**: ${COVERAGE_DIFF}%" >> coverage-report.md + echo "" >> coverage-report.md + echo "## Package Coverage" >> coverage-report.md + echo '```' >> coverage-report.md + cat coverage-summary.txt >> coverage-report.md + echo '```' >> coverage-report.md + + - name: Upload coverage artifacts + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: | + coverage.html + coverage-treemap.svg + coverage-report.md + coverage-summary.txt + + - name: Comment PR + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + body-path: coverage-report.md + + # Performance profiling + performance-profile: + name: Performance Profiling + runs-on: ubuntu-latest + needs: quick-checks + if: github.event_name == 'pull_request' + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@v4 + + - name: Read Go Version + run: | + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + cache: true + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y etcd graphviz + go install github.com/google/pprof@latest + + - name: Run benchmarks with profiling + run: | + # Run benchmarks with CPU and memory profiling + go test -bench=. -benchmem -cpuprofile=cpu.prof -memprofile=mem.prof -benchtime=10s ./deb || true + + # Generate reports + go tool pprof -svg cpu.prof > cpu-profile.svg || true + go tool pprof -svg mem.prof > mem-profile.svg || true + + # Generate text reports + go tool pprof -text cpu.prof > cpu-profile.txt || true + go tool pprof -text mem.prof > mem-profile.txt || true + + # Create summary + echo "# Performance Profile Report" > performance-report.md + echo "" >> performance-report.md + echo "## CPU Profile Top Functions" >> performance-report.md + echo '```' >> performance-report.md + head -20 cpu-profile.txt >> performance-report.md || echo "No CPU profile data" >> performance-report.md + echo '```' >> performance-report.md + echo "" >> performance-report.md + echo "## Memory Profile Top Allocations" >> performance-report.md + echo '```' >> performance-report.md + head -20 mem-profile.txt >> performance-report.md || echo "No memory profile data" >> performance-report.md + echo '```' >> performance-report.md + + - name: Upload profile artifacts + uses: actions/upload-artifact@v4 + with: + name: performance-profiles + path: | + *.prof + *.svg + performance-report.md + + # Fuzz testing + fuzz-test: + name: Fuzz Testing + runs-on: ubuntu-latest + needs: quick-checks + if: github.event_name == 'pull_request' + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@v4 + + - name: Read Go Version + run: | + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + cache: true + + - name: Run fuzz tests + run: | + echo "# Fuzz Testing Report" > fuzz-report.md + echo "" >> fuzz-report.md + echo "## Results" >> fuzz-report.md + + # Find and run any fuzz tests + FUZZ_FOUND=false + for pkg in $(go list ./...); do + if go test -list "^Fuzz" $pkg 2>/dev/null | grep -q "^Fuzz"; then + FUZZ_FOUND=true + echo "Running fuzz tests in $pkg..." >> fuzz-report.md + if go test -fuzz=. -fuzztime=30s $pkg 2>&1 | tee fuzz-output.txt; then + echo "✅ Passed" >> fuzz-report.md + else + echo "❌ Failed" >> fuzz-report.md + cat fuzz-output.txt >> fuzz-report.md + fi + echo "" >> fuzz-report.md + fi + done + + if [ "$FUZZ_FOUND" = false ]; then + echo "ℹ️ No fuzz tests found in the codebase" >> fuzz-report.md fi - echo "Aptly credentials available: $found" - echo "FOUND=$found" >> $GITHUB_OUTPUT - id: aptlycreds - - - name: "Publish CI release to aptly" - if: github.ref == 'refs/heads/master' && steps.aptlycreds.outputs.FOUND == 'yes' - env: - APTLY_USER: ${{ secrets.APTLY_USER }} - APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }} - run: | - .github/workflows/scripts/upload-artifacts.sh ci ${{ matrix.suite }} - - - name: "Publish release to aptly" - if: startsWith(github.event.ref, 'refs/tags') && steps.aptlycreds.outputs.FOUND == 'yes' - env: - APTLY_USER: ${{ secrets.APTLY_USER }} - APTLY_PASSWORD: ${{ secrets.APTLY_PASSWORD }} - run: | - .github/workflows/scripts/upload-artifacts.sh release ${{ matrix.suite }} - - - name: "Get aptly version" - env: - FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }} - run: | - aptlyver=$(make -s version) - echo "Aptly Version: $aptlyver" - echo "VERSION=$aptlyver" >> $GITHUB_OUTPUT - id: releaseversion - - - name: "Upload CI Artifacts" - if: github.ref != 'refs/heads/master' && !startsWith(github.event.ref, 'refs/tags') + + - name: Upload fuzz artifacts uses: actions/upload-artifact@v4 + if: always() with: - name: aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.suite }}_${{ matrix.arch }} - path: build/ - retention-days: 7 + name: fuzz-test-results + path: | + fuzz-report.md + testdata/fuzz/ - ci-binary-build: - name: "Build" - needs: test + # Static analysis suite + deep-analysis: + name: Deep Static Analysis runs-on: ubuntu-latest - strategy: - matrix: - goos: [linux, freebsd, darwin] - goarch: ["386", "amd64", "arm", "arm64"] - exclude: - - goos: darwin - goarch: 386 - - goos: darwin - goarch: arm + needs: quick-checks + if: github.event_name == 'pull_request' + permissions: + contents: read + pull-requests: write + security-events: write steps: - - name: "Checkout Repository" - uses: actions/checkout@v4 - with: - # fetch the whole repo for `git describe` to work - fetch-depth: 0 - - - name: "Read Go Version" + - uses: actions/checkout@v4 + + - name: Read Go Version run: | - echo "GOVER=$(sed -n 's/^go \(.*\)/\1/p' go.mod)" >> $GITHUB_OUTPUT + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT id: goversion - - - name: "Setup Go" - uses: actions/setup-go@v3 + + - name: Set up Go + uses: actions/setup-go@v5 with: go-version: ${{ steps.goversion.outputs.GOVER }} - - - name: "Ensure CI build" - if: github.ref == 'refs/heads/master' + cache: true + + - name: Install analysis tools run: | - echo "FORCE_CI=true" >> $GITHUB_OUTPUT - id: force_ci - - - name: "Get aptly version" - env: - FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }} + go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest + go install github.com/gordonklaus/ineffassign@latest + go install github.com/securego/gosec/v2/cmd/gosec@latest + go install honnef.co/go/tools/cmd/staticcheck@latest + go install github.com/nishanths/exhaustive/cmd/exhaustive@latest + + - name: Run analysis suite run: | - aptlyver=$(make -s version) - echo "Aptly Version: $aptlyver" - echo "VERSION=$aptlyver" >> $GITHUB_OUTPUT - id: releaseversion - - - name: "Build aptly ${{ matrix.goos }}/${{ matrix.goarch }}" - env: - GOBIN: /usr/local/bin - FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }} - run: | - make binaries GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} - - - name: "Upload Artifacts" + echo "# Static Analysis Report" > analysis-report.md + echo "" >> analysis-report.md + + # Shadow detection + echo "## Shadow Variable Detection" >> analysis-report.md + if go vet -vettool=$(which shadow) ./... 2>&1 | tee shadow.txt | grep -q "shadowed variable"; then + echo "⚠️ Shadow variables detected:" >> analysis-report.md + echo '```' >> analysis-report.md + cat shadow.txt >> analysis-report.md + echo '```' >> analysis-report.md + else + echo "✅ No shadow variables detected" >> analysis-report.md + fi + echo "" >> analysis-report.md + + # Ineffectual assignments + echo "## Ineffectual Assignments" >> analysis-report.md + if ineffassign ./... 2>&1 | tee ineffassign.txt | grep -q ".go:"; then + echo "⚠️ Ineffectual assignments found:" >> analysis-report.md + echo '```' >> analysis-report.md + cat ineffassign.txt >> analysis-report.md + echo '```' >> analysis-report.md + else + echo "✅ No ineffectual assignments" >> analysis-report.md + fi + echo "" >> analysis-report.md + + # Security scan + echo "## Security Scan (gosec)" >> analysis-report.md + gosec -fmt=json -out=gosec.json ./... || true + if [ -s gosec.json ] && [ $(jq '.Issues | length' gosec.json) -gt 0 ]; then + echo "⚠️ Security issues found:" >> analysis-report.md + echo '```' >> analysis-report.md + jq -r '.Issues[] | "[\(.severity)] \(.file):\(.line) - \(.details)"' gosec.json >> analysis-report.md + echo '```' >> analysis-report.md + else + echo "✅ No security issues found" >> analysis-report.md + fi + echo "" >> analysis-report.md + + # Staticcheck + echo "## Staticcheck Analysis" >> analysis-report.md + if staticcheck ./... 2>&1 | tee staticcheck.txt | grep -q ".go:"; then + echo "ℹ️ Staticcheck suggestions:" >> analysis-report.md + echo '```' >> analysis-report.md + cat staticcheck.txt >> analysis-report.md + echo '```' >> analysis-report.md + else + echo "✅ No staticcheck issues" >> analysis-report.md + fi + echo "" >> analysis-report.md + + # Exhaustive enum checks + echo "## Exhaustive Enum Switch Analysis" >> analysis-report.md + if exhaustive ./... 2>&1 | tee exhaustive.txt | grep -q ".go:"; then + echo "⚠️ Non-exhaustive switches found:" >> analysis-report.md + echo '```' >> analysis-report.md + cat exhaustive.txt >> analysis-report.md + echo '```' >> analysis-report.md + else + echo "✅ All enum switches are exhaustive" >> analysis-report.md + fi + + - name: Upload analysis artifacts uses: actions/upload-artifact@v4 - if: startsWith(github.event.ref, 'refs/tags') with: - name: aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }} - path: build/aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip - compression-level: 0 # no compression - - - name: "Upload CI Artifacts" - uses: actions/upload-artifact@v4 - if: "!startsWith(github.event.ref, 'refs/tags')" + name: static-analysis-results + path: | + analysis-report.md + *.json + *.txt + + - name: Upload SARIF if available + uses: github/codeql-action/upload-sarif@v3 + if: always() + continue-on-error: true with: - name: aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }} - path: build/aptly_${{ steps.releaseversion.outputs.VERSION }}_${{ matrix.goos }}_${{ matrix.goarch }}.zip - compression-level: 0 # no compression - retention-days: 7 + sarif_file: gosec.sarif - gh-release: - name: "Github Release" + # Mutation testing + mutation-test: + name: Mutation Testing runs-on: ubuntu-latest - continue-on-error: false - needs: ci-binary-build - if: startsWith(github.event.ref, 'refs/tags') + needs: quick-checks + if: github.event_name == 'pull_request' + permissions: + contents: read + pull-requests: write steps: - - name: "Checkout Repository" - uses: actions/checkout@v4 - - - name: "Get aptly version" - env: - FORCE_CI: ${{ steps.force_ci.outputs.FORCE_CI }} + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Read Go Version run: | - aptlyver=$(make -s version) - echo "Aptly Version: $aptlyver" - echo "VERSION=$aptlyver" >> $GITHUB_OUTPUT - id: releaseversion + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + cache: true + + - name: Install mutation testing tool + run: | + go install github.com/zimmski/go-mutesting/cmd/go-mutesting@latest + sudo apt-get update && sudo apt-get install -y etcd + + - name: Run mutation tests on changed files + run: | + echo "# Mutation Testing Report" > mutation-report.md + echo "" >> mutation-report.md + + # Get changed Go files + CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep '\.go$' | grep -v '_test\.go$' || true) + + if [ -z "$CHANGED_FILES" ]; then + echo "ℹ️ No Go source files changed" >> mutation-report.md + else + echo "## Mutation Testing Results" >> mutation-report.md + echo "" >> mutation-report.md + + TOTAL_MUTANTS=0 + KILLED_MUTANTS=0 + + for file in $CHANGED_FILES; do + if [ -f "$file" ]; then + echo "Testing mutations in $file..." >> mutation-report.md + + # Run mutation testing with timeout + timeout 300 go-mutesting -verbose -include="$file" . 2>&1 | tee mutation-output.txt || true + + # Parse results + KILLED=$(grep -c "PASS" mutation-output.txt || echo "0") + TOTAL=$(grep -c "MUTATION" mutation-output.txt || echo "0") + + TOTAL_MUTANTS=$((TOTAL_MUTANTS + TOTAL)) + KILLED_MUTANTS=$((KILLED_MUTANTS + KILLED)) + + if [ "$TOTAL" -gt 0 ]; then + SCORE=$((KILLED * 100 / TOTAL)) + echo "- $file: $KILLED/$TOTAL mutants killed (${SCORE}%)" >> mutation-report.md + fi + fi + done + + if [ "$TOTAL_MUTANTS" -gt 0 ]; then + TOTAL_SCORE=$((KILLED_MUTANTS * 100 / TOTAL_MUTANTS)) + echo "" >> mutation-report.md + echo "**Total: $KILLED_MUTANTS/$TOTAL_MUTANTS mutants killed (${TOTAL_SCORE}%)**" >> mutation-report.md + fi + fi + + - name: Upload mutation artifacts + uses: actions/upload-artifact@v4 + with: + name: mutation-test-results + path: mutation-report.md - - name: "Download Artifacts" + # Dependency audit + dependency-audit: + name: Dependency Audit + runs-on: ubuntu-latest + needs: quick-checks + if: github.event_name == 'pull_request' + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@v4 + + - name: Read Go Version + run: | + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + cache: true + + - name: Install audit tools + run: | + go install github.com/sonatype-nexus-community/nancy@latest + go install github.com/Zxilly/go-size-analyzer/cmd/gsa@latest + + - name: Run dependency audit + run: | + echo "# Dependency Audit Report" > dependency-report.md + echo "" >> dependency-report.md + + # Check for outdated dependencies + echo "## Outdated Dependencies" >> dependency-report.md + go list -u -m all | grep '\[' > outdated.txt || true + if [ -s outdated.txt ]; then + echo "⚠️ Found outdated dependencies:" >> dependency-report.md + echo '```' >> dependency-report.md + cat outdated.txt >> dependency-report.md + echo '```' >> dependency-report.md + else + echo "✅ All dependencies are up to date" >> dependency-report.md + fi + echo "" >> dependency-report.md + + # Vulnerability scan + echo "## Vulnerability Scan (Nancy)" >> dependency-report.md + go list -json -m all | nancy sleuth 2>&1 | tee nancy.txt || true + if grep -q "Vulnerable" nancy.txt; then + echo "⚠️ Vulnerabilities found:" >> dependency-report.md + echo '```' >> dependency-report.md + grep -A5 "Vulnerable" nancy.txt >> dependency-report.md + echo '```' >> dependency-report.md + else + echo "✅ No known vulnerabilities" >> dependency-report.md + fi + echo "" >> dependency-report.md + + # Dependency graph + echo "## Dependency Statistics" >> dependency-report.md + DIRECT_DEPS=$(go list -m all | grep -v "^github.com/aptly-dev/aptly" | wc -l) + echo "- Direct dependencies: $DIRECT_DEPS" >> dependency-report.md + + # Binary size analysis + echo "" >> dependency-report.md + echo "## Binary Size Analysis" >> dependency-report.md + go build -o aptly-binary || true + if [ -f aptly-binary ]; then + SIZE=$(du -h aptly-binary | cut -f1) + echo "- Binary size: $SIZE" >> dependency-report.md + + # Analyze size + gsa aptly-binary -o size-analysis.txt || true + if [ -f size-analysis.txt ]; then + echo "### Top packages by size:" >> dependency-report.md + echo '```' >> dependency-report.md + head -20 size-analysis.txt >> dependency-report.md + echo '```' >> dependency-report.md + fi + fi + + - name: Upload dependency artifacts + uses: actions/upload-artifact@v4 + with: + name: dependency-audit-results + path: | + dependency-report.md + *.txt + + # Stress testing + stress-test: + name: Stress Testing + runs-on: ubuntu-latest + needs: quick-checks + if: github.event_name == 'pull_request' + permissions: + contents: read + pull-requests: write + steps: + - uses: actions/checkout@v4 + + - name: Read Go Version + run: | + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + cache: true + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y etcd + + - name: Run stress tests + run: | + echo "# Stress Testing Report" > stress-report.md + echo "" >> stress-report.md + + # Run race detection with multiple iterations + echo "## Race Detection (100 iterations)" >> stress-report.md + if go test -race -count=100 -timeout=30m ./utils ./database/goleveldb ./context 2>&1 | tee race-stress.txt; then + echo "✅ No races detected in 100 iterations" >> stress-report.md + else + echo "❌ Race conditions detected:" >> stress-report.md + echo '```' >> stress-report.md + grep -A10 "WARNING: DATA RACE" race-stress.txt | head -50 >> stress-report.md + echo '```' >> stress-report.md + fi + echo "" >> stress-report.md + + # Parallel stress test + echo "## Parallel Execution Test" >> stress-report.md + if go test -parallel=10 -count=10 ./deb 2>&1 | tee parallel-stress.txt; then + echo "✅ Parallel tests passed" >> stress-report.md + else + echo "❌ Parallel test failures" >> stress-report.md + tail -50 parallel-stress.txt >> stress-report.md + fi + echo "" >> stress-report.md + + # Memory stress test + echo "## Memory Stress Test" >> stress-report.md + GOGC=10 go test -run=TestPackageCollection -count=50 ./deb 2>&1 | tee memory-stress.txt || true + echo "✅ Memory stress test completed" >> stress-report.md + + - name: Upload stress test artifacts + uses: actions/upload-artifact@v4 + with: + name: stress-test-results + path: | + stress-report.md + *-stress.txt + + # Aggregate all reports into a single PR comment + test-report-summary: + name: Test Report Summary + runs-on: ubuntu-latest + needs: [advanced-coverage, performance-profile, fuzz-test, deep-analysis, mutation-test, dependency-audit, stress-test] + if: always() && github.event_name == 'pull_request' + permissions: + contents: read + pull-requests: write + steps: + - name: Download all artifacts uses: actions/download-artifact@v4 with: - path: out/ - - - name: "Create Release Notes" + path: reports + + - name: Create combined report run: | - echo -e "## Changes\n\n" > out/release-notes.md - dpkg-parsechangelog -S Changes | tail -n +4 >> out/release-notes.md - - - name: "Release" - uses: softprops/action-gh-release@v2 + echo "# 📊 Comprehensive Test Report for PR #${{ github.event.pull_request.number }}" > combined-report.md + echo "" >> combined-report.md + echo "*Generated at $(date -u)*" >> combined-report.md + echo "" >> combined-report.md + + # Coverage section + if [ -f reports/coverage-report/coverage-report.md ]; then + cat reports/coverage-report/coverage-report.md >> combined-report.md + echo "" >> combined-report.md + echo "[📄 Full HTML Report](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> combined-report.md + echo "" >> combined-report.md + fi + + # Performance section + if [ -f reports/performance-profiles/performance-report.md ]; then + echo "
" >> combined-report.md + echo "🔥 Performance Profile" >> combined-report.md + echo "" >> combined-report.md + cat reports/performance-profiles/performance-report.md >> combined-report.md + echo "
" >> combined-report.md + echo "" >> combined-report.md + fi + + # Fuzz testing section + if [ -f reports/fuzz-test-results/fuzz-report.md ]; then + echo "
" >> combined-report.md + echo "🧬 Fuzz Testing" >> combined-report.md + echo "" >> combined-report.md + cat reports/fuzz-test-results/fuzz-report.md >> combined-report.md + echo "
" >> combined-report.md + echo "" >> combined-report.md + fi + + # Static analysis section + if [ -f reports/static-analysis-results/analysis-report.md ]; then + echo "
" >> combined-report.md + echo "🔍 Static Analysis" >> combined-report.md + echo "" >> combined-report.md + cat reports/static-analysis-results/analysis-report.md >> combined-report.md + echo "
" >> combined-report.md + echo "" >> combined-report.md + fi + + # Mutation testing section + if [ -f reports/mutation-test-results/mutation-report.md ]; then + echo "
" >> combined-report.md + echo "🦠 Mutation Testing" >> combined-report.md + echo "" >> combined-report.md + cat reports/mutation-test-results/mutation-report.md >> combined-report.md + echo "
" >> combined-report.md + echo "" >> combined-report.md + fi + + # Dependency audit section + if [ -f reports/dependency-audit-results/dependency-report.md ]; then + echo "
" >> combined-report.md + echo "📦 Dependency Audit" >> combined-report.md + echo "" >> combined-report.md + cat reports/dependency-audit-results/dependency-report.md >> combined-report.md + echo "
" >> combined-report.md + echo "" >> combined-report.md + fi + + # Stress test section + if [ -f reports/stress-test-results/stress-report.md ]; then + echo "
" >> combined-report.md + echo "💪 Stress Testing" >> combined-report.md + echo "" >> combined-report.md + cat reports/stress-test-results/stress-report.md >> combined-report.md + echo "
" >> combined-report.md + echo "" >> combined-report.md + fi + + echo "---" >> combined-report.md + echo "*View detailed artifacts in the [Actions run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})*" >> combined-report.md + + - name: Find Comment + uses: peter-evans/find-comment@v3 + id: fc with: - name: "Aptly Release ${{ steps.releaseversion.outputs.VERSION }}" - files: "out/**/aptly_*.zip" - body_path: "out/release-notes.md" + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Comprehensive Test Report + + - name: Create or Update Comment + uses: peter-evans/create-or-update-comment@v4 + with: + comment-id: ${{ steps.fc.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + body-path: combined-report.md + edit-mode: replace + # Build artifacts + build: + name: Build + runs-on: ubuntu-latest + needs: [lint, test-unit] + strategy: + matrix: + include: + - os: linux + arch: amd64 + - os: linux + arch: arm64 + - os: darwin + arch: amd64 + - os: darwin + arch: arm64 + - os: windows + arch: amd64 + steps: + - uses: actions/checkout@v4 + + - name: Read Go Version + run: | + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + cache: true + + - name: Build + run: | + GOOS=${{ matrix.os }} GOARCH=${{ matrix.arch }} go build \ + -ldflags "-X main.Version=${GITHUB_SHA::8}" \ + -o aptly-${{ matrix.os }}-${{ matrix.arch }}${{ matrix.os == 'windows' && '.exe' || '' }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: aptly-${{ matrix.os }}-${{ matrix.arch }} + path: aptly-${{ matrix.os }}-${{ matrix.arch }}* + + # Docker build + docker: + name: Docker Build + runs-on: ubuntu-latest + needs: [lint, test-unit] + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: false + tags: | + aptly/aptly:latest + aptly/aptly:${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # Debian package building + debian-packages: + name: Build Debian Packages + runs-on: ubuntu-22.04 + needs: [lint, test-unit] + if: github.event_name == 'push' || github.event_name == 'pull_request' + strategy: + matrix: + include: + - distribution: debian + release: buster + arch: amd64 + - distribution: debian + release: bullseye + arch: amd64 + - distribution: debian + release: bookworm + arch: amd64 + - distribution: debian + release: trixie + arch: amd64 + - distribution: ubuntu + release: focal + arch: amd64 + - distribution: ubuntu + release: jammy + arch: amd64 + - distribution: ubuntu + release: noble + arch: amd64 + # ARM builds + - distribution: debian + release: bullseye + arch: arm64 + - distribution: debian + release: bookworm + arch: arm64 + - distribution: ubuntu + release: jammy + arch: arm64 + - distribution: ubuntu + release: noble + arch: arm64 + # 32-bit builds + - distribution: debian + release: bullseye + arch: i386 + - distribution: debian + release: bookworm + arch: i386 + - distribution: debian + release: bullseye + arch: armhf + - distribution: debian + release: bookworm + arch: armhf + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up QEMU + if: matrix.arch != 'amd64' && matrix.arch != 'i386' + uses: docker/setup-qemu-action@v3 + + - name: Read Go Version + run: | + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + + - name: Setup CI version suffix + if: github.event_name != 'push' || !startsWith(github.ref, 'refs/tags/') + run: | + echo "CI_VERSION_SUFFIX=+ci" >> $GITHUB_ENV + + - name: Build Debian package + run: | + export DEBIAN_FRONTEND=noninteractive + + # Install build dependencies + sudo apt-get update + sudo apt-get install -y devscripts dpkg-dev + + # Install cross-compilation tools if needed + if [[ "${{ matrix.arch }}" != "amd64" && "${{ matrix.arch }}" != "i386" ]]; then + sudo apt-get install -y gcc-aarch64-linux-gnu gcc-arm-linux-gnueabihf + fi + + # Set up environment for cross-compilation + case "${{ matrix.arch }}" in + i386) + export GOARCH=386 + ;; + arm64) + export GOARCH=arm64 + export CC=aarch64-linux-gnu-gcc + ;; + armhf) + export GOARCH=arm + export GOARM=7 + export CC=arm-linux-gnueabihf-gcc + ;; + *) + export GOARCH=amd64 + ;; + esac + + # Build the package + make dpkg DEBIAN_RELEASE=${{ matrix.release }} ARCHITECTURE=${{ matrix.arch }} + + - name: Upload packages + uses: actions/upload-artifact@v4 + with: + name: debian-${{ matrix.distribution }}-${{ matrix.release }}-${{ matrix.arch }} + path: build/*.deb + retention-days: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && 90 || 7 }} + + # Binary builds with expanded platform support + binary-builds: + name: Build Binaries + runs-on: ubuntu-latest + needs: [lint, test-unit] + strategy: + matrix: + include: + # 64-bit builds + - os: linux + arch: amd64 + - os: linux + arch: arm64 + - os: darwin + arch: amd64 + - os: darwin + arch: arm64 + - os: windows + arch: amd64 + - os: freebsd + arch: amd64 + # 32-bit builds + - os: linux + arch: 386 + - os: linux + arch: arm + - os: windows + arch: 386 + - os: freebsd + arch: 386 + - os: freebsd + arch: arm + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Read Go Version + run: | + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + + - name: Setup version + run: | + if [[ "${{ github.ref }}" == refs/tags/* ]]; then + VERSION=${GITHUB_REF#refs/tags/} + else + VERSION=$(make version) + fi + echo "VERSION=$VERSION" >> $GITHUB_ENV + + - name: Build binary + run: | + # Set architecture-specific variables + case "${{ matrix.arch }}" in + 386) + export GOARCH=386 + ;; + arm) + export GOARCH=arm + export GOARM=7 + ;; + *) + export GOARCH=${{ matrix.arch }} + ;; + esac + + export GOOS=${{ matrix.os }} + + # Build with static linking for Linux + if [[ "${{ matrix.os }}" == "linux" ]]; then + export CGO_ENABLED=0 + LDFLAGS="-extldflags=-static" + else + LDFLAGS="" + fi + + # Build the binary + go build -ldflags "$LDFLAGS -X main.Version=$VERSION" -o aptly${{ matrix.os == 'windows' && '.exe' || '' }} + + - name: Build man pages + if: matrix.os == 'linux' && matrix.arch == 'amd64' + run: | + make man + + - name: Build completion files + if: matrix.os == 'linux' && matrix.arch == 'amd64' + run: | + make completion + + - name: Create archive + run: | + ARCHIVE_NAME="aptly_${VERSION}_${{ matrix.os }}_${{ matrix.arch }}.zip" + + # Include binary + FILES="aptly${{ matrix.os == 'windows' && '.exe' || '' }}" + + # Include man pages and completions for main Linux build + if [[ "${{ matrix.os }}" == "linux" && "${{ matrix.arch }}" == "amd64" ]]; then + FILES="$FILES man completion" + fi + + zip -r $ARCHIVE_NAME $FILES + echo "ARCHIVE_NAME=$ARCHIVE_NAME" >> $GITHUB_ENV + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: binary-${{ matrix.os }}-${{ matrix.arch }} + path: ${{ env.ARCHIVE_NAME }} + retention-days: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') && 90 || 7 }} + + # Dependency check + dependencies: + name: Check Dependencies + runs-on: ubuntu-latest + if: github.event_name == 'schedule' + steps: + - uses: actions/checkout@v4 + + - name: Read Go Version + run: | + gover=$(sed -n 's/^go \(.*\)/\1/p' go.mod) + echo "Go Version: $gover" + echo "GOVER=$gover" >> $GITHUB_OUTPUT + id: goversion + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version: ${{ steps.goversion.outputs.GOVER }} + cache: true + + - name: Check for updates + run: | + echo "## Outdated Dependencies" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + go list -u -m all | grep '\[' >> $GITHUB_STEP_SUMMARY || echo "All dependencies are up to date!" >> $GITHUB_STEP_SUMMARY \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index 1e62d33b..313ece8a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,11 +1,211 @@ -version: "2" +# golangci-lint configuration for aptly +# Run with: golangci-lint run + +run: + # Timeout for analysis + timeout: 5m + + # Include test files + tests: true + + # Skip directories + skip-dirs: + - vendor + - testdata + - system/files + + # Skip files + skip-files: + - ".*\\.pb\\.go$" + - ".*\\.gen\\.go$" + +output: + # Format of output + formats: + - format: colored-line-number + + # Print lines of code with issue + print-issued-lines: true + + # Print linter name in the end of issue text + print-linter-name: true + linters: - settings: - staticcheck: - checks: - - "all" - - "-QF1004" # could use strings.ReplaceAll instead - - "-QF1012" # Use fmt.Fprintf(...) instead of WriteString(fmt.Sprintf(...)) - - "-QF1003" # could use tagged switch - - "-ST1000" # at least one file in a package should have a package comment - - "-QF1001" # could apply De Morgan's law + enable: + # Default linters + - errcheck # Check for unchecked errors + - gosimple # Simplify code + - govet # Go vet + - ineffassign # Detect ineffectual assignments + - staticcheck # Static analysis + - typecheck # Type checking + - unused # Find unused code + + # Additional linters for code quality + - bodyclose # Check HTTP response body is closed + - dupl # Code duplication + - exportloopref # Check loop variable export + - gocognit # Cognitive complexity + - gocritic # Opinionated linter + - gocyclo # Cyclomatic complexity + - gofmt # Formatting + - goimports # Import formatting + - revive # Fast, configurable linter + - unconvert # Unnecessary type conversions + - unparam # Unused function parameters + - gosec # Security issues + - prealloc # Preallocate slices + - predeclared # Shadowing of predeclared identifiers + - makezero # Make slice with non-zero length + - nakedret # Naked returns in long functions + + disable: + # Disabled because they're too strict or noisy + - exhaustive # Too strict for switch statements + - wsl # Whitespace linter (too opinionated) + - godox # TODO/FIXME comments + - gochecknoglobals # We use some globals + - gochecknoinits # We use init functions + +linters-settings: + # errcheck + errcheck: + # Report about not checking of errors in type assertions + check-type-assertions: true + # Report about assignment of errors to blank identifier + check-blank: true + # Exclude some functions from checking + exclude-functions: + - io/ioutil.ReadFile + - io.Copy(*bytes.Buffer) + - io.Copy(os.Stdout) + + # govet + govet: + enable-all: true + disable: + - fieldalignment # Too many false positives + + # gocyclo + gocyclo: + min-complexity: 15 + + # gocognit + gocognit: + min-complexity: 20 + + # dupl + dupl: + threshold: 150 + + # gocritic + gocritic: + enabled-tags: + - diagnostic + - performance + - style + disabled-checks: + - commentedOutCode + - whyNoLint + + # gosec + gosec: + severity: low + confidence: low + excludes: + - G404 # Weak random for non-crypto use is ok + + # revive + revive: + rules: + - name: blank-imports + - name: context-as-argument + - name: context-keys-type + - name: dot-imports + - name: empty-block + - name: error-naming + - name: error-return + - name: error-strings + - name: errorf + - name: exported + - name: increment-decrement + - name: indent-error-flow + - name: package-comments + - name: range + - name: receiver-naming + - name: time-naming + - name: unexported-return + - name: var-declaration + - name: var-naming + + # goimports + goimports: + local-prefixes: github.com/aptly-dev/aptly + + # gofmt + gofmt: + simplify: true + + # unparam + unparam: + check-exported: false + + # nakedret + nakedret: + max-func-lines: 30 + + # prealloc + prealloc: + simple: true + range-loops: true + for-loops: false + +issues: + # Maximum issues count per one linter + max-issues-per-linter: 0 + + # Maximum count of issues with the same text + max-same-issues: 0 + + # Exclude some linters from running on tests files + exclude-rules: + - path: _test\.go + linters: + - dupl + - gosec + - gocognit + - gocyclo + + # Exclude some linters from running on generated files + - path: ".*\\.gen\\.go$" + linters: + - all + + # Exclude known issues in vendor + - path: vendor/ + linters: + - all + + # Allow fmt.Printf in main/cmd + - path: (cmd|main)\.go + linters: + - forbidigo + + # Independently from option `exclude` we use default exclude patterns + exclude-use-default: true + + # Fix found issues (if it's supported by the linter) + fix: false + +severity: + # Set the default severity for issues + default-severity: warning + + # The list of ids of default excludes to include or disable + rules: + - linters: + - gosec + severity: info + - linters: + - dupl + severity: info \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 762cf8a4..72b071aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -158,7 +158,7 @@ This section describes local setup to start contributing to aptly. #### Dependencies -Building aptly requires go version 1.22. +Building aptly requires go version 1.24. On Debian bookworm with backports enabled, go can be installed with: @@ -234,6 +234,59 @@ There are some packages available under `system/files/` directory which are used this default location. You can run aptly under different user or by using non-default config location with non-default aptly root directory. +### Continuous Integration (CI) + +aptly uses GitHub Actions for continuous integration. The CI pipeline includes: + +- **Quick checks**: Code formatting, go vet, mod tidy, and flake8 linting +- **Security scanning**: govulncheck and Trivy vulnerability scanning +- **Linting**: golangci-lint with extensive checks +- **Unit tests**: With race detection on Go 1.23 and 1.24 +- **Integration tests**: Full system tests with cloud storage backends +- **Benchmarks**: Performance testing +- **Extended tests**: Combined unit tests and benchmarks with coverage merging +- **Cross-platform builds**: Binaries for Linux, macOS, Windows, FreeBSD (multiple architectures) +- **Debian packages**: Built for Debian (buster, bullseye, bookworm, trixie) and Ubuntu (focal, jammy, noble) +- **Docker images**: Multi-architecture container images (linux/amd64, linux/arm64) + +All pull requests must pass CI checks before merging. Build artifacts are available for download from GitHub Actions runs with the following retention: +- CI builds: 7 days +- Tagged releases: 90 days + +#### Testing CI Locally with act + +You can test GitHub Actions workflows locally using [act](https://github.com/nektos/act): + +```bash +# Install act +brew install act # macOS +# or +curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash # Linux + +# Run default push event +act + +# Run pull request event +act pull_request + +# Run specific job +act -j test-unit + +# Run with specific matrix values +act -j test-unit --matrix go:1.24 + +# List all available jobs +act -l +``` + +For Apple Silicon Macs, use: `act --container-architecture linux/amd64` + +Common use cases: +- Test a job before pushing: `act -j quick-checks` +- Test PR workflows: Create a PR event file and run `act pull_request -e pr-event.json` +- Debug failures: `act -j failing-job -v` for verbose output +- Use secrets: Create `.secrets` file with `KEY=value` format and run `act --secret-file .secrets` + ### man Page aptly is using combination of [Go templates](http://godoc.org/text/template) and automatically generated text to build `aptly.1` man page. If either source diff --git a/Makefile b/Makefile index ffe2e8a3..6d64d44a 100644 --- a/Makefile +++ b/Makefile @@ -1,42 +1,55 @@ -GOPATH=$(shell go env GOPATH) -VERSION=$(shell make -s version) -PYTHON?=python3 -BINPATH?=$(GOPATH)/bin -GOLANGCI_LINT_VERSION=v2.0.2 # version supporting go 1.24 -COVERAGE_DIR?=$(shell mktemp -d) -GOOS=$(shell go env GOHOSTOS) -GOARCH=$(shell go env GOHOSTARCH) +# Modern Makefile for aptly with improved tooling and practices -# Unit Tests and some sysmte tests rely on expired certificates, turn back the time -export TEST_FAKETIME := 2025-01-02 03:04:05 +SHELL := /bin/bash +.DEFAULT_GOAL := help +.PHONY: help -# export CAPUTRE=1 for regenrating test gold files -ifeq ($(CAPTURE),1) -CAPTURE_ARG := --capture -endif +# Version and build info +BUILD_DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ") +GIT_COMMIT := $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown") -help: ## Print this help - @grep -E '^[a-zA-Z][a-zA-Z0-9_-]*:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' +# Go parameters +GOCMD := go +GOBUILD := $(GOCMD) build +GOTEST := $(GOCMD) test +GOGET := $(GOCMD) get +GOMOD := $(GOCMD) mod +GOFMT := gofmt +GOPATH := $(shell go env GOPATH) +BINPATH := $(GOPATH)/bin +GOOS := $(shell go env GOHOSTOS) +GOARCH := $(shell go env GOHOSTARCH) -prepare: ## Install go module dependencies - # Prepare go modules - go mod verify - go mod tidy -v - # Generate VERSION file - go generate +# Tool versions +GOLANGCI_VERSION := v1.64.5 +AIR_VERSION := v1.52.3 +SWAG_VERSION := v1.16.4 +GOVULNCHECK_VERSION := latest -releasetype: # Print release type: ci (on any branch/commit), release (on a tag) - @reltype=ci ; \ - gitbranch=`git rev-parse --abbrev-ref HEAD` ; \ - if [ "$$gitbranch" = "HEAD" ] && [ "$$FORCE_CI" != "true" ]; then \ - gittag=`git describe --tags --exact-match 2>/dev/null` ;\ - if echo "$$gittag" | grep -q '^v[0-9]'; then \ - reltype=release ; \ - fi ; \ - fi ; \ - echo $$reltype +# Build parameters +BINARY_NAME := aptly +BUILD_DIR := build +COVERAGE_DIR := coverage +COVERAGE_FILE := $(COVERAGE_DIR)/coverage.out -version: ## Print aptly version +# Docker parameters +DOCKER_IMAGE := aptly/aptly +DOCKER_TAG := $(VERSION) + +# Colors for output +COLOR_RESET := \033[0m +COLOR_BOLD := \033[1m +COLOR_GREEN := \033[32m +COLOR_YELLOW := \033[33m +COLOR_RED := \033[31m +COLOR_BLUE := \033[34m + +##@ General + +help: ## Display this help + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +version: ## Show version @ci="" ; \ if [ "`make -s releasetype`" = "ci" ]; then \ ci=`TZ=UTC git show -s --format='+%cd.%h' --date=format-local:'%Y%m%d%H%M%S'`; \ @@ -47,183 +60,233 @@ version: ## Print aptly version echo `grep ^aptly -m1 debian/changelog | sed 's/.*(\([^)]\+\)).*/\1/'`$$ci ; \ fi -swagger-install: - # Install swag - @test -f $(BINPATH)/swag || GOOS= GOARCH= go install github.com/swaggo/swag/cmd/swag@latest - # Generate swagger.conf - cp docs/swagger.conf.tpl docs/swagger.conf - echo "// @version $(VERSION)" >> docs/swagger.conf - -azurite-start: - azurite -l /tmp/aptly-azurite > ~/.azurite.log 2>&1 & \ - echo $$! > ~/.azurite.pid - -azurite-stop: - @kill `cat ~/.azurite.pid` - -swagger: swagger-install - # Generate swagger docs - @PATH=$(BINPATH)/:$(PATH) swag init --parseDependency --parseInternal --markdownFiles docs --generalInfo docs/swagger.conf - -etcd-install: - # Install etcd - test -d /tmp/aptly-etcd || system/t13_etcd/install-etcd.sh - -flake8: ## run flake8 on system test python files - flake8 system/ - -lint: prepare - # Install golangci-lint - @test -f $(BINPATH)/golangci-lint || go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) - # Running lint - @NO_COLOR=true PATH=$(BINPATH)/:$(PATH) golangci-lint run --max-issues-per-linter=0 --max-same-issues=0 - - -build: prepare swagger ## Build aptly - go build -o build/aptly - -install: - @echo "\e[33m\e[1mBuilding aptly ...\e[0m" - # go generate - @go generate - # go install -v - @out=`mktemp`; if ! go install -v > $$out 2>&1; then cat $$out; rm -f $$out; echo "\nBuild failed\n"; exit 1; else rm -f $$out; fi - -test: prepare swagger etcd-install ## Run unit tests (add TEST=regex to specify which tests to run) - @echo "\e[33m\e[1mStarting etcd ...\e[0m" - @mkdir -p /tmp/aptly-etcd-data; system/t13_etcd/start-etcd.sh > /tmp/aptly-etcd-data/etcd.log 2>&1 & - @echo "\e[33m\e[1mRunning go test ...\e[0m" - faketime "$(TEST_FAKETIME)" go test -v ./... -gocheck.v=true -check.f "$(TEST)" -coverprofile=unit.out; echo $$? > .unit-test.ret - @echo "\e[33m\e[1mStopping etcd ...\e[0m" - @pid=`cat /tmp/etcd.pid`; kill $$pid - @rm -f /tmp/aptly-etcd-data/etcd.log - @ret=`cat .unit-test.ret`; if [ "$$ret" = "0" ]; then echo "\n\e[32m\e[1mUnit Tests SUCCESSFUL\e[0m"; else echo "\n\e[31m\e[1mUnit Tests FAILED\e[0m"; fi; rm -f .unit-test.ret; exit $$ret - -system-test: prepare swagger etcd-install ## Run system tests - # build coverage binary - go test -v -coverpkg="./..." -c -tags testruncli - # Download fixture-db, fixture-pool, etcd.db - if [ ! -e ~/aptly-fixture-db ]; then git clone https://github.com/aptly-dev/aptly-fixture-db.git ~/aptly-fixture-db/; fi - if [ ! -e ~/aptly-fixture-pool ]; then git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; fi - test -f ~/etcd.db || (curl -o ~/etcd.db.xz http://repo.aptly.info/system-tests/etcd.db.xz && xz -d ~/etcd.db.xz) - # Run system tests - PATH=$(BINPATH)/:$(PATH) FORCE_COLOR=1 $(PYTHON) system/run.py --long --coverage-dir $(COVERAGE_DIR) $(CAPTURE_ARG) $(TEST) - -bench: - @echo "\e[33m\e[1mRunning benchmark ...\e[0m" - go test -v ./deb -run=nothing -bench=. -benchmem - -serve: prepare swagger-install ## Run development server (auto recompiling) - test -f $(BINPATH)/air || go install github.com/air-verse/air@v1.52.3 - cp debian/aptly.conf ~/.aptly.conf - sed -i /enable_swagger_endpoint/s/false/true/ ~/.aptly.conf - PATH=$(BINPATH):$$PATH air -build.pre_cmd 'swag init -q --markdownFiles docs --generalInfo docs/swagger.conf' -build.exclude_dir docs,system,debian,pgp/keyrings,pgp/test-bins,completion.d,man,deb/testdata,console,_man,systemd,obj-x86_64-linux-gnu -- api serve -listen 0.0.0.0:3142 - -dpkg: prepare swagger ## Build debian packages - @test -n "$(DEBARCH)" || (echo "please define DEBARCH"; exit 1) - # set debian version - @if [ "`make -s releasetype`" = "ci" ]; then \ - echo CI Build, setting version... ; \ - test ! -f debian/changelog.dpkg-bak || mv debian/changelog.dpkg-bak debian/changelog ; \ - cp debian/changelog debian/changelog.dpkg-bak ; \ - DEBEMAIL="CI " dch -v `make -s version` "CI build" ; \ - fi - # clean - rm -rf obj-i686-linux-gnu obj-arm-linux-gnueabihf obj-aarch64-linux-gnu obj-x86_64-linux-gnu - # Run dpkg-buildpackage - @buildtype="any" ; \ - if [ "$(DEBARCH)" = "amd64" ]; then \ - buildtype="any,all" ; \ +releasetype: # Print release type: ci (on any branch/commit), release (on a tag) + @reltype=ci ; \ + gitbranch=`git rev-parse --abbrev-ref HEAD` ; \ + if [ "$$gitbranch" = "HEAD" ] && [ "$$FORCE_CI" != "true" ]; then \ + gittag=`git describe --tags --exact-match 2>/dev/null` ;\ + if echo "$$gittag" | grep -q '^v[0-9]'; then \ + reltype=release ; \ + fi ; \ fi ; \ - echo "\e[33m\e[1mBuilding: $$buildtype\e[0m" ; \ - cmd="dpkg-buildpackage -us -uc --build=$$buildtype -d --host-arch=$(DEBARCH)" ; \ - echo "$$cmd" ; \ - $$cmd - lintian ../*_$(DEBARCH).changes || true - # cleanup - @test ! -f debian/changelog.dpkg-bak || mv debian/changelog.dpkg-bak debian/changelog; \ - mkdir -p build && mv ../*.deb build/ ; \ - cd build && ls -l *.deb + echo $$reltype -binaries: prepare swagger ## Build binary releases (FreeBSD, macOS, Linux generic) - # build aptly - GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o build/tmp/aptly -ldflags='-extldflags=-static' - # install - @mkdir -p build/tmp/man build/tmp/completion/bash_completion.d build/tmp/completion/zsh/vendor-completions - @cp man/aptly.1 build/tmp/man/ - @cp completion.d/aptly build/tmp/completion/bash_completion.d/ - @cp completion.d/_aptly build/tmp/completion/zsh/vendor-completions/ - @cp README.rst LICENSE AUTHORS build/tmp/ - @gzip -f build/tmp/man/aptly.1 - @path="aptly_$(VERSION)_$(GOOS)_$(GOARCH)"; \ - rm -rf "build/$$path"; \ - mv build/tmp build/"$$path"; \ - rm -rf build/tmp; \ - cd build; \ - zip -r "$$path".zip "$$path" > /dev/null \ - && echo "Built build/$${path}.zip"; \ - rm -rf "$$path" +##@ Development -docker-image: ## Build aptly-dev docker image - @docker build -f system/Dockerfile . -t aptly-dev +prepare: ## Prepare development environment + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Preparing development environment...$(COLOR_RESET)" + $(GOMOD) download + $(GOMOD) verify + $(GOMOD) tidy -v + @go generate ./... -docker-image-no-cache: ## Build aptly-dev docker image (no cache) - @docker build --no-cache -f system/Dockerfile . -t aptly-dev +dev-tools: ## Install development tools + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Installing development tools...$(COLOR_RESET)" + @go install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_VERSION) + @go install github.com/air-verse/air@$(AIR_VERSION) + @go install github.com/swaggo/swag/cmd/swag@$(SWAG_VERSION) + @go install golang.org/x/vuln/cmd/govulncheck@$(GOVULNCHECK_VERSION) + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Development tools installed$(COLOR_RESET)" -docker-build: ## Build aptly in docker container - @docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper build +##@ Build -docker-shell: ## Run aptly and other commands in docker container - @docker run -it --rm -p 3142:3142 -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper || true +build: prepare swagger ## Build aptly binary + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Building aptly...$(COLOR_RESET)" + @mkdir -p $(BUILD_DIR) + $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME) . + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Build complete: $(BUILD_DIR)/$(BINARY_NAME)$(COLOR_RESET)" -docker-deb: ## Build debian packages in docker container - @docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper dpkg DEBARCH=amd64 +build-all: prepare swagger ## Build for all platforms + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Building for all platforms...$(COLOR_RESET)" + @mkdir -p $(BUILD_DIR) + # Linux + GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 + GOOS=linux GOARCH=arm64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64 + # macOS + GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-amd64 + GOOS=darwin GOARCH=arm64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-arm64 + # Windows + GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Multi-platform build complete$(COLOR_RESET)" -docker-unit-test: ## Run unit tests in docker container (add TEST=regex to specify which tests to run) - @docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper \ - azurite-start \ - AZURE_STORAGE_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1 \ - AZURE_STORAGE_ACCOUNT=devstoreaccount1 \ - AZURE_STORAGE_ACCESS_KEY="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" \ - test TEST=$(TEST) \ - azurite-stop +install: build ## Install aptly to GOPATH/bin + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Installing aptly...$(COLOR_RESET)" + @cp $(BUILD_DIR)/$(BINARY_NAME) $(BINPATH)/ + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Installed to $(BINPATH)/$(BINARY_NAME)$(COLOR_RESET)" -docker-system-test: ## Run system tests in docker container (add TEST=t04_mirror or TEST=UpdateMirror26Test to run only specific tests) - @docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper \ - azurite-start \ - AZURE_STORAGE_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1 \ - AZURE_STORAGE_ACCOUNT=devstoreaccount1 \ - AZURE_STORAGE_ACCESS_KEY="Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==" \ - AWS_ACCESS_KEY_ID=$(AWS_ACCESS_KEY_ID) \ - AWS_SECRET_ACCESS_KEY=$(AWS_SECRET_ACCESS_KEY) \ - system-test TEST=$(TEST) \ - azurite-stop +##@ Testing -docker-serve: ## Run development server (auto recompiling) on http://localhost:3142 - @docker run -it --rm -p 3142:3142 -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper serve || true +test: prepare test-unit test-integration ## Run all tests -docker-lint: ## Run golangci-lint in docker container - @docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper lint +test-unit: prepare swagger etcd-install ## Run unit tests + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Running unit tests...$(COLOR_RESET)" + @mkdir -p $(COVERAGE_DIR) + $(GOTEST) -v -race -coverprofile=$(COVERAGE_DIR)/unit.out -covermode=atomic ./... + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Unit tests complete$(COLOR_RESET)" -docker-binaries: ## Build binary releases (FreeBSD, macOS, Linux generic) in docker container - @docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper binaries +test-integration: prepare swagger etcd-install ## Run integration tests + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Running integration tests...$(COLOR_RESET)" + @mkdir -p $(COVERAGE_DIR) + # Download fixtures if needed + @if [ ! -e ~/aptly-fixture-db ]; then \ + git clone https://github.com/aptly-dev/aptly-fixture-db.git ~/aptly-fixture-db/; \ + fi + @if [ ! -e ~/aptly-fixture-pool ]; then \ + git clone https://github.com/aptly-dev/aptly-fixture-pool.git ~/aptly-fixture-pool/; \ + fi + # Run system tests + PATH=$(BINPATH):$$PATH python3 system/run.py --coverage-dir $(COVERAGE_DIR) $(TEST) + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Integration tests complete$(COLOR_RESET)" -docker-man: ## Create man page in docker container - @docker run -it --rm -v ${PWD}:/work/src aptly-dev /work/src/system/docker-wrapper man +test-race: ## Run tests with race detector + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Running tests with race detector...$(COLOR_RESET)" + $(GOTEST) -race -short ./... + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Race detection complete$(COLOR_RESET)" -mem.png: mem.dat mem.gp - gnuplot mem.gp - open mem.png +coverage: test ## Generate coverage report + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Generating coverage report...$(COLOR_RESET)" + @mkdir -p $(COVERAGE_DIR) + @go tool cover -html=$(COVERAGE_DIR)/unit.out -o $(COVERAGE_DIR)/coverage.html + @go tool cover -func=$(COVERAGE_DIR)/unit.out + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Coverage report: $(COVERAGE_DIR)/coverage.html$(COLOR_RESET)" -man: ## Create man pages - make -C man +benchmark: ## Run benchmarks + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Running benchmarks...$(COLOR_RESET)" + $(GOTEST) -bench=. -benchmem ./deb ./files ./utils + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Benchmarks complete$(COLOR_RESET)" -clean: ## remove local build and module cache - # Clean all generated and build files - test ! -e .go || find .go/ -type d ! -perm -u=w -exec chmod u+w {} \; - rm -rf .go/ - rm -rf build/ obj-*-linux-gnu* tmp/ - rm -f unit.out aptly.test VERSION docs/docs.go docs/swagger.json docs/swagger.yaml docs/swagger.conf - find system/ -type d -name __pycache__ -exec rm -rf {} \; 2>/dev/null || true +##@ Code Quality -.PHONY: help man prepare swagger version binaries build docker-release docker-system-test docker-unit-test docker-lint docker-build docker-image docker-man docker-shell docker-serve clean releasetype dpkg serve flake8 +lint: dev-tools ## Run linters + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Running linters...$(COLOR_RESET)" + @golangci-lint run --timeout=5m + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Linting complete$(COLOR_RESET)" + +fmt: ## Format code + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Formatting code...$(COLOR_RESET)" + @$(GOFMT) -w -s . + @$(GOMOD) tidy + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Code formatted$(COLOR_RESET)" + +vet: ## Run go vet + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Running go vet...$(COLOR_RESET)" + @go vet ./... + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Vet complete$(COLOR_RESET)" + +security: dev-tools ## Run security checks + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Running security checks...$(COLOR_RESET)" + @govulncheck ./... + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Security check complete$(COLOR_RESET)" + +##@ Dependencies + +deps-update: ## Update dependencies + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Updating dependencies...$(COLOR_RESET)" + @./scripts/update-deps.sh + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Dependencies updated$(COLOR_RESET)" + +deps-check: ## Check for outdated dependencies + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Checking for outdated dependencies...$(COLOR_RESET)" + @go list -u -m all | grep '\[' || echo "All dependencies are up to date!" + +deps-graph: ## Generate dependency graph + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Generating dependency graph...$(COLOR_RESET)" + @go mod graph | grep -v '@' | sort | uniq + +##@ Documentation + +swagger: swagger-install ## Generate Swagger documentation + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Generating Swagger documentation...$(COLOR_RESET)" + @cp docs/swagger.conf.tpl docs/swagger.conf + @echo "// @version $(VERSION)" >> docs/swagger.conf + @swag init --parseDependency --parseInternal --markdownFiles docs --generalInfo docs/swagger.conf + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Swagger docs generated$(COLOR_RESET)" + +swagger-install: ## Install swagger tools + @test -f $(BINPATH)/swag || go install github.com/swaggo/swag/cmd/swag@$(SWAG_VERSION) + +docs: swagger ## Generate all documentation + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Documentation generated$(COLOR_RESET)" + +##@ Development Server + +serve: dev-tools prepare ## Run development server with hot reload + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Starting development server...$(COLOR_RESET)" + @cp debian/aptly.conf ~/.aptly.conf || true + @sed -i.bak '/enable_swagger_endpoint/s/false/true/' ~/.aptly.conf || true + @air -build.pre_cmd 'swag init -q --markdownFiles docs --generalInfo docs/swagger.conf' \ + -build.exclude_dir docs,system,debian,pgp/keyrings,pgp/test-bins,completion.d,man,deb/testdata,console,_man,systemd,obj-x86_64-linux-gnu \ + -- api serve -listen 0.0.0.0:3142 + +##@ Docker + +docker-build: ## Build Docker image + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Building Docker image...$(COLOR_RESET)" + docker build -t $(DOCKER_IMAGE):$(DOCKER_TAG) -t $(DOCKER_IMAGE):latest . + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Docker image built: $(DOCKER_IMAGE):$(DOCKER_TAG)$(COLOR_RESET)" + +docker-push: ## Push Docker image + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Pushing Docker image...$(COLOR_RESET)" + docker push $(DOCKER_IMAGE):$(DOCKER_TAG) + docker push $(DOCKER_IMAGE):latest + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Docker image pushed$(COLOR_RESET)" + +##@ Cleanup + +clean: ## Clean build artifacts + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Cleaning build artifacts...$(COLOR_RESET)" + @rm -rf $(BUILD_DIR) $(COVERAGE_DIR) + @rm -f docs/docs.go docs/swagger.json docs/swagger.yaml docs/swagger.conf + @rm -rf obj-* *.out *.test + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Clean complete$(COLOR_RESET)" + +clean-deps: ## Clean dependency cache + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Cleaning dependency cache...$(COLOR_RESET)" + @go clean -modcache + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Dependency cache cleaned$(COLOR_RESET)" + +##@ CI/CD + +ci: prepare lint test security ## Run CI pipeline + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ CI pipeline complete$(COLOR_RESET)" + +release: clean build-all ## Prepare release artifacts + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Preparing release...$(COLOR_RESET)" + @mkdir -p $(BUILD_DIR)/release + @for file in $(BUILD_DIR)/$(BINARY_NAME)-*; do \ + base=$$(basename $$file); \ + tar -czf $(BUILD_DIR)/release/$$base.tar.gz -C $(BUILD_DIR) $$base; \ + done + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Release artifacts ready in $(BUILD_DIR)/release$(COLOR_RESET)" + +##@ Utilities + +etcd-install: ## Install etcd for testing + @test -d /tmp/aptly-etcd || system/t13_etcd/install-etcd.sh + +etcd-start: ## Start etcd + @mkdir -p /tmp/aptly-etcd-data + @system/t13_etcd/start-etcd.sh > /tmp/aptly-etcd-data/etcd.log 2>&1 & + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ etcd started$(COLOR_RESET)" + +etcd-stop: ## Stop etcd + @kill `cat /tmp/etcd.pid` 2>/dev/null || true + @rm -f /tmp/etcd.pid + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ etcd stopped$(COLOR_RESET)" + +azurite-start: ## Start Azurite (Azure Storage Emulator) for tests + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Starting Azurite...$(COLOR_RESET)" + @azurite -l /tmp/aptly-azurite > ~/.azurite.log 2>&1 & \ + echo $$! > ~/.azurite.pid + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Azurite started (PID: $$(cat ~/.azurite.pid))$(COLOR_RESET)" + +azurite-stop: ## Stop Azurite + @echo -e "$(COLOR_YELLOW)$(COLOR_BOLD)Stopping Azurite...$(COLOR_RESET)" + @-kill `cat ~/.azurite.pid` 2>/dev/null || true + @rm -f ~/.azurite.pid + @echo -e "$(COLOR_GREEN)$(COLOR_BOLD)✓ Azurite stopped$(COLOR_RESET)" + +.PHONY: all build build-all install test test-unit test-integration test-race coverage benchmark \ + lint fmt vet security deps-update deps-check deps-graph docs swagger swagger-install serve \ + docker-build docker-push clean clean-deps ci release prepare dev-tools etcd-install etcd-start etcd-stop \ + azurite-start azurite-stop \ No newline at end of file diff --git a/README.rst b/README.rst index 0cdcac38..815d3a21 100644 --- a/README.rst +++ b/README.rst @@ -135,3 +135,52 @@ Scala sbt: Molior: - `Molior Debian Build System `_ by André Roth + +Configuration +============= + +etcd Database Configuration +--------------------------- + +When using etcd as the database backend, aptly supports several environment variables for configuration: + +**Timeout Configuration:** + +- ``APTLY_ETCD_TIMEOUT``: Operation timeout for etcd requests (default: ``60s``) + + Example: ``export APTLY_ETCD_TIMEOUT=30s`` + +- ``APTLY_ETCD_DIAL_TIMEOUT``: Connection timeout when establishing etcd connection (default: ``60s``) + + Example: ``export APTLY_ETCD_DIAL_TIMEOUT=10s`` + +**Connection Configuration:** + +- ``APTLY_ETCD_KEEPALIVE``: Keep-alive timeout for etcd connections (default: ``7200s``) + + Example: ``export APTLY_ETCD_KEEPALIVE=3600s`` + +- ``APTLY_ETCD_MAX_MSG_SIZE``: Maximum message size in bytes for etcd requests/responses (default: ``52428800`` - 50MB) + + Example: ``export APTLY_ETCD_MAX_MSG_SIZE=104857600`` # 100MB + +**Example Configuration:** + +.. code-block:: bash + + # Set shorter timeouts for faster failure detection + export APTLY_ETCD_TIMEOUT=30s + export APTLY_ETCD_DIAL_TIMEOUT=10s + + # Increase message size for large package operations + export APTLY_ETCD_MAX_MSG_SIZE=104857600 + + # Run aptly with etcd backend + aptly -config=/etc/aptly-etcd.conf mirror update debian-stable + +**Features:** + +- **Automatic Retry**: Read operations (Get) automatically retry up to 3 times with exponential backoff on temporary failures +- **Timeout Protection**: All etcd operations use context with timeout to prevent indefinite hangs +- **Enhanced Logging**: All etcd errors are logged with operation context for better debugging +- **Configurable Limits**: Message size limits can be adjusted for large package operations diff --git a/api/api.go b/api/api.go index ab8c8ba5..f9ed680c 100644 --- a/api/api.go +++ b/api/api.go @@ -7,6 +7,7 @@ import ( "sort" "strconv" "strings" + "sync" "sync/atomic" "github.com/aptly-dev/aptly/aptly" @@ -70,7 +71,7 @@ func apiReady(isReady *atomic.Value) func(*gin.Context) { return } - status := aptlyStatus{Status: "Aptly is ready"} + status := aptlyStatus{Status: "Aptly is ready"} c.JSON(200, status) } } @@ -100,7 +101,18 @@ type dbRequest struct { err chan<- error } -var dbRequests chan dbRequest +var ( + dbRequests chan dbRequest + dbRequestsOnce sync.Once +) + +// initDBRequests initializes the database request channel in a thread-safe manner +func initDBRequests() { + dbRequestsOnce.Do(func() { + dbRequests = make(chan dbRequest, 1) + go acquireDatabase() + }) +} // Acquire database lock and release it when not needed anymore. // @@ -139,9 +151,8 @@ func acquireDatabase() { // runTaskInBackground to run a task which accquire database. // Important do not forget to defer to releaseDatabaseConnection func acquireDatabaseConnection() error { - if dbRequests == nil { - return nil - } + // Ensure channel is initialized + initDBRequests() errCh := make(chan error) dbRequests <- dbRequest{acquiredb, errCh} @@ -151,9 +162,8 @@ func acquireDatabaseConnection() error { // Release database connection when not needed anymore func releaseDatabaseConnection() error { - if dbRequests == nil { - return nil - } + // Ensure channel is initialized + initDBRequests() errCh := make(chan error) dbRequests <- dbRequest{releasedb, errCh} @@ -178,7 +188,7 @@ func truthy(value interface{}) bool { if value == nil { return false } - switch v := value.(type) { + switch v := value.(type) { case string: switch strings.ToLower(v) { case "n", "no", "f", "false", "0", "off": diff --git a/api/db_test.go b/api/db_test.go index a17a544f..16187499 100644 --- a/api/db_test.go +++ b/api/db_test.go @@ -164,11 +164,11 @@ func (s *DBTestSuite) TestDbCleanupErrorHandling(c *C) { method string expectError bool }{ - {"Normal cleanup call", "/api/db/cleanup", "POST", true}, // Expect error due to no context + {"Normal cleanup call", "/api/db/cleanup", "POST", true}, // Expect error due to no context {"Cleanup with extra path", "/api/db/cleanup/extra", "POST", false}, // Route not matched - {"Cleanup with trailing slash", "/api/db/cleanup/", "POST", false}, // Route not matched - {"Case sensitive path", "/api/DB/cleanup", "POST", false}, // Route not matched - {"Case sensitive path", "/api/db/CLEANUP", "POST", false}, // Route not matched + {"Cleanup with trailing slash", "/api/db/cleanup/", "POST", false}, // Route not matched + {"Case sensitive path", "/api/DB/cleanup", "POST", false}, // Route not matched + {"Case sensitive path", "/api/db/CLEANUP", "POST", false}, // Route not matched } for _, test := range errorTests { @@ -229,10 +229,10 @@ func (s *DBTestSuite) TestDbCleanupResponseFormat(c *C) { // Should have proper response structure c.Check(w.Code, Not(Equals), 0) c.Check(w.Header(), NotNil) - + // If there's a response body, it should be valid if w.Body.Len() > 0 { body := w.Body.String() c.Check(len(body), Not(Equals), 0) } -} \ No newline at end of file +} diff --git a/api/error_test.go b/api/error_test.go index 2dff5ca4..bf2f3763 100644 --- a/api/error_test.go +++ b/api/error_test.go @@ -3,7 +3,6 @@ package api import ( "encoding/json" - . "gopkg.in/check.v1" ) @@ -20,7 +19,7 @@ func (s *ErrorTestSuite) TestErrorStruct(c *C) { func (s *ErrorTestSuite) TestErrorJSONMarshaling(c *C) { // Test JSON marshaling of Error struct err := Error{Error: "test error message"} - + jsonData, marshalErr := json.Marshal(err) c.Check(marshalErr, IsNil) c.Check(string(jsonData), Equals, `{"error":"test error message"}`) @@ -29,7 +28,7 @@ func (s *ErrorTestSuite) TestErrorJSONMarshaling(c *C) { func (s *ErrorTestSuite) TestErrorJSONUnmarshaling(c *C) { // Test JSON unmarshaling into Error struct jsonData := `{"error":"test error message"}` - + var err Error unmarshalErr := json.Unmarshal([]byte(jsonData), &err) c.Check(unmarshalErr, IsNil) @@ -40,7 +39,7 @@ func (s *ErrorTestSuite) TestErrorEmptyMessage(c *C) { // Test Error struct with empty message err := Error{Error: ""} c.Check(err.Error, Equals, "") - + jsonData, marshalErr := json.Marshal(err) c.Check(marshalErr, IsNil) c.Check(string(jsonData), Equals, `{"error":""}`) @@ -60,15 +59,15 @@ func (s *ErrorTestSuite) TestErrorSpecialCharacters(c *C) { "error with < > & characters", "error with null \x00 character", } - + for i, message := range specialMessages { err := Error{Error: message} c.Check(err.Error, Equals, message, Commentf("Test case %d", i)) - + // Test JSON marshaling works with special characters jsonData, marshalErr := json.Marshal(err) c.Check(marshalErr, IsNil, Commentf("Marshal failed for case %d: %s", i, message)) - + // Test JSON unmarshaling works with special characters var unmarshaled Error unmarshalErr := json.Unmarshal(jsonData, &unmarshaled) @@ -83,14 +82,14 @@ func (s *ErrorTestSuite) TestErrorLongMessage(c *C) { for i := 0; i < 1000; i++ { longMessage += "This is a very long error message. " } - + err := Error{Error: longMessage} c.Check(err.Error, Equals, longMessage) - + // Test JSON marshaling/unmarshaling with long message jsonData, marshalErr := json.Marshal(err) c.Check(marshalErr, IsNil) - + var unmarshaled Error unmarshalErr := json.Unmarshal(jsonData, &unmarshaled) c.Check(unmarshalErr, IsNil) @@ -100,20 +99,20 @@ func (s *ErrorTestSuite) TestErrorLongMessage(c *C) { func (s *ErrorTestSuite) TestErrorJSONFieldName(c *C) { // Test that the JSON field name is exactly "error" err := Error{Error: "test"} - + jsonData, marshalErr := json.Marshal(err) c.Check(marshalErr, IsNil) - + // Parse as generic map to check field name var result map[string]interface{} unmarshalErr := json.Unmarshal(jsonData, &result) c.Check(unmarshalErr, IsNil) - + // Check that the field is named "error" value, exists := result["error"] c.Check(exists, Equals, true) c.Check(value, Equals, "test") - + // Check that no other fields exist c.Check(len(result), Equals, 1) } @@ -121,7 +120,7 @@ func (s *ErrorTestSuite) TestErrorJSONFieldName(c *C) { func (s *ErrorTestSuite) TestErrorJSONWithExtraFields(c *C) { // Test unmarshaling JSON with extra fields (should be ignored) jsonData := `{"error":"test error","extra":"ignored","number":123}` - + var err Error unmarshalErr := json.Unmarshal([]byte(jsonData), &err) c.Check(unmarshalErr, IsNil) @@ -131,7 +130,7 @@ func (s *ErrorTestSuite) TestErrorJSONWithExtraFields(c *C) { func (s *ErrorTestSuite) TestErrorJSONMissingField(c *C) { // Test unmarshaling JSON missing the error field jsonData := `{"other":"value"}` - + var err Error unmarshalErr := json.Unmarshal([]byte(jsonData), &err) c.Check(unmarshalErr, IsNil) @@ -151,11 +150,11 @@ func (s *ErrorTestSuite) TestErrorJSONInvalidJSON(c *C) { `[]`, `123`, } - + for i, jsonData := range invalidJSONs { var err Error unmarshalErr := json.Unmarshal([]byte(jsonData), &err) - + // Should either error or handle gracefully if unmarshalErr == nil { // If no error, check the result is reasonable @@ -171,7 +170,7 @@ func (s *ErrorTestSuite) TestErrorZeroValue(c *C) { // Test zero value of Error struct var err Error c.Check(err.Error, Equals, "") - + // Test JSON marshaling of zero value jsonData, marshalErr := json.Marshal(err) c.Check(marshalErr, IsNil) @@ -182,12 +181,12 @@ func (s *ErrorTestSuite) TestErrorPointer(c *C) { // Test Error struct as pointer err := &Error{Error: "pointer error"} c.Check(err.Error, Equals, "pointer error") - + // Test JSON marshaling of pointer jsonData, marshalErr := json.Marshal(err) c.Check(marshalErr, IsNil) c.Check(string(jsonData), Equals, `{"error":"pointer error"}`) - + // Test JSON unmarshaling into pointer var err2 *Error unmarshalErr := json.Unmarshal(jsonData, &err2) @@ -200,9 +199,9 @@ func (s *ErrorTestSuite) TestErrorStructCopy(c *C) { // Test copying Error struct err1 := Error{Error: "original error"} err2 := err1 - + c.Check(err2.Error, Equals, "original error") - + // Modify original and ensure copy is independent err1.Error = "modified error" c.Check(err1.Error, Equals, "modified error") @@ -214,7 +213,7 @@ func (s *ErrorTestSuite) TestErrorStructComparison(c *C) { err1 := Error{Error: "same message"} err2 := Error{Error: "same message"} err3 := Error{Error: "different message"} - + c.Check(err1 == err2, Equals, true) c.Check(err1 == err3, Equals, false) c.Check(err2 == err3, Equals, false) @@ -227,16 +226,16 @@ func (s *ErrorTestSuite) TestErrorStructInSlice(c *C) { {Error: "second error"}, {Error: "third error"}, } - + c.Check(len(errors), Equals, 3) c.Check(errors[0].Error, Equals, "first error") c.Check(errors[1].Error, Equals, "second error") c.Check(errors[2].Error, Equals, "third error") - + // Test JSON marshaling of slice jsonData, marshalErr := json.Marshal(errors) c.Check(marshalErr, IsNil) - + var unmarshaled []Error unmarshalErr := json.Unmarshal(jsonData, &unmarshaled) c.Check(unmarshalErr, IsNil) @@ -250,19 +249,19 @@ func (s *ErrorTestSuite) TestErrorStructInMap(c *C) { "key1": {Error: "first error"}, "key2": {Error: "second error"}, } - + c.Check(len(errorMap), Equals, 2) c.Check(errorMap["key1"].Error, Equals, "first error") c.Check(errorMap["key2"].Error, Equals, "second error") - + // Test JSON marshaling of map jsonData, marshalErr := json.Marshal(errorMap) c.Check(marshalErr, IsNil) - + var unmarshaled map[string]Error unmarshalErr := json.Unmarshal(jsonData, &unmarshaled) c.Check(unmarshalErr, IsNil) c.Check(len(unmarshaled), Equals, 2) c.Check(unmarshaled["key1"].Error, Equals, "first error") c.Check(unmarshaled["key2"].Error, Equals, "second error") -} \ No newline at end of file +} diff --git a/api/files_test.go b/api/files_test.go index a0e7f341..c9b2bf6d 100644 --- a/api/files_test.go +++ b/api/files_test.go @@ -260,7 +260,7 @@ func (s *FilesSuite) TestTruthy(c *C) { c.Check(truthy("false"), Equals, false) c.Check(truthy("0"), Equals, false) c.Check(truthy("off"), Equals, false) - c.Check(truthy("NO"), Equals, false) // case insensitive + c.Check(truthy("NO"), Equals, false) // case insensitive c.Check(truthy("FALSE"), Equals, false) // case insensitive // Test int values @@ -275,4 +275,4 @@ func (s *FilesSuite) TestTruthy(c *C) { // Test nil c.Check(truthy(nil), Equals, false) -} \ No newline at end of file +} diff --git a/api/gpg_test.go b/api/gpg_test.go index 30c6d833..fe83f012 100644 --- a/api/gpg_test.go +++ b/api/gpg_test.go @@ -5,7 +5,6 @@ import ( "net/http" "net/http/httptest" - "github.com/gin-gonic/gin" . "gopkg.in/check.v1" ) @@ -211,4 +210,4 @@ func min(a, b int) int { return a } return b -} \ No newline at end of file +} diff --git a/api/graph_test.go b/api/graph_test.go index a205058d..41ab0495 100644 --- a/api/graph_test.go +++ b/api/graph_test.go @@ -6,7 +6,6 @@ import ( "net/http/httptest" "strings" - "github.com/gin-gonic/gin" . "gopkg.in/check.v1" ) @@ -155,7 +154,7 @@ func (s *GraphTestSuite) TestGraphMimeTypeHandling(c *C) { for ext, expectedMime := range extensions { actualMime := mime.TypeByExtension("." + ext) if actualMime != "" { - c.Check(actualMime, Matches, expectedMime+".*", + c.Check(actualMime, Matches, expectedMime+".*", Commentf("MIME type mismatch for extension: %s", ext)) } } @@ -305,12 +304,12 @@ func (s *GraphTestSuite) TestGraphContentTypeHeaders(c *C) { s.router.ServeHTTP(w, req) contentType := w.Header().Get("Content-Type") - + if test.expectJSON { c.Check(strings.Contains(contentType, "application/json"), Equals, true, Commentf("Expected JSON content type for .%s, got: %s", test.ext, contentType)) } - + // Note: Image content types will only be set if graphviz is available and context exists c.Check(contentType, Not(Equals), "", Commentf("Content type should be set for .%s", test.ext)) } @@ -381,4 +380,4 @@ func (s *GraphTestSuite) TestGraphConcurrency(c *C) { } c.Check(true, Equals, true) // Test completed without deadlocks -} \ No newline at end of file +} diff --git a/api/metrics_test.go b/api/metrics_test.go index 9ea43512..473f7542 100644 --- a/api/metrics_test.go +++ b/api/metrics_test.go @@ -6,7 +6,6 @@ import ( "runtime" "strings" - "github.com/aptly-dev/aptly/aptly" "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus" @@ -23,7 +22,7 @@ var _ = Suite(&MetricsTestSuite{}) func (s *MetricsTestSuite) SetUpTest(c *C) { // Reset metrics registrar state for each test MetricsCollectorRegistrar.hasRegistered = false - + // Create new router for testing s.router = gin.New() gin.SetMode(gin.TestMode) @@ -32,11 +31,11 @@ func (s *MetricsTestSuite) SetUpTest(c *C) { func (s *MetricsTestSuite) TestMetricsCollectorRegistrarRegisterOnce(c *C) { // Test that metrics are only registered once registrar := &metricsCollectorRegistrar{hasRegistered: false} - + // First registration should work registrar.Register(s.router) c.Check(registrar.hasRegistered, Equals, true) - + // Second registration should be skipped registrar.Register(s.router) c.Check(registrar.hasRegistered, Equals, true) @@ -45,19 +44,19 @@ func (s *MetricsTestSuite) TestMetricsCollectorRegistrarRegisterOnce(c *C) { func (s *MetricsTestSuite) TestMetricsCollectorRegistrarVersionGauge(c *C) { // Test that version gauge is set correctly registrar := &metricsCollectorRegistrar{hasRegistered: false} - + // Register metrics registrar.Register(s.router) - + // Check that version gauge was set expectedLabels := prometheus.Labels{ "version": aptly.Version, "goversion": runtime.Version(), } - + gauge := apiVersionGauge.With(expectedLabels) c.Check(gauge, NotNil) - + // Verify the gauge value is 1 metric := &dto.Metric{} gauge.(prometheus.Gauge).Write(metric) @@ -67,11 +66,11 @@ func (s *MetricsTestSuite) TestMetricsCollectorRegistrarVersionGauge(c *C) { func (s *MetricsTestSuite) TestApiRequestsInFlightGauge(c *C) { // Test that in-flight requests gauge works c.Check(apiRequestsInFlightGauge, NotNil) - + // Test that we can create labels for the gauge gauge := apiRequestsInFlightGauge.WithLabelValues("GET", "/api/test") c.Check(gauge, NotNil) - + // Test incrementing and decrementing gauge.Inc() gauge.Dec() @@ -80,11 +79,11 @@ func (s *MetricsTestSuite) TestApiRequestsInFlightGauge(c *C) { func (s *MetricsTestSuite) TestApiRequestsTotalCounter(c *C) { // Test that total requests counter works c.Check(apiRequestsTotalCounter, NotNil) - + // Test that we can create labels for the counter counter := apiRequestsTotalCounter.WithLabelValues("200", "GET", "/api/test") c.Check(counter, NotNil) - + // Test incrementing counter.Inc() } @@ -92,11 +91,11 @@ func (s *MetricsTestSuite) TestApiRequestsTotalCounter(c *C) { func (s *MetricsTestSuite) TestApiRequestSizeSummary(c *C) { // Test that request size summary works c.Check(apiRequestSizeSummary, NotNil) - + // Test that we can create labels for the summary summary := apiRequestSizeSummary.WithLabelValues("200", "POST", "/api/test") c.Check(summary, NotNil) - + // Test observing values summary.Observe(1024.0) summary.Observe(512.0) @@ -105,11 +104,11 @@ func (s *MetricsTestSuite) TestApiRequestSizeSummary(c *C) { func (s *MetricsTestSuite) TestApiResponseSizeSummary(c *C) { // Test that response size summary works c.Check(apiResponseSizeSummary, NotNil) - + // Test that we can create labels for the summary summary := apiResponseSizeSummary.WithLabelValues("200", "GET", "/api/test") c.Check(summary, NotNil) - + // Test observing values summary.Observe(2048.0) summary.Observe(1024.0) @@ -118,25 +117,25 @@ func (s *MetricsTestSuite) TestApiResponseSizeSummary(c *C) { func (s *MetricsTestSuite) TestApiRequestsDurationSummary(c *C) { // Test that request duration summary works c.Check(apiRequestsDurationSummary, NotNil) - + // Test that we can create labels for the summary summary := apiRequestsDurationSummary.WithLabelValues("200", "GET", "/api/test") c.Check(summary, NotNil) - + // Test observing duration values - summary.Observe(0.1) // 100ms - summary.Observe(0.05) // 50ms - summary.Observe(1.0) // 1s + summary.Observe(0.1) // 100ms + summary.Observe(0.05) // 50ms + summary.Observe(1.0) // 1s } func (s *MetricsTestSuite) TestApiFilesUploadedCounter(c *C) { // Test that files uploaded counter works c.Check(apiFilesUploadedCounter, NotNil) - + // Test that we can create labels for the counter counter := apiFilesUploadedCounter.WithLabelValues("uploads") c.Check(counter, NotNil) - + // Test incrementing counter.Inc() counter.Add(5) @@ -145,11 +144,11 @@ func (s *MetricsTestSuite) TestApiFilesUploadedCounter(c *C) { func (s *MetricsTestSuite) TestApiReposPackageCountGauge(c *C) { // Test that repos package count gauge works c.Check(apiReposPackageCountGauge, NotNil) - + // Test that we can create labels for the gauge gauge := apiReposPackageCountGauge.WithLabelValues("source", "stable", "main") c.Check(gauge, NotNil) - + // Test setting values gauge.Set(100) gauge.Set(150) @@ -159,11 +158,11 @@ func (s *MetricsTestSuite) TestApiReposPackageCountGauge(c *C) { func (s *MetricsTestSuite) TestMetricsPrometheusIntegration(c *C) { // Test integration with Prometheus client library - + // Test that metrics are properly registered with default registry metricNames := []string{ "aptly_api_http_requests_in_flight", - "aptly_api_http_requests_total", + "aptly_api_http_requests_total", "aptly_api_http_request_size_bytes", "aptly_api_http_response_size_bytes", "aptly_api_http_request_duration_seconds", @@ -171,12 +170,12 @@ func (s *MetricsTestSuite) TestMetricsPrometheusIntegration(c *C) { "aptly_api_files_uploaded_total", "aptly_repos_package_count", } - + for _, metricName := range metricNames { // Try to gather metrics to ensure they're registered gathered, err := prometheus.DefaultGatherer.Gather() c.Check(err, IsNil) - + found := false for _, metricFamily := range gathered { if metricFamily.GetName() == metricName { @@ -190,35 +189,35 @@ func (s *MetricsTestSuite) TestMetricsPrometheusIntegration(c *C) { func (s *MetricsTestSuite) TestMetricsLabels(c *C) { // Test that metrics have expected labels - + // Test in-flight gauge labels gauge := apiRequestsInFlightGauge.WithLabelValues("GET", "/api/test") c.Check(gauge, NotNil) - + // Test total counter labels counter := apiRequestsTotalCounter.WithLabelValues("200", "GET", "/api/test") c.Check(counter, NotNil) - + // Test request size summary labels requestSummary := apiRequestSizeSummary.WithLabelValues("200", "POST", "/api/upload") c.Check(requestSummary, NotNil) - + // Test response size summary labels responseSummary := apiResponseSizeSummary.WithLabelValues("404", "GET", "/api/missing") c.Check(responseSummary, NotNil) - + // Test duration summary labels durationSummary := apiRequestsDurationSummary.WithLabelValues("500", "POST", "/api/error") c.Check(durationSummary, NotNil) - + // Test version gauge labels versionGauge := apiVersionGauge.WithLabelValues("1.0.0", "go1.19") c.Check(versionGauge, NotNil) - + // Test files uploaded counter labels filesCounter := apiFilesUploadedCounter.WithLabelValues("temp-uploads") c.Check(filesCounter, NotNil) - + // Test repos package count gauge labels reposGauge := apiReposPackageCountGauge.WithLabelValues("snapshot:test", "testing", "contrib") c.Check(reposGauge, NotNil) @@ -227,18 +226,18 @@ func (s *MetricsTestSuite) TestMetricsLabels(c *C) { func (s *MetricsTestSuite) TestMetricsWithDifferentHTTPCodes(c *C) { // Test metrics with various HTTP status codes httpCodes := []string{"200", "201", "400", "401", "403", "404", "409", "500", "502", "503"} - + for _, code := range httpCodes { // Test that metrics work with different status codes counter := apiRequestsTotalCounter.WithLabelValues(code, "GET", "/api/test") counter.Inc() - + requestSummary := apiRequestSizeSummary.WithLabelValues(code, "POST", "/api/test") requestSummary.Observe(100) - + responseSummary := apiResponseSizeSummary.WithLabelValues(code, "GET", "/api/test") responseSummary.Observe(200) - + durationSummary := apiRequestsDurationSummary.WithLabelValues(code, "PUT", "/api/test") durationSummary.Observe(0.1) } @@ -247,13 +246,13 @@ func (s *MetricsTestSuite) TestMetricsWithDifferentHTTPCodes(c *C) { func (s *MetricsTestSuite) TestMetricsWithDifferentHTTPMethods(c *C) { // Test metrics with various HTTP methods httpMethods := []string{"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"} - + for _, method := range httpMethods { // Test that metrics work with different HTTP methods gauge := apiRequestsInFlightGauge.WithLabelValues(method, "/api/test") gauge.Inc() gauge.Dec() - + counter := apiRequestsTotalCounter.WithLabelValues("200", method, "/api/test") counter.Inc() } @@ -272,11 +271,11 @@ func (s *MetricsTestSuite) TestMetricsWithDifferentPaths(c *C) { "/api/tasks", "/api/version", } - + for _, path := range apiPaths { counter := apiRequestsTotalCounter.WithLabelValues("200", "GET", path) counter.Inc() - + gauge := apiRequestsInFlightGauge.WithLabelValues("GET", path) gauge.Inc() gauge.Dec() @@ -286,42 +285,42 @@ func (s *MetricsTestSuite) TestMetricsWithDifferentPaths(c *C) { func (s *MetricsTestSuite) TestMetricsThreadSafety(c *C) { // Test that metrics are thread-safe by simulating concurrent access done := make(chan bool, 10) - + for i := 0; i < 10; i++ { go func(id int) { defer func() { done <- true }() - + // Simulate concurrent metric updates for j := 0; j < 100; j++ { counter := apiRequestsTotalCounter.WithLabelValues("200", "GET", "/api/concurrent") counter.Inc() - - gauge := apiRequestsInFlightGauge.WithLabelValues("GET", "/api/concurrent") + + gauge := apiRequestsInFlightGauge.WithLabelValues("GET", "/api/concurrent") gauge.Inc() gauge.Dec() - + summary := apiRequestsDurationSummary.WithLabelValues("200", "GET", "/api/concurrent") summary.Observe(0.01) } }(i) } - + // Wait for all goroutines to complete for i := 0; i < 10; i++ { <-done } - + // Verify metrics were updated (exact count doesn't matter due to concurrency) c.Check(true, Equals, true) // Test completed without race conditions } func (s *MetricsTestSuite) TestMetricsMetadata(c *C) { // Test that metrics have proper metadata (help text, names) - + // Gather all metrics gathered, err := prometheus.DefaultGatherer.Gather() c.Check(err, IsNil) - + expectedMetrics := map[string]string{ "aptly_api_http_requests_in_flight": "Number of concurrent HTTP api requests currently handled.", "aptly_api_http_requests_total": "Total number of api requests.", @@ -332,11 +331,11 @@ func (s *MetricsTestSuite) TestMetricsMetadata(c *C) { "aptly_api_files_uploaded_total": "Total number of uploaded files labeled by upload directory.", "aptly_repos_package_count": "Current number of published packages labeled by source, distribution and component.", } - + for _, metricFamily := range gathered { metricName := metricFamily.GetName() if expectedHelp, exists := expectedMetrics[metricName]; exists { - c.Check(metricFamily.GetHelp(), Equals, expectedHelp, + c.Check(metricFamily.GetHelp(), Equals, expectedHelp, Commentf("Help text mismatch for metric: %s", metricName)) } } @@ -346,16 +345,16 @@ func (s *MetricsTestSuite) TestCountPackagesByRepos(c *C) { // Test countPackagesByRepos function structure // Note: This function requires database context which we don't have in tests, // but we can test that it doesn't crash when called - + // This will likely error due to no context, but should not panic defer func() { if r := recover(); r != nil { c.Fatalf("countPackagesByRepos panicked: %v", r) } }() - + countPackagesByRepos() - + // If we get here, the function didn't panic c.Check(true, Equals, true) } @@ -363,34 +362,34 @@ func (s *MetricsTestSuite) TestCountPackagesByRepos(c *C) { func (s *MetricsTestSuite) TestMetricsRegistration(c *C) { // Test that metrics registration works correctly with gin router MetricsCollectorRegistrar.Register(s.router) - + // Create a test request to trigger middleware req, _ := http.NewRequest("GET", "/test", nil) w := httptest.NewRecorder() - + // Add a test handler s.router.GET("/test", func(c *gin.Context) { c.JSON(200, gin.H{"test": "response"}) }) - + s.router.ServeHTTP(w, req) - + c.Check(w.Code, Equals, 200) c.Check(MetricsCollectorRegistrar.hasRegistered, Equals, true) } func (s *MetricsTestSuite) TestMetricsErrorConditions(c *C) { // Test error handling in metrics collection - + // Test with invalid label values (should not crash) invalidLabels := []string{"", "very_long_label_" + strings.Repeat("x", 1000), "label\nwith\nnewlines"} - + for _, label := range invalidLabels { // These should not crash, even with invalid labels gauge := apiRequestsInFlightGauge.WithLabelValues("GET", label) gauge.Inc() gauge.Dec() - + counter := apiRequestsTotalCounter.WithLabelValues("200", "GET", label) counter.Inc() } @@ -398,21 +397,21 @@ func (s *MetricsTestSuite) TestMetricsErrorConditions(c *C) { func (s *MetricsTestSuite) TestMetricsValueRanges(c *C) { // Test metrics with various value ranges - + // Test large values summary := apiRequestSizeSummary.WithLabelValues("200", "POST", "/api/large") summary.Observe(1e9) // 1GB summary.Observe(1e12) // 1TB - + // Test very small values durationSummary := apiRequestsDurationSummary.WithLabelValues("200", "GET", "/api/fast") durationSummary.Observe(1e-9) // 1 nanosecond durationSummary.Observe(1e-6) // 1 microsecond - + // Test zero values gauge := apiReposPackageCountGauge.WithLabelValues("empty", "dist", "comp") gauge.Set(0) - + // Test negative values (should be handled gracefully) gauge.Set(-1) // May or may not be allowed by Prometheus, but shouldn't crash } @@ -426,13 +425,13 @@ func (s *MetricsTestSuite) TestMetricsWithSpecialCharacters(c *C) { "/api/repos/repo+with+plus", "/api/repos/repo%20with%20encoded", } - + for _, path := range specialPaths { counter := apiRequestsTotalCounter.WithLabelValues("200", "GET", path) counter.Inc() - + gauge := apiRequestsInFlightGauge.WithLabelValues("GET", path) gauge.Inc() gauge.Dec() } -} \ No newline at end of file +} diff --git a/api/publish_test.go b/api/publish_test.go index f9670582..78e22425 100644 --- a/api/publish_test.go +++ b/api/publish_test.go @@ -384,10 +384,10 @@ func (s *PublishAPITestSuite) TestSigningParamsEdgeCases(c *C) { // Test signingParams with edge cases testCases := []signingParams{ {Skip: true}, // Minimal case - {Skip: false, GpgKey: "", Keyring: "", SecretKeyring: "", Passphrase: "", PassphraseFile: ""}, // Empty strings + {Skip: false, GpgKey: "", Keyring: "", SecretKeyring: "", Passphrase: "", PassphraseFile: ""}, // Empty strings {Skip: false, GpgKey: "very-long-key-id-123456789012345678901234567890", Keyring: "very-long-keyring-name.gpg"}, // Long values - {Skip: false, Passphrase: "password with spaces and special chars !@#$%^&*()"}, // Special characters - {Skip: false, PassphraseFile: "/very/long/path/to/passphrase/file/that/might/not/exist.txt"}, // Long file path + {Skip: false, Passphrase: "password with spaces and special chars !@#$%^&*()"}, // Special characters + {Skip: false, PassphraseFile: "/very/long/path/to/passphrase/file/that/might/not/exist.txt"}, // Long file path } for i, params := range testCases { @@ -409,9 +409,9 @@ func (s *PublishAPITestSuite) TestSourceParamsEdgeCases(c *C) { testCases := []sourceParams{ {Component: "", Name: ""}, // Empty strings {Component: "very-long-component-name-with-dashes-and-numbers-123", Name: "very-long-name-456"}, // Long values - {Component: "comp.with.dots", Name: "name_with_underscores"}, // Special characters - {Component: "UPPERCASE", Name: "MixedCase"}, // Case variations - {Component: "123numeric", Name: "456numbers"}, // Numeric values + {Component: "comp.with.dots", Name: "name_with_underscores"}, // Special characters + {Component: "UPPERCASE", Name: "MixedCase"}, // Case variations + {Component: "123numeric", Name: "456numbers"}, // Numeric values } for i, params := range testCases { @@ -460,20 +460,26 @@ func (s *PublishAPITestSuite) TestSlashEscapeComprehensive(c *C) { // Mock implementations for testing context dependencies type MockSigner struct { - initError error - key string - keyring string - secretKeyring string - passphrase string + initError error + key string + keyring string + secretKeyring string + passphrase string passphraseFile string - batch bool + batch bool } -func (m *MockSigner) SetKey(key string) { m.key = key } -func (m *MockSigner) SetKeyRing(keyring, secretKeyring string) { m.keyring = keyring; m.secretKeyring = secretKeyring } -func (m *MockSigner) SetPassphrase(passphrase, passphraseFile string) { m.passphrase = passphrase; m.passphraseFile = passphraseFile } -func (m *MockSigner) SetBatch(batch bool) { m.batch = batch } -func (m *MockSigner) Init() error { return m.initError } +func (m *MockSigner) SetKey(key string) { m.key = key } +func (m *MockSigner) SetKeyRing(keyring, secretKeyring string) { + m.keyring = keyring + m.secretKeyring = secretKeyring +} +func (m *MockSigner) SetPassphrase(passphrase, passphraseFile string) { + m.passphrase = passphrase + m.passphraseFile = passphraseFile +} +func (m *MockSigner) SetBatch(batch bool) { m.batch = batch } +func (m *MockSigner) Init() error { return m.initError } func (s *PublishAPITestSuite) TestGetSignerMockSuccess(c *C) { // Test getSigner logic with mock (can't test actual getSigner due to context dependencies) @@ -488,7 +494,7 @@ func (s *PublishAPITestSuite) TestGetSignerMockSuccess(c *C) { // Mock the signer behavior mockSigner := &MockSigner{initError: nil} - + // Simulate what getSigner would do mockSigner.SetKey(options.GpgKey) mockSigner.SetKeyRing(options.Keyring, options.SecretKeyring) @@ -514,7 +520,7 @@ func (s *PublishAPITestSuite) TestGetSignerMockError(c *C) { // Mock the signer behavior with error mockSigner := &MockSigner{initError: fmt.Errorf("mock init error")} - + mockSigner.SetKey(options.GpgKey) mockSigner.SetKeyRing(options.Keyring, options.SecretKeyring) mockSigner.SetPassphrase(options.Passphrase, options.PassphraseFile) @@ -523,4 +529,4 @@ func (s *PublishAPITestSuite) TestGetSignerMockError(c *C) { c.Check(err, NotNil) c.Check(err.Error(), Equals, "mock init error") -} \ No newline at end of file +} diff --git a/api/repos.go b/api/repos.go index 0ced5efd..17b8a454 100644 --- a/api/repos.go +++ b/api/repos.go @@ -901,10 +901,10 @@ func apiReposIncludePackageFromDir(c *gin.Context) { out.Printf("Failed files: %s\n", strings.Join(failedFiles, ", ")) } - ret := reposIncludePackageFromDirResponse{ + ret := reposIncludePackageFromDirResponse{ Report: reporter, FailedFiles: failedFiles, - } + } return &task.ProcessReturnValue{Code: http.StatusOK, Value: ret}, nil }) } diff --git a/api/repos_test.go b/api/repos_test.go index a6d1e6dd..f0e7320c 100644 --- a/api/repos_test.go +++ b/api/repos_test.go @@ -8,7 +8,6 @@ import ( "net/http/httptest" "strings" - "github.com/aptly-dev/aptly/deb" "github.com/aptly-dev/aptly/utils" "github.com/gin-gonic/gin" @@ -479,8 +478,8 @@ func (s *ReposTestSuite) TestReposErrorHandling(c *C) { {"Missing required fields", "POST", "/api/repos", `{}`, true}, {"Invalid package refs", "POST", "/api/repos/test/packages", `{"PackageRefs":[]}`, true}, {"Invalid query format", "GET", "/api/repos/test/packages?q=invalid[query", "", false}, // Query validation happens deeper - {"Copy to same repo", "POST", "/api/repos/test/copy/test/pkg", `{}`, false}, // Error happens in business logic - {"Empty directory path", "POST", "/api/repos/test/file/", "", false}, // Path handling + {"Copy to same repo", "POST", "/api/repos/test/copy/test/pkg", `{}`, false}, // Error happens in business logic + {"Empty directory path", "POST", "/api/repos/test/file/", "", false}, // Path handling } for _, test := range errorTests { @@ -498,4 +497,4 @@ func (s *ReposTestSuite) TestReposErrorHandling(c *C) { // All should return some response without crashing c.Check(w.Code, Not(Equals), 0, Commentf("Test: %s", test.description)) } -} \ No newline at end of file +} diff --git a/api/router.go b/api/router.go index 3cd7d427..8e9544f5 100644 --- a/api/router.go +++ b/api/router.go @@ -77,7 +77,7 @@ func Router(c *ctx.AptlyContext) http.Handler { } if c.Config().ServeInAPIMode { - router.GET("/repos/", reposListInAPIMode(c.Config().FileSystemPublishRoots)) + router.GET("/repos/", reposListInAPIMode(c.Config().GetFileSystemPublishRoots())) router.GET("/repos/:storage/*pkgPath", reposServeInAPIMode) } @@ -86,25 +86,17 @@ func Router(c *ctx.AptlyContext) http.Handler { // We use a goroutine to count the number of // concurrent requests. When no more requests are // running, we close the database to free the lock. - dbRequests = make(chan dbRequest) - - go acquireDatabase() + initDBRequests() api.Use(func(c *gin.Context) { - var err error - - errCh := make(chan error) - dbRequests <- dbRequest{acquiredb, errCh} - - err = <-errCh + err := acquireDatabaseConnection() if err != nil { AbortWithJSONError(c, 500, err) return } defer func() { - dbRequests <- dbRequest{releasedb, errCh} - err = <-errCh + err := releaseDatabaseConnection() if err != nil { AbortWithJSONError(c, 500, err) } diff --git a/api/s3.go b/api/s3.go index f38b0847..e5ebda29 100644 --- a/api/s3.go +++ b/api/s3.go @@ -14,7 +14,9 @@ import ( // @Router /api/s3 [get] func apiS3List(c *gin.Context) { keys := []string{} - for k := range context.Config().S3PublishRoots { + // Use safe accessor to get a copy of the map + s3Roots := context.Config().GetS3PublishRoots() + for k := range s3Roots { keys = append(keys, k) } c.JSON(200, keys) diff --git a/api/snapshot_test.go b/api/snapshot_test.go index 4fb201a0..1500b838 100644 --- a/api/snapshot_test.go +++ b/api/snapshot_test.go @@ -7,7 +7,6 @@ import ( "net/http/httptest" "strings" - "github.com/gin-gonic/gin" . "gopkg.in/check.v1" ) @@ -21,7 +20,7 @@ var _ = Suite(&SnapshotAPITestSuite{}) func (s *SnapshotAPITestSuite) SetUpTest(c *C) { s.router = gin.New() gin.SetMode(gin.TestMode) - + // Set up API routes s.router.GET("/api/snapshots", apiSnapshotsList) s.router.POST("/api/snapshots", apiSnapshotsCreate) @@ -33,7 +32,7 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsListGet(c *C) { req, _ := http.NewRequest("GET", "/api/snapshots", nil) w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + // Should handle the request without crashing (will likely error due to no context) c.Check(w.Code, Not(Equals), 0) } @@ -43,19 +42,19 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsListWithSort(c *C) { req, _ := http.NewRequest("GET", "/api/snapshots?sort=name", nil) w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Not(Equals), 0) } func (s *SnapshotAPITestSuite) TestApiSnapshotsListWithDifferentSorts(c *C) { // Test various sort methods sortMethods := []string{"name", "time", "created"} - + for _, sortMethod := range sortMethods { req, _ := http.NewRequest("GET", "/api/snapshots?sort="+sortMethod, nil) w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Not(Equals), 0, Commentf("Sort method: %s", sortMethod)) } } @@ -68,14 +67,14 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsCreatePost(c *C) { SourceSnapshots: []string{"source1"}, PackageRefs: []string{}, } - + jsonBody, _ := json.Marshal(requestBody) req, _ := http.NewRequest("POST", "/api/snapshots", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") - + w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + // Should handle the request without crashing (will likely error due to no context) c.Check(w.Code, Not(Equals), 0) } @@ -84,10 +83,10 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsCreateInvalidJSON(c *C) { // Test POST with invalid JSON req, _ := http.NewRequest("POST", "/api/snapshots", strings.NewReader("invalid json")) req.Header.Set("Content-Type", "application/json") - + w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Equals, 400) // Should return bad request for invalid JSON } @@ -96,14 +95,14 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsCreateMissingName(c *C) { requestBody := map[string]interface{}{ "Description": "Test without name", } - + jsonBody, _ := json.Marshal(requestBody) req, _ := http.NewRequest("POST", "/api/snapshots", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") - + w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Equals, 400) // Should return bad request for missing name } @@ -113,14 +112,14 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsCreateFromMirrorPost(c *C) { Name: "mirror-snapshot", Description: "Snapshot from mirror", } - + jsonBody, _ := json.Marshal(requestBody) req, _ := http.NewRequest("POST", "/api/mirrors/test-mirror/snapshots", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") - + w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + // Should handle the request without crashing (will likely error due to no context) c.Check(w.Code, Not(Equals), 0) } @@ -129,10 +128,10 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsCreateFromMirrorInvalidJSON(c *C) // Test POST with invalid JSON for mirror snapshot req, _ := http.NewRequest("POST", "/api/mirrors/test-mirror/snapshots", strings.NewReader("invalid json")) req.Header.Set("Content-Type", "application/json") - + w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Equals, 400) // Should return bad request for invalid JSON } @@ -141,14 +140,14 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsCreateFromMirrorMissingName(c *C) requestBody := map[string]interface{}{ "Description": "Mirror snapshot without name", } - + jsonBody, _ := json.Marshal(requestBody) req, _ := http.NewRequest("POST", "/api/mirrors/test-mirror/snapshots", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") - + w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Equals, 400) // Should return bad request for missing name } @@ -158,14 +157,14 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsCreateWithAsync(c *C) { Name: "async-snapshot", Description: "Async test snapshot", } - + jsonBody, _ := json.Marshal(requestBody) req, _ := http.NewRequest("POST", "/api/snapshots?_async=true", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") - + w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Not(Equals), 0) } @@ -175,14 +174,14 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsCreateFromMirrorWithAsync(c *C) { Name: "async-mirror-snapshot", Description: "Async mirror snapshot", } - + jsonBody, _ := json.Marshal(requestBody) req, _ := http.NewRequest("POST", "/api/mirrors/test-mirror/snapshots?_async=true", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") - + w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Not(Equals), 0) } @@ -194,7 +193,7 @@ func (s *SnapshotAPITestSuite) TestSnapshotsCreateParamsStruct(c *C) { SourceSnapshots: []string{"snap1", "snap2"}, PackageRefs: []string{"ref1", "ref2"}, } - + c.Check(params.Name, Equals, "test-name") c.Check(params.Description, Equals, "test-description") c.Check(params.SourceSnapshots, DeepEquals, []string{"snap1", "snap2"}) @@ -207,7 +206,7 @@ func (s *SnapshotAPITestSuite) TestSnapshotsCreateFromMirrorParamsStruct(c *C) { Name: "mirror-test-name", Description: "mirror-test-description", } - + c.Check(params.Name, Equals, "mirror-test-name") c.Check(params.Description, Equals, "mirror-test-description") } @@ -216,10 +215,10 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsCreateEmptyRequest(c *C) { // Test POST with empty request body req, _ := http.NewRequest("POST", "/api/snapshots", strings.NewReader("")) req.Header.Set("Content-Type", "application/json") - + w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Equals, 400) // Should return bad request for empty body } @@ -227,10 +226,10 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsCreateFromMirrorEmptyRequest(c *C // Test POST mirror snapshot with empty request body req, _ := http.NewRequest("POST", "/api/mirrors/test-mirror/snapshots", strings.NewReader("")) req.Header.Set("Content-Type", "application/json") - + w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Equals, 400) // Should return bad request for empty body } @@ -239,7 +238,7 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsListDefaultSort(c *C) { req, _ := http.NewRequest("GET", "/api/snapshots", nil) w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + // Endpoint should handle default sort without issues c.Check(w.Code, Not(Equals), 0) } @@ -252,27 +251,27 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsCreateComplexPayload(c *C) { SourceSnapshots: []string{"base-snapshot", "updates-snapshot", "security-snapshot"}, PackageRefs: []string{"pkg1_1.0_amd64", "pkg2_2.0_i386", "pkg3_3.0_all"}, } - + jsonBody, _ := json.Marshal(requestBody) req, _ := http.NewRequest("POST", "/api/snapshots", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") - + w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Not(Equals), 0) } func (s *SnapshotAPITestSuite) TestApiSnapshotsHTTPMethods(c *C) { // Test that only allowed HTTP methods work - + // Test unsupported methods for snapshots list deniedMethods := []string{"PUT", "DELETE", "PATCH"} for _, method := range deniedMethods { req, _ := http.NewRequest(method, "/api/snapshots", nil) w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Equals, 404, Commentf("Method %s should not be allowed for snapshots list", method)) } } @@ -286,20 +285,20 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsCreateSpecialCharacters(c *C) { "snapshot123", "UPPERCASESNAPSHOT", } - + for _, name := range specialNames { requestBody := snapshotsCreateParams{ Name: name, Description: "Test snapshot with special characters", } - + jsonBody, _ := json.Marshal(requestBody) req, _ := http.NewRequest("POST", "/api/snapshots", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") - + w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Not(Equals), 0, Commentf("Special name test failed: %s", name)) } } @@ -309,7 +308,7 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsListEmptyResponse(c *C) { req, _ := http.NewRequest("GET", "/api/snapshots", nil) w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + // Should return some response (likely error due to no context, but shouldn't crash) c.Check(w.Code, Not(Equals), 0) } @@ -318,45 +317,45 @@ func (s *SnapshotAPITestSuite) TestApiSnapshotsCreateWithoutContentType(c *C) { // Test POST without Content-Type header requestBody := `{"Name": "test-snapshot"}` req, _ := http.NewRequest("POST", "/api/snapshots", strings.NewReader(requestBody)) - + w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + // Should handle missing content type c.Check(w.Code, Not(Equals), 0) } func (s *SnapshotAPITestSuite) TestApiSnapshotsParameterEdgeCases(c *C) { // Test edge cases for parameter validation - + // Test with very long name longName := strings.Repeat("a", 1000) requestBody := snapshotsCreateParams{ Name: longName, } - + jsonBody, _ := json.Marshal(requestBody) req, _ := http.NewRequest("POST", "/api/snapshots", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") - + w := httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Not(Equals), 0) - + // Test with empty arrays emptyArrayBody := snapshotsCreateParams{ Name: "empty-arrays", SourceSnapshots: []string{}, PackageRefs: []string{}, } - + jsonBody, _ = json.Marshal(emptyArrayBody) req, _ = http.NewRequest("POST", "/api/snapshots", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") - + w = httptest.NewRecorder() s.router.ServeHTTP(w, req) - + c.Check(w.Code, Not(Equals), 0) -} \ No newline at end of file +} diff --git a/api/storage_test.go b/api/storage_test.go index 72415843..7d18a73b 100644 --- a/api/storage_test.go +++ b/api/storage_test.go @@ -4,7 +4,6 @@ import ( "net/http" "net/http/httptest" - "github.com/gin-gonic/gin" . "gopkg.in/check.v1" ) @@ -66,11 +65,11 @@ func (s *StorageTestSuite) TestStorageResponseStructure(c *C) { s.router.ServeHTTP(w, req) c.Check(w.Code, Equals, 200) - + // Should have valid JSON response body := w.Body.String() c.Check(len(body), Not(Equals), 0) - + // Should start with valid JSON structure c.Check(body[0], Equals, byte('{'), Commentf("Response should be JSON object")) -} \ No newline at end of file +} diff --git a/api/task_test.go b/api/task_test.go index ea0e49d6..8b91e707 100644 --- a/api/task_test.go +++ b/api/task_test.go @@ -4,7 +4,6 @@ import ( "net/http" "net/http/httptest" - "github.com/gin-gonic/gin" . "gopkg.in/check.v1" ) @@ -352,7 +351,7 @@ func (s *TaskTestSuite) TestTasksContentTypes(c *C) { if test.expectedType != "" { // Check that JSON endpoints return JSON content type contentType := w.Header().Get("Content-Type") - c.Check(contentType, Matches, ".*"+test.expectedType+".*", + c.Check(contentType, Matches, ".*"+test.expectedType+".*", Commentf("Path: %s, Expected: %s, Got: %s", test.path, test.expectedType, contentType)) } } @@ -372,7 +371,7 @@ func (s *TaskTestSuite) TestTasksErrorConditions(c *C) { {"Non-existent task detail", "/api/tasks/999999/detail", "GET", true}, {"Non-existent task return value", "/api/tasks/999999/return_value", "GET", true}, {"Non-existent task delete", "/api/tasks/999999", "DELETE", true}, - {"Malformed task path", "/api/tasks/", "GET", false}, // Route not matched + {"Malformed task path", "/api/tasks/", "GET", false}, // Route not matched {"Extra path segments", "/api/tasks/123/extra/segment", "GET", false}, // Route not matched } @@ -390,12 +389,12 @@ func (s *TaskTestSuite) TestTasksResourceManagement(c *C) { // Test that endpoints handle resource management correctly endpoints := []string{ "/api/tasks", - "/api/tasks-clear", + "/api/tasks-clear", "/api/tasks-wait", "/api/tasks/1", "/api/tasks/1/wait", "/api/tasks/1/output", - "/api/tasks/1/detail", + "/api/tasks/1/detail", "/api/tasks/1/return_value", } @@ -411,8 +410,8 @@ func (s *TaskTestSuite) TestTasksResourceManagement(c *C) { // Should complete without hanging or crashing c.Check(w.Code, Not(Equals), 0, Commentf("Endpoint: %s", endpoint)) - + // Response should have proper headers c.Check(w.Header(), NotNil, Commentf("Endpoint: %s", endpoint)) } -} \ No newline at end of file +} diff --git a/aptly/aptly_test.go b/aptly/aptly_test.go index 50150dc8..24a40905 100644 --- a/aptly/aptly_test.go +++ b/aptly/aptly_test.go @@ -334,44 +334,44 @@ func (m *MockChecksumStorage) Update(path string, c *utils.ChecksumInfo) error { func (s *AptlySuite) TestPackagePoolInterface(c *C) { // Test PackagePool interface with mock implementation var pool PackagePool = &MockPackagePool{} - + checksums := &utils.ChecksumInfo{} mockStorage := &MockChecksumStorage{} - + // Test Verify path, exists, err := pool.Verify("test/path", "package.deb", checksums, mockStorage) c.Check(err, IsNil) c.Check(exists, Equals, true) c.Check(path, Equals, "test/path") - + // Test Import importedPath, err := pool.Import("/src/package.deb", "package.deb", checksums, false, mockStorage) c.Check(err, IsNil) c.Check(importedPath, Equals, "imported/path/package.deb") - + // Test LegacyPath legacyPath, err := pool.LegacyPath("package.deb", checksums) c.Check(err, IsNil) c.Check(legacyPath, Equals, "legacy/package.deb") - + // Test Size size, err := pool.Size("test/path") c.Check(err, IsNil) c.Check(size, Equals, int64(1024)) - + // Test Open reader, err := pool.Open("test/path") c.Check(err, IsNil) c.Check(reader, NotNil) reader.Close() - + // Test FilepathList mockProgress := &MockProgress{} files, err := pool.FilepathList(mockProgress) c.Check(err, IsNil) c.Check(len(files), Equals, 2) c.Check(files[0], Equals, "file1.deb") - + // Test Remove removedSize, err := pool.Remove("test/path") c.Check(err, IsNil) @@ -381,52 +381,52 @@ func (s *AptlySuite) TestPackagePoolInterface(c *C) { func (s *AptlySuite) TestPublishedStorageInterface(c *C) { // Test PublishedStorage interface with mock implementation var storage PublishedStorage = &MockPublishedStorage{} - + // Test MkDir err := storage.MkDir("test/dir") c.Check(err, IsNil) - + // Test PutFile err = storage.PutFile("dest/path", "source/file") c.Check(err, IsNil) - + // Test RemoveDirs mockProgress := &MockProgress{} err = storage.RemoveDirs("test/dir", mockProgress) c.Check(err, IsNil) - + // Test Remove err = storage.Remove("test/file") c.Check(err, IsNil) - + // Test LinkFromPool mockPool := &MockPackagePool{} checksums := utils.ChecksumInfo{} err = storage.LinkFromPool("prefix", "rel/path", "file.deb", mockPool, "pool/path", checksums, false) c.Check(err, IsNil) - + // Test Filelist files, err := storage.Filelist("prefix") c.Check(err, IsNil) c.Check(len(files), Equals, 2) - + // Test RenameFile err = storage.RenameFile("old", "new") c.Check(err, IsNil) - + // Test SymLink err = storage.SymLink("src", "dst") c.Check(err, IsNil) - + // Test HardLink err = storage.HardLink("src", "dst") c.Check(err, IsNil) - + // Test FileExists exists, err := storage.FileExists("test/file") c.Check(err, IsNil) c.Check(exists, Equals, true) - + // Test ReadLink target, err := storage.ReadLink("link") c.Check(err, IsNil) @@ -436,27 +436,27 @@ func (s *AptlySuite) TestPublishedStorageInterface(c *C) { func (s *AptlySuite) TestProgressInterface(c *C) { // Test Progress interface with mock implementation var progress Progress = &MockProgress{} - + // Test Start/Shutdown progress.Start() progress.Shutdown() - + // Test Write n, err := progress.Write([]byte("test")) c.Check(err, IsNil) c.Check(n, Equals, 4) - + // Test progress bar functions progress.InitBar(100, false, BarGeneralBuildPackageList) progress.AddBar(10) progress.SetBar(50) progress.ShutdownBar() - + // Test Printf functions progress.Printf("test %s", "message") progress.ColoredPrintf("colored %s", "message") progress.PrintfStdErr("error %s", "message") - + // Test Flush progress.Flush() } @@ -464,22 +464,22 @@ func (s *AptlySuite) TestProgressInterface(c *C) { func (s *AptlySuite) TestDownloaderInterface(c *C) { // Test Downloader interface with mock implementation var downloader Downloader = &MockDownloader{} - + ctx := context.Background() - + // Test Download err := downloader.Download(ctx, "http://example.com/file", "/tmp/dest") c.Check(err, IsNil) - + // Test DownloadWithChecksum checksums := &utils.ChecksumInfo{} err = downloader.DownloadWithChecksum(ctx, "http://example.com/file", "/tmp/dest", checksums, false) c.Check(err, IsNil) - + // Test GetProgress progress := downloader.GetProgress() c.Check(progress, NotNil) - + // Test GetLength length, err := downloader.GetLength(ctx, "http://example.com/file") c.Check(err, IsNil) @@ -489,12 +489,12 @@ func (s *AptlySuite) TestDownloaderInterface(c *C) { func (s *AptlySuite) TestChecksumStorageInterface(c *C) { // Test ChecksumStorage interface with mock implementation var storage ChecksumStorage = &MockChecksumStorage{} - + // Test Get checksums, err := storage.Get("test/path") c.Check(err, IsNil) c.Check(checksums, NotNil) - + // Test Update newChecksums := &utils.ChecksumInfo{} err = storage.Update("test/path", newChecksums) @@ -505,28 +505,28 @@ func (s *AptlySuite) TestConsoleResultReporter(c *C) { // Test ConsoleResultReporter implementation mockProgress := &MockProgress{} reporter := &ConsoleResultReporter{Progress: mockProgress} - + // Test interface compliance var _ ResultReporter = reporter - + // Test Warning reporter.Warning("test warning %s", "message") output := mockProgress.buffer.String() c.Check(strings.Contains(output, "test warning message"), Equals, true) c.Check(strings.Contains(output, "[!]"), Equals, true) - + // Reset buffer mockProgress.buffer.Reset() - + // Test Removed reporter.Removed("removed %s", "item") output = mockProgress.buffer.String() c.Check(strings.Contains(output, "removed item"), Equals, true) c.Check(strings.Contains(output, "[-]"), Equals, true) - + // Reset buffer mockProgress.buffer.Reset() - + // Test Added reporter.Added("added %s", "item") output = mockProgress.buffer.String() @@ -541,25 +541,25 @@ func (s *AptlySuite) TestRecordingResultReporter(c *C) { AddedLines: []string{}, RemovedLines: []string{}, } - + // Test interface compliance var _ ResultReporter = reporter - + // Test Warning reporter.Warning("test warning %s", "message") c.Check(len(reporter.Warnings), Equals, 1) c.Check(reporter.Warnings[0], Equals, "test warning message") - + // Test Removed reporter.Removed("removed %s", "item") c.Check(len(reporter.RemovedLines), Equals, 1) c.Check(reporter.RemovedLines[0], Equals, "removed item") - + // Test Added reporter.Added("added %s", "item") c.Check(len(reporter.AddedLines), Equals, 1) c.Check(reporter.AddedLines[0], Equals, "added item") - + // Test multiple entries reporter.Warning("second warning") reporter.Added("second addition") @@ -574,39 +574,39 @@ func (s *AptlySuite) TestReadSeekerCloserInterface(c *C) { var rsc ReadSeekerCloser = &MockReadSeekerCloser{ content: []byte("Hello, World!"), } - + // Test Read buf := make([]byte, 5) n, err := rsc.Read(buf) c.Check(err, IsNil) c.Check(n, Equals, 5) c.Check(string(buf), Equals, "Hello") - + // Test Seek pos, err := rsc.Seek(0, io.SeekStart) c.Check(err, IsNil) c.Check(pos, Equals, int64(0)) - + // Test Read again from beginning n, err = rsc.Read(buf) c.Check(err, IsNil) c.Check(string(buf), Equals, "Hello") - + // Test Seek to end pos, err = rsc.Seek(-6, io.SeekEnd) c.Check(err, IsNil) c.Check(pos, Equals, int64(7)) - + // Test Read from near end buf = make([]byte, 10) n, err = rsc.Read(buf) c.Check(err, IsNil) c.Check(string(buf[:n]), Equals, "World!") - + // Test Close err = rsc.Close() c.Check(err, IsNil) - + // Test Read after close (should error) _, err = rsc.Read(buf) c.Check(err, NotNil) @@ -628,14 +628,14 @@ func (s *AptlySuite) TestBarTypeConstants(c *C) { BarPublishGeneratePackageFiles, BarPublishFinalizeIndexes, } - + // Check that all constants are different seen := make(map[BarType]bool) for _, barType := range barTypes { c.Check(seen[barType], Equals, false, Commentf("Duplicate BarType: %v", barType)) seen[barType] = true } - + // Check that they are sequential integers starting from 0 for i, barType := range barTypes { c.Check(int(barType), Equals, i, Commentf("BarType not sequential: %v", barType)) @@ -644,7 +644,7 @@ func (s *AptlySuite) TestBarTypeConstants(c *C) { func (s *AptlySuite) TestErrorHandling(c *C) { // Test error handling in mock implementations - + // Test PackagePool with errors pool := &MockPackagePool{ verifyFunc: func(string, string, *utils.ChecksumInfo, ChecksumStorage) (string, bool, error) { @@ -654,15 +654,15 @@ func (s *AptlySuite) TestErrorHandling(c *C) { return "", errors.New("import error") }, } - + _, _, err := pool.Verify("", "", nil, nil) c.Check(err, NotNil) c.Check(err.Error(), Equals, "verify error") - + _, err = pool.Import("", "", nil, false, nil) c.Check(err, NotNil) c.Check(err.Error(), Equals, "import error") - + // Test PublishedStorage with errors storage := &MockPublishedStorage{ mkDirFunc: func(string) error { @@ -672,11 +672,11 @@ func (s *AptlySuite) TestErrorHandling(c *C) { return false, errors.New("file exists error") }, } - + err = storage.MkDir("test") c.Check(err, NotNil) c.Check(err.Error(), Equals, "mkdir error") - + _, err = storage.FileExists("test") c.Check(err, NotNil) c.Check(err.Error(), Equals, "file exists error") @@ -684,29 +684,29 @@ func (s *AptlySuite) TestErrorHandling(c *C) { func (s *AptlySuite) TestInterfaceCompatibility(c *C) { // Test that our mocks properly implement the interfaces - + // PackagePool interface var _ PackagePool = &MockPackagePool{} - - // PublishedStorage interface + + // PublishedStorage interface var _ PublishedStorage = &MockPublishedStorage{} - + // Progress interface var _ Progress = &MockProgress{} - + // Downloader interface var _ Downloader = &MockDownloader{} - + // ChecksumStorage interface var _ ChecksumStorage = &MockChecksumStorage{} - + // ReadSeekerCloser interface var _ ReadSeekerCloser = &MockReadSeekerCloser{} - + // ResultReporter interface var _ ResultReporter = &ConsoleResultReporter{} var _ ResultReporter = &RecordingResultReporter{} - + // Test that the interface checks pass c.Check(true, Equals, true) -} \ No newline at end of file +} diff --git a/aptly/interfaces.go b/aptly/interfaces.go index 412daecd..6d687d90 100644 --- a/aptly/interfaces.go +++ b/aptly/interfaces.go @@ -85,6 +85,8 @@ type PublishedStorage interface { FileExists(path string) (bool, error) // ReadLink returns the symbolic link pointed to by path ReadLink(path string) (string, error) + // Flush waits for any pending operations to complete (used by concurrent upload implementations) + Flush() error } // FileSystemPublishedStorage is published storage on filesystem diff --git a/aptly/interfaces_test.go b/aptly/interfaces_test.go index 29c4673f..70fa2e46 100644 --- a/aptly/interfaces_test.go +++ b/aptly/interfaces_test.go @@ -22,4 +22,4 @@ func (s *InterfacesSuite) TestBarTypeValues(c *C) { c.Check(int(BarMirrorUpdateFinalizeDownload), Equals, 9) c.Check(int(BarPublishGeneratePackageFiles), Equals, 10) c.Check(int(BarPublishFinalizeIndexes), Equals, 11) -} \ No newline at end of file +} diff --git a/azure/package_pool.go b/azure/package_pool.go index 97be8e63..1e78fabe 100644 --- a/azure/package_pool.go +++ b/azure/package_pool.go @@ -104,7 +104,7 @@ func (pool *PackagePool) Open(path string) (aptly.ReadSeekerCloser, error) { if err != nil { return nil, errors.Wrapf(err, "error creating tempfile for %s", path) } - defer func () { _ = os.Remove(temp.Name()) }() + defer func() { _ = os.Remove(temp.Name()) }() _, err = pool.az.client.DownloadFile(context.TODO(), pool.az.container, path, temp, nil) if err != nil { diff --git a/azure/package_pool_test.go b/azure/package_pool_test.go index ef562cb3..1c235666 100644 --- a/azure/package_pool_test.go +++ b/azure/package_pool_test.go @@ -7,7 +7,7 @@ import ( "path/filepath" "runtime" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/files" "github.com/aptly-dev/aptly/utils" @@ -50,10 +50,10 @@ func (s *PackagePoolSuite) SetUpTest(c *C) { s.pool, err = NewPackagePool(s.accountName, s.accountKey, container, "", s.endpoint) c.Assert(err, IsNil) - publicAccessType := azblob.PublicAccessTypeContainer - _, err = s.pool.az.client.CreateContainer(context.TODO(), s.pool.az.container, &azblob.CreateContainerOptions{ - Access: &publicAccessType, - }) + publicAccessType := azblob.PublicAccessTypeContainer + _, err = s.pool.az.client.CreateContainer(context.TODO(), s.pool.az.container, &azblob.CreateContainerOptions{ + Access: &publicAccessType, + }) c.Assert(err, IsNil) s.prefixedPool, err = NewPackagePool(s.accountName, s.accountKey, container, prefix, s.endpoint) diff --git a/azure/public.go b/azure/public.go index 6775e14c..c5f6d48a 100644 --- a/azure/public.go +++ b/azure/public.go @@ -287,3 +287,8 @@ func (storage *PublishedStorage) ReadLink(path string) (string, error) { } return "", fmt.Errorf("error reading link %s: %v", path, err) } + +// Flush is a no-op for Azure storage +func (storage *PublishedStorage) Flush() error { + return nil +} diff --git a/azure/public_test.go b/azure/public_test.go index 5c912c51..0425e738 100644 --- a/azure/public_test.go +++ b/azure/public_test.go @@ -1,17 +1,17 @@ package azure import ( + "bytes" "context" "crypto/md5" "crypto/rand" "io" "os" "path/filepath" - "bytes" - "github.com/Azure/azure-sdk-for-go/sdk/azcore" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" - "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" "github.com/aptly-dev/aptly/files" "github.com/aptly-dev/aptly/utils" . "gopkg.in/check.v1" @@ -69,10 +69,10 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) { s.storage, err = NewPublishedStorage(s.accountName, s.accountKey, container, "", s.endpoint) c.Assert(err, IsNil) - publicAccessType := azblob.PublicAccessTypeContainer - _, err = s.storage.az.client.CreateContainer(context.Background(), s.storage.az.container, &azblob.CreateContainerOptions{ - Access: &publicAccessType, - }) + publicAccessType := azblob.PublicAccessTypeContainer + _, err = s.storage.az.client.CreateContainer(context.Background(), s.storage.az.container, &azblob.CreateContainerOptions{ + Access: &publicAccessType, + }) c.Assert(err, IsNil) s.prefixedStorage, err = NewPublishedStorage(s.accountName, s.accountKey, container, prefix, s.endpoint) @@ -80,12 +80,12 @@ func (s *PublishedStorageSuite) SetUpTest(c *C) { } func (s *PublishedStorageSuite) TearDownTest(c *C) { - _, err := s.storage.az.client.DeleteContainer(context.Background(), s.storage.az.container, nil) + _, err := s.storage.az.client.DeleteContainer(context.Background(), s.storage.az.container, nil) c.Assert(err, IsNil) } func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte { - resp, err := s.storage.az.client.DownloadStream(context.Background(), s.storage.az.container, path, nil) + resp, err := s.storage.az.client.DownloadStream(context.Background(), s.storage.az.container, path, nil) c.Assert(err, IsNil) data, err := io.ReadAll(resp.Body) c.Assert(err, IsNil) @@ -93,26 +93,26 @@ func (s *PublishedStorageSuite) GetFile(c *C, path string) []byte { } func (s *PublishedStorageSuite) AssertNoFile(c *C, path string) { - serviceClient := s.storage.az.client.ServiceClient() - containerClient := serviceClient.NewContainerClient(s.storage.az.container) - blobClient := containerClient.NewBlobClient(path) - _, err := blobClient.GetProperties(context.Background(), nil) + serviceClient := s.storage.az.client.ServiceClient() + containerClient := serviceClient.NewContainerClient(s.storage.az.container) + blobClient := containerClient.NewBlobClient(path) + _, err := blobClient.GetProperties(context.Background(), nil) c.Assert(err, NotNil) - storageError, ok := err.(*azcore.ResponseError) + storageError, ok := err.(*azcore.ResponseError) c.Assert(ok, Equals, true) c.Assert(storageError.StatusCode, Equals, 404) } func (s *PublishedStorageSuite) PutFile(c *C, path string, data []byte) { hash := md5.Sum(data) - uploadOptions := &azblob.UploadStreamOptions{ - HTTPHeaders: &blob.HTTPHeaders{ - BlobContentMD5: hash[:], - }, - } - reader := bytes.NewReader(data) - _, err := s.storage.az.client.UploadStream(context.Background(), s.storage.az.container, path, reader, uploadOptions) + uploadOptions := &azblob.UploadStreamOptions{ + HTTPHeaders: &blob.HTTPHeaders{ + BlobContentMD5: hash[:], + }, + } + reader := bytes.NewReader(data) + _, err := s.storage.az.client.UploadStream(context.Background(), s.storage.az.container, path, reader, uploadOptions) c.Assert(err, IsNil) } diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go index 36fbbdd5..4fa92bc1 100644 --- a/cmd/cmd_test.go +++ b/cmd/cmd_test.go @@ -9,9 +9,9 @@ import ( ) type CmdSuite struct { - mockProgress *MockCmdProgress - collectionFactory *deb.CollectionFactory - mockContext *MockCmdContext + mockProgress *MockCmdProgress + collectionFactory *deb.CollectionFactory + mockContext *MockCmdContext } var _ = Suite(&CmdSuite{}) @@ -35,7 +35,7 @@ func (s *CmdSuite) SetUpTest(c *C) { func (s *CmdSuite) TestListPackagesRefListBasic(c *C) { // Test basic functionality of ListPackagesRefList reflist := &deb.PackageRefList{} - + err := ListPackagesRefList(reflist, s.collectionFactory) c.Check(err, IsNil) } @@ -43,7 +43,7 @@ func (s *CmdSuite) TestListPackagesRefListBasic(c *C) { func (s *CmdSuite) TestPrintPackageListBasic(c *C) { // Test basic PrintPackageList functionality packageList := deb.NewPackageList() - + err := PrintPackageList(packageList, "", " ") c.Check(err, IsNil) } @@ -54,18 +54,18 @@ type MockCmdProgress struct { messages []string } -func (m *MockCmdProgress) Printf(msg string, a ...interface{}) {} -func (m *MockCmdProgress) ColoredPrintf(msg string, a ...interface{}) {} -func (m *MockCmdProgress) PrintfStdErr(msg string, a ...interface{}) {} -func (m *MockCmdProgress) Flush() {} -func (m *MockCmdProgress) Start() {} -func (m *MockCmdProgress) Shutdown() {} +func (m *MockCmdProgress) Printf(msg string, a ...interface{}) {} +func (m *MockCmdProgress) ColoredPrintf(msg string, a ...interface{}) {} +func (m *MockCmdProgress) PrintfStdErr(msg string, a ...interface{}) {} +func (m *MockCmdProgress) Flush() {} +func (m *MockCmdProgress) Start() {} +func (m *MockCmdProgress) Shutdown() {} func (m *MockCmdProgress) InitBar(count int64, isBytes bool, barType aptly.BarType) {} -func (m *MockCmdProgress) ShutdownBar() {} -func (m *MockCmdProgress) AddBar(count int) {} -func (m *MockCmdProgress) SetBar(count int) {} -func (m *MockCmdProgress) PrintfBar(msg string, a ...interface{}) {} -func (m *MockCmdProgress) Write(p []byte) (n int, err error) { return len(p), nil } +func (m *MockCmdProgress) ShutdownBar() {} +func (m *MockCmdProgress) AddBar(count int) {} +func (m *MockCmdProgress) SetBar(count int) {} +func (m *MockCmdProgress) PrintfBar(msg string, a ...interface{}) {} +func (m *MockCmdProgress) Write(p []byte) (n int, err error) { return len(p), nil } type MockCmdContext struct { progress *MockCmdProgress @@ -77,4 +77,4 @@ func (m *MockCmdContext) Progress() aptly.Progress { return func (m *MockCmdContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } func (m *MockCmdContext) Config() *utils.ConfigStructure { return &utils.ConfigStructure{} } -// Note: Complex integration tests have been simplified for compilation compatibility. \ No newline at end of file +// Note: Complex integration tests have been simplified for compilation compatibility. diff --git a/cmd/context_test.go b/cmd/context_test.go index eb3e9117..b640caa3 100644 --- a/cmd/context_test.go +++ b/cmd/context_test.go @@ -35,7 +35,7 @@ func (s *ContextSuite) TearDownTest(c *C) { func (s *ContextSuite) TestInitContextSuccess(c *C) { // Test successful context initialization flags := flag.NewFlagSet("test", flag.ContinueOnError) - + err := InitContext(flags) c.Check(err, IsNil) c.Check(context, NotNil) @@ -45,12 +45,12 @@ func (s *ContextSuite) TestInitContextSuccess(c *C) { func (s *ContextSuite) TestInitContextPanic(c *C) { // Test that initializing context twice causes panic flags := flag.NewFlagSet("test", flag.ContinueOnError) - + // First initialization should succeed err := InitContext(flags) c.Check(err, IsNil) c.Check(context, NotNil) - + // Second initialization should panic c.Check(func() { InitContext(flags) }, Panics, "context already initialized") } @@ -59,12 +59,12 @@ func (s *ContextSuite) TestInitContextError(c *C) { // Test context initialization with invalid flags // This tests the error path where ctx.NewContext might fail flags := flag.NewFlagSet("test", flag.ContinueOnError) - + // Add some invalid flag configuration that might cause NewContext to fail // Note: This depends on the ctx.NewContext implementation details flags.String("invalid-config", "/nonexistent/path/to/config", "invalid config") flags.Set("invalid-config", "/nonexistent/path/to/config") - + err := InitContext(flags) // The error handling depends on the ctx.NewContext implementation // If it doesn't fail with invalid paths, the test still validates the error path exists @@ -85,10 +85,10 @@ func (s *ContextSuite) TestGetContextBeforeInit(c *C) { func (s *ContextSuite) TestGetContextAfterInit(c *C) { // Test GetContext after successful initialization flags := flag.NewFlagSet("test", flag.ContinueOnError) - + err := InitContext(flags) c.Check(err, IsNil) - + result := GetContext() c.Check(result, NotNil) c.Check(result, Equals, context) @@ -97,11 +97,11 @@ func (s *ContextSuite) TestGetContextAfterInit(c *C) { func (s *ContextSuite) TestShutdownContext(c *C) { // Test ShutdownContext function flags := flag.NewFlagSet("test", flag.ContinueOnError) - + err := InitContext(flags) c.Check(err, IsNil) c.Check(context, NotNil) - + // ShutdownContext should not panic and should call context.Shutdown() c.Check(func() { ShutdownContext() }, Not(Panics)) } @@ -109,7 +109,7 @@ func (s *ContextSuite) TestShutdownContext(c *C) { func (s *ContextSuite) TestShutdownContextNil(c *C) { // Test ShutdownContext when context is nil (should panic or handle gracefully) context = nil - + // This will panic if context is nil, which might be expected behavior c.Check(func() { ShutdownContext() }, Panics, ".*") } @@ -117,11 +117,11 @@ func (s *ContextSuite) TestShutdownContextNil(c *C) { func (s *ContextSuite) TestCleanupContext(c *C) { // Test CleanupContext function flags := flag.NewFlagSet("test", flag.ContinueOnError) - + err := InitContext(flags) c.Check(err, IsNil) c.Check(context, NotNil) - + // CleanupContext should not panic and should call context.Cleanup() c.Check(func() { CleanupContext() }, Not(Panics)) } @@ -129,7 +129,7 @@ func (s *ContextSuite) TestCleanupContext(c *C) { func (s *ContextSuite) TestCleanupContextNil(c *C) { // Test CleanupContext when context is nil (should panic or handle gracefully) context = nil - + // This will panic if context is nil, which might be expected behavior c.Check(func() { CleanupContext() }, Panics, ".*") } @@ -137,24 +137,24 @@ func (s *ContextSuite) TestCleanupContextNil(c *C) { func (s *ContextSuite) TestContextLifecycle(c *C) { // Test complete context lifecycle: init -> use -> cleanup -> shutdown flags := flag.NewFlagSet("test", flag.ContinueOnError) - + // Initialize err := InitContext(flags) c.Check(err, IsNil) c.Check(context, NotNil) - + // Use ctx := GetContext() c.Check(ctx, NotNil) c.Check(ctx, Equals, context) - + // Cleanup c.Check(func() { CleanupContext() }, Not(Panics)) - + // Context should still exist after cleanup c.Check(context, NotNil) c.Check(GetContext(), NotNil) - + // Shutdown c.Check(func() { ShutdownContext() }, Not(Panics)) } @@ -162,10 +162,10 @@ func (s *ContextSuite) TestContextLifecycle(c *C) { func (s *ContextSuite) TestMultipleCleanups(c *C) { // Test calling CleanupContext multiple times flags := flag.NewFlagSet("test", flag.ContinueOnError) - + err := InitContext(flags) c.Check(err, IsNil) - + // Multiple cleanups should not cause issues c.Check(func() { CleanupContext() }, Not(Panics)) c.Check(func() { CleanupContext() }, Not(Panics)) @@ -175,19 +175,19 @@ func (s *ContextSuite) TestMultipleCleanups(c *C) { func (s *ContextSuite) TestContextVariableIsolation(c *C) { // Test that the context variable is properly managed c.Check(context, IsNil) - + flags := flag.NewFlagSet("test", flag.ContinueOnError) err := InitContext(flags) c.Check(err, IsNil) - + // Store reference originalContext := context c.Check(originalContext, NotNil) - + // GetContext should return the same instance retrievedContext := GetContext() c.Check(retrievedContext, Equals, originalContext) - + // Context variable should be the same c.Check(context, Equals, originalContext) } @@ -195,8 +195,8 @@ func (s *ContextSuite) TestContextVariableIsolation(c *C) { func (s *ContextSuite) TestFlagSetVariations(c *C) { // Test InitContext with different FlagSet configurations testCases := []struct { - name string - setupFn func() *flag.FlagSet + name string + setupFn func() *flag.FlagSet }{ { name: "empty flagset", @@ -223,18 +223,18 @@ func (s *ContextSuite) TestFlagSetVariations(c *C) { }, }, } - + for _, tc := range testCases { // Reset context for each test case if context != nil { context.Shutdown() context = nil } - + flags := tc.setupFn() err := InitContext(flags) c.Check(err, IsNil, Commentf("Failed for test case: %s", tc.name)) c.Check(context, NotNil, Commentf("Context is nil for test case: %s", tc.name)) c.Check(GetContext(), NotNil, Commentf("GetContext returned nil for test case: %s", tc.name)) } -} \ No newline at end of file +} diff --git a/cmd/db_cleanup_test.go b/cmd/db_cleanup_test.go index c4caf38e..5e5b3734 100644 --- a/cmd/db_cleanup_test.go +++ b/cmd/db_cleanup_test.go @@ -40,7 +40,7 @@ func (s *DBCleanupSuite) TestMakeCmdDBCleanup(c *C) { func (s *DBCleanupSuite) TestDBCleanupFlags(c *C) { err := s.cmd.Flag.Set("dry-run", "true") c.Check(err, IsNil) - + err = s.cmd.Flag.Set("verbose", "true") c.Check(err, IsNil) } @@ -49,17 +49,17 @@ func (s *DBCleanupSuite) TestDBCleanupFlags(c *C) { type MockDBProgress struct{} -func (m *MockDBProgress) Printf(msg string, a ...interface{}) {} -func (m *MockDBProgress) ColoredPrintf(msg string, a ...interface{}) {} -func (m *MockDBProgress) PrintfStdErr(msg string, a ...interface{}) {} -func (m *MockDBProgress) Flush() {} -func (m *MockDBProgress) Start() {} -func (m *MockDBProgress) Shutdown() {} +func (m *MockDBProgress) Printf(msg string, a ...interface{}) {} +func (m *MockDBProgress) ColoredPrintf(msg string, a ...interface{}) {} +func (m *MockDBProgress) PrintfStdErr(msg string, a ...interface{}) {} +func (m *MockDBProgress) Flush() {} +func (m *MockDBProgress) Start() {} +func (m *MockDBProgress) Shutdown() {} func (m *MockDBProgress) InitBar(count int64, isBytes bool, barType aptly.BarType) {} -func (m *MockDBProgress) ShutdownBar() {} -func (m *MockDBProgress) AddBar(count int) {} -func (m *MockDBProgress) SetBar(count int) {} -func (m *MockDBProgress) PrintfBar(msg string, a ...interface{}) {} -func (m *MockDBProgress) Write(p []byte) (n int, err error) { return len(p), nil } +func (m *MockDBProgress) ShutdownBar() {} +func (m *MockDBProgress) AddBar(count int) {} +func (m *MockDBProgress) SetBar(count int) {} +func (m *MockDBProgress) PrintfBar(msg string, a ...interface{}) {} +func (m *MockDBProgress) Write(p []byte) (n int, err error) { return len(p), nil } -// Note: Complex integration tests have been simplified for compilation compatibility. \ No newline at end of file +// Note: Complex integration tests have been simplified for compilation compatibility. diff --git a/cmd/db_recover_test.go b/cmd/db_recover_test.go index 5324b905..5a6815c9 100644 --- a/cmd/db_recover_test.go +++ b/cmd/db_recover_test.go @@ -5,8 +5,8 @@ import ( "fmt" "testing" - "github.com/aptly-dev/aptly/deb" ctx "github.com/aptly-dev/aptly/context" + "github.com/aptly-dev/aptly/deb" "github.com/smira/commander" "github.com/smira/flag" . "gopkg.in/check.v1" @@ -57,7 +57,7 @@ func (s *DBRecoverSuite) TearDownTest(c *C) { func (s *DBRecoverSuite) setupMockContext(c *C) { // Create a mock context for testing flags := flag.NewFlagSet("test", flag.ContinueOnError) - + err := InitContext(flags) c.Assert(err, IsNil) } @@ -65,13 +65,13 @@ func (s *DBRecoverSuite) setupMockContext(c *C) { func (s *DBRecoverSuite) TestMakeCmdDBRecover(c *C) { // Test that makeCmdDBRecover creates a proper command cmd := makeCmdDBRecover() - + c.Check(cmd, NotNil) c.Check(cmd.Run, NotNil) c.Check(cmd.UsageLine, Equals, "recover") c.Check(cmd.Short, Equals, "recover DB after crash") c.Check(cmd.Long, Not(Equals), "") - + // Check that the command has the right structure c.Check(cmd.Long, Matches, "(?s).*Database recover.*") c.Check(cmd.Long, Matches, "(?s).*Example:.*aptly db recover.*") @@ -80,7 +80,7 @@ func (s *DBRecoverSuite) TestMakeCmdDBRecover(c *C) { func (s *DBRecoverSuite) TestAptlyDBRecoverWithArgs(c *C) { // Test aptlyDBRecover with arguments (should fail) s.setupMockContext(c) - + err := aptlyDBRecover(s.cmd, []string{"extra", "args"}) c.Check(err, Equals, commander.ErrCommandError) } @@ -88,7 +88,7 @@ func (s *DBRecoverSuite) TestAptlyDBRecoverWithArgs(c *C) { func (s *DBRecoverSuite) TestAptlyDBRecoverNoArgs(c *C) { // Test aptlyDBRecover with no arguments s.setupMockContext(c) - + // This will likely fail due to missing database, but tests the flow err := aptlyDBRecover(s.cmd, []string{}) // We expect an error since we don't have a real database setup @@ -98,7 +98,7 @@ func (s *DBRecoverSuite) TestAptlyDBRecoverNoArgs(c *C) { func (s *DBRecoverSuite) TestCommandUsage(c *C) { // Test command usage information cmd := makeCmdDBRecover() - + c.Check(cmd.UsageLine, Equals, "recover") c.Check(cmd.Short, Equals, "recover DB after crash") c.Check(cmd.Long, Matches, "(?s).*Database recover does its' best to recover.*") @@ -124,7 +124,7 @@ func (s *DBRecoverSuite) TestDBRecoverErrorHandling(c *C) { expected: commander.ErrCommandError.Error(), }, } - + for _, tc := range testCases { s.setupMockContext(c) err := aptlyDBRecover(s.cmd, tc.args) @@ -135,7 +135,7 @@ func (s *DBRecoverSuite) TestDBRecoverErrorHandling(c *C) { func (s *DBRecoverSuite) TestCheckIntegrityFunction(c *C) { // Test checkIntegrity function structure and patterns s.setupMockContext(c) - + // Test that checkIntegrity tries to call ForEach with checkRepo // Since we don't have a real database, this will likely error, but tests the structure err := checkIntegrity() @@ -189,10 +189,10 @@ func (m *mockLocalRepoCollection) Update(repo *deb.LocalRepo) error { func (s *DBRecoverSuite) TestCheckRepoFunction(c *C) { // Test checkRepo function with mock data s.setupMockContext(c) - + // Create a mock repo repo := &deb.LocalRepo{} - + // Test that checkRepo handles the basic flow // This will likely error due to missing collections, but tests the structure err := checkRepo(repo) @@ -201,20 +201,20 @@ func (s *DBRecoverSuite) TestCheckRepoFunction(c *C) { func (s *DBRecoverSuite) TestCheckRepoErrorHandling(c *C) { // Test error handling patterns in checkRepo - + // Test error message formatting repoName := "test-repo" loadErr := errors.New("failed to load") - + // Test error wrapping pattern used in checkRepo err := fmt.Errorf("load complete repo %q: %s", repoName, loadErr) c.Check(err.Error(), Equals, "load complete repo \"test-repo\": failed to load") - + // Test another error pattern danglingErr := errors.New("dangling reference error") err = fmt.Errorf("find dangling references: %w", danglingErr) c.Check(err.Error(), Equals, "find dangling references: dangling reference error") - + // Test update error pattern updateErr := errors.New("update failed") err = fmt.Errorf("update repo: %w", updateErr) @@ -223,22 +223,22 @@ func (s *DBRecoverSuite) TestCheckRepoErrorHandling(c *C) { func (s *DBRecoverSuite) TestDanglingReferencesHandling(c *C) { // Test dangling references handling patterns - + // Mock dangling references structure type mockDanglingRefs struct { Refs []string } - + // Test with no dangling references noDangling := &mockDanglingRefs{Refs: []string{}} c.Check(len(noDangling.Refs), Equals, 0) - + // Test with dangling references withDangling := &mockDanglingRefs{ Refs: []string{"ref1", "ref2", "ref3"}, } c.Check(len(withDangling.Refs), Equals, 3) - + // Test processing dangling references for i, ref := range withDangling.Refs { c.Check(ref, Equals, fmt.Sprintf("ref%d", i+1)) @@ -247,18 +247,18 @@ func (s *DBRecoverSuite) TestDanglingReferencesHandling(c *C) { func (s *DBRecoverSuite) TestProgressReporting(c *C) { // Test progress reporting patterns used in db recover - + type mockProgress struct { messages []string } - + progress := &mockProgress{} - + // Test progress messages used in the functions progress.messages = append(progress.messages, "Recovering database...") progress.messages = append(progress.messages, "Checking database integrity...") progress.messages = append(progress.messages, "Removing dangling database reference \"ref1\"") - + c.Check(len(progress.messages), Equals, 3) c.Check(progress.messages[0], Equals, "Recovering database...") c.Check(progress.messages[1], Equals, "Checking database integrity...") @@ -267,7 +267,7 @@ func (s *DBRecoverSuite) TestProgressReporting(c *C) { func (s *DBRecoverSuite) TestCollectionFactoryUsage(c *C) { // Test collection factory usage patterns - + factory := &mockCollectionFactory{ localRepoCollection: mockLocalRepoCollectionInterface{ repos: make(map[string]*deb.LocalRepo), @@ -276,7 +276,7 @@ func (s *DBRecoverSuite) TestCollectionFactoryUsage(c *C) { packages: []string{}, }, } - + // Test factory usage c.Check(factory.localRepoCollection, NotNil) c.Check(factory.packageCollection, NotNil) @@ -286,7 +286,7 @@ func (s *DBRecoverSuite) TestCollectionFactoryUsage(c *C) { func (s *DBRecoverSuite) TestDBPathHandling(c *C) { // Test database path handling patterns s.setupMockContext(c) - + // Test that context has DBPath method // This will test the pattern used in goleveldb.RecoverDB(context.DBPath()) if context != nil { @@ -298,13 +298,13 @@ func (s *DBRecoverSuite) TestDBPathHandling(c *C) { func (s *DBRecoverSuite) TestRecoveryWorkflow(c *C) { // Test the overall recovery workflow structure - + type recoveryStep struct { name string description string completed bool } - + // Simulate the recovery workflow steps := []recoveryStep{ { @@ -318,12 +318,12 @@ func (s *DBRecoverSuite) TestRecoveryWorkflow(c *C) { completed: false, }, } - + // Simulate executing steps for i := range steps { steps[i].completed = true } - + // Verify all steps completed allCompleted := true for _, step := range steps { @@ -332,7 +332,7 @@ func (s *DBRecoverSuite) TestRecoveryWorkflow(c *C) { break } } - + c.Check(allCompleted, Equals, true) c.Check(len(steps), Equals, 2) c.Check(steps[0].name, Equals, "recover_db") @@ -341,16 +341,16 @@ func (s *DBRecoverSuite) TestRecoveryWorkflow(c *C) { func (s *DBRecoverSuite) TestForEachRepoPattern(c *C) { // Test the ForEach pattern used in checkIntegrity - + // Mock repository list repos := []*deb.LocalRepo{ // These would be real LocalRepo objects in practice } - + // Mock the ForEach pattern processedRepos := 0 errors := []error{} - + // Simulate ForEach with checkRepo for _, repo := range repos { err := checkRepo(repo) @@ -359,7 +359,7 @@ func (s *DBRecoverSuite) TestForEachRepoPattern(c *C) { errors = append(errors, err) } } - + c.Check(processedRepos, Equals, len(repos)) // We expect errors since we're using mock data c.Check(len(errors), Equals, len(repos)) @@ -367,19 +367,19 @@ func (s *DBRecoverSuite) TestForEachRepoPattern(c *C) { func (s *DBRecoverSuite) TestRefListOperations(c *C) { // Test reference list operations used in checkRepo - + // Mock reference operations totalRefs := 100 danglingCount := 5 remainingRefs := totalRefs - danglingCount - + c.Check(remainingRefs, Equals, 95) c.Check(danglingCount, Equals, 5) - + // Test dangling reference removal pattern danglingRefs := []string{"ref1", "ref2", "ref3", "ref4", "ref5"} c.Check(len(danglingRefs), Equals, danglingCount) - + for i, ref := range danglingRefs { c.Check(ref, Equals, fmt.Sprintf("ref%d", i+1)) } @@ -388,19 +388,19 @@ func (s *DBRecoverSuite) TestRefListOperations(c *C) { func (s *DBRecoverSuite) TestCommandIntegration(c *C) { // Test command integration and structure cmd := makeCmdDBRecover() - + // Verify command is properly constructed c.Check(cmd.Run, Equals, aptlyDBRecover) c.Check(cmd.UsageLine, Not(Equals), "") c.Check(cmd.Short, Not(Equals), "") c.Check(cmd.Long, Not(Equals), "") - + // Test that command can be called (will error due to no real setup) s.setupMockContext(c) err := cmd.Run(cmd, []string{"invalid"}) c.Check(err, Equals, commander.ErrCommandError) - + // Test with no args (expected case) err = cmd.Run(cmd, []string{}) c.Check(err, NotNil) // Expected to fail with mock setup -} \ No newline at end of file +} diff --git a/cmd/graph_test.go b/cmd/graph_test.go index 36717df8..877bcd0b 100644 --- a/cmd/graph_test.go +++ b/cmd/graph_test.go @@ -411,13 +411,13 @@ func (m *MockGraphProgress) Write(p []byte) (n int, err error) { } // Implement aptly.Progress interface -func (m *MockGraphProgress) Start() {} -func (m *MockGraphProgress) Shutdown() {} -func (m *MockGraphProgress) Flush() {} +func (m *MockGraphProgress) Start() {} +func (m *MockGraphProgress) Shutdown() {} +func (m *MockGraphProgress) Flush() {} func (m *MockGraphProgress) InitBar(count int64, isBytes bool, barType aptly.BarType) {} -func (m *MockGraphProgress) ShutdownBar() {} -func (m *MockGraphProgress) AddBar(count int) {} -func (m *MockGraphProgress) SetBar(count int) {} +func (m *MockGraphProgress) ShutdownBar() {} +func (m *MockGraphProgress) AddBar(count int) {} +func (m *MockGraphProgress) SetBar(count int) {} func (m *MockGraphProgress) Printf(msg string, a ...interface{}) { formatted := fmt.Sprintf(msg, a...) m.Messages = append(m.Messages, formatted) @@ -439,8 +439,8 @@ type MockGraphContext struct { mockGraph *MockGraph } -func (m *MockGraphContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockGraphContext) Progress() aptly.Progress { return m.progress } +func (m *MockGraphContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockGraphContext) Progress() aptly.Progress { return m.progress } func (m *MockGraphContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } type MockGraph struct { @@ -475,7 +475,7 @@ func (s *GraphSuite) TestAptlyGraphStdinPipeError(c *C) { // This is harder to mock since exec.Command.StdinPipe() is not easily mockable // We test this indirectly by ensuring our basic flow works // The actual stdin pipe error would be rare and hard to reproduce in tests - + // Clear PATH to ensure dot is not found (which triggers the error before stdin pipe) originalPath := os.Getenv("PATH") defer os.Setenv("PATH", originalPath) @@ -484,4 +484,4 @@ func (s *GraphSuite) TestAptlyGraphStdinPipeError(c *C) { err := aptlyGraph(s.cmd, []string{}) c.Check(err, NotNil) c.Check(err.Error(), Matches, ".*unable to execute dot.*") -} \ No newline at end of file +} diff --git a/cmd/mirror_create_test.go b/cmd/mirror_create_test.go index b1fc2645..f9e3ec92 100644 --- a/cmd/mirror_create_test.go +++ b/cmd/mirror_create_test.go @@ -317,13 +317,13 @@ func (m *MockMirrorCreateProgress) Write(p []byte) (n int, err error) { } // Implement aptly.Progress interface -func (m *MockMirrorCreateProgress) Start() {} -func (m *MockMirrorCreateProgress) Shutdown() {} -func (m *MockMirrorCreateProgress) Flush() {} +func (m *MockMirrorCreateProgress) Start() {} +func (m *MockMirrorCreateProgress) Shutdown() {} +func (m *MockMirrorCreateProgress) Flush() {} func (m *MockMirrorCreateProgress) InitBar(count int64, isBytes bool, barType aptly.BarType) {} -func (m *MockMirrorCreateProgress) ShutdownBar() {} -func (m *MockMirrorCreateProgress) AddBar(count int) {} -func (m *MockMirrorCreateProgress) SetBar(count int) {} +func (m *MockMirrorCreateProgress) ShutdownBar() {} +func (m *MockMirrorCreateProgress) AddBar(count int) {} +func (m *MockMirrorCreateProgress) SetBar(count int) {} func (m *MockMirrorCreateProgress) Printf(msg string, a ...interface{}) { formatted := fmt.Sprintf(msg, a...) m.Messages = append(m.Messages, formatted) @@ -338,24 +338,26 @@ func (m *MockMirrorCreateProgress) PrintfStdErr(msg string, a ...interface{}) { } type MockMirrorCreateContext struct { - flags *flag.FlagSet - progress *MockMirrorCreateProgress - collectionFactory *deb.CollectionFactory - architectures []string - config *utils.ConfigStructure - downloader aptly.Downloader - ppaError bool - newRemoteRepoError bool - verifierError bool - fetchError bool + flags *flag.FlagSet + progress *MockMirrorCreateProgress + collectionFactory *deb.CollectionFactory + architectures []string + config *utils.ConfigStructure + downloader aptly.Downloader + ppaError bool + newRemoteRepoError bool + verifierError bool + fetchError bool } -func (m *MockMirrorCreateContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockMirrorCreateContext) Progress() aptly.Progress { return m.progress } -func (m *MockMirrorCreateContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockMirrorCreateContext) ArchitecturesList() []string { return m.architectures } -func (m *MockMirrorCreateContext) Config() *utils.ConfigStructure { return m.config } -func (m *MockMirrorCreateContext) Downloader() aptly.Downloader { return m.downloader } +func (m *MockMirrorCreateContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockMirrorCreateContext) Progress() aptly.Progress { return m.progress } +func (m *MockMirrorCreateContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockMirrorCreateContext) ArchitecturesList() []string { return m.architectures } +func (m *MockMirrorCreateContext) Config() *utils.ConfigStructure { return m.config } +func (m *MockMirrorCreateContext) Downloader() aptly.Downloader { return m.downloader } type MockMirrorCreateDownloader struct{} @@ -366,8 +368,8 @@ func (m *MockMirrorCreateDownloader) Download(ctx stdcontext.Context, url string func (m *MockMirrorCreateDownloader) DownloadWithChecksum(ctx stdcontext.Context, url string, destination string, expected *utils.ChecksumInfo, ignoreMismatch bool) error { return nil } -func (m *MockMirrorCreateDownloader) GetProgress() aptly.Progress { - return &MockMirrorCreateProgress{} +func (m *MockMirrorCreateDownloader) GetProgress() aptly.Progress { + return &MockMirrorCreateProgress{} } func (m *MockMirrorCreateDownloader) GetLength(ctx stdcontext.Context, url string) (int64, error) { return 0, nil @@ -452,18 +454,18 @@ func (s *MirrorCreateSuite) TestAptlyMirrorCreateFlagCombinations(c *C) { // Test LookupOption functionality func (s *MirrorCreateSuite) TestLookupOptionLogic(c *C) { // Test the LookupOption function behavior - + // Test with global config enabled, flag not set s.mockContext.config.DownloadSourcePackages = true result := LookupOption(s.mockContext.config.DownloadSourcePackages, &s.cmd.Flag, "with-sources") c.Check(result, Equals, true) - + // Test with global config disabled, flag explicitly set s.mockContext.config.DownloadSourcePackages = false s.cmd.Flag.Set("with-sources", "true") result = LookupOption(s.mockContext.config.DownloadSourcePackages, &s.cmd.Flag, "with-sources") c.Check(result, Equals, true) - + // Reset s.cmd.Flag.Set("with-sources", "false") } @@ -489,4 +491,4 @@ func (s *MirrorCreateSuite) TestArchitectureHandling(c *C) { // Note: Removed fmt.Printf restoration } -} \ No newline at end of file +} diff --git a/cmd/mirror_edit_test.go b/cmd/mirror_edit_test.go index 5953798e..8abae557 100644 --- a/cmd/mirror_edit_test.go +++ b/cmd/mirror_edit_test.go @@ -291,13 +291,13 @@ func (m *MockMirrorEditProgress) Write(p []byte) (n int, err error) { } // Implement aptly.Progress interface -func (m *MockMirrorEditProgress) Start() {} -func (m *MockMirrorEditProgress) Shutdown() {} -func (m *MockMirrorEditProgress) Flush() {} +func (m *MockMirrorEditProgress) Start() {} +func (m *MockMirrorEditProgress) Shutdown() {} +func (m *MockMirrorEditProgress) Flush() {} func (m *MockMirrorEditProgress) InitBar(count int64, isBytes bool, barType aptly.BarType) {} -func (m *MockMirrorEditProgress) ShutdownBar() {} -func (m *MockMirrorEditProgress) AddBar(count int) {} -func (m *MockMirrorEditProgress) SetBar(count int) {} +func (m *MockMirrorEditProgress) ShutdownBar() {} +func (m *MockMirrorEditProgress) AddBar(count int) {} +func (m *MockMirrorEditProgress) SetBar(count int) {} func (m *MockMirrorEditProgress) Printf(msg string, a ...interface{}) { formatted := fmt.Sprintf(msg, a...) m.Messages = append(m.Messages, formatted) @@ -322,12 +322,14 @@ type MockMirrorEditContext struct { verifierError bool } -func (m *MockMirrorEditContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockMirrorEditContext) Progress() aptly.Progress { return m.progress } -func (m *MockMirrorEditContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockMirrorEditContext) ArchitecturesList() []string { return m.architectures } -func (m *MockMirrorEditContext) Config() *utils.ConfigStructure { return m.config } -func (m *MockMirrorEditContext) Downloader() aptly.Downloader { return m.downloader } +func (m *MockMirrorEditContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockMirrorEditContext) Progress() aptly.Progress { return m.progress } +func (m *MockMirrorEditContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockMirrorEditContext) ArchitecturesList() []string { return m.architectures } +func (m *MockMirrorEditContext) Config() *utils.ConfigStructure { return m.config } +func (m *MockMirrorEditContext) Downloader() aptly.Downloader { return m.downloader } func (m *MockMirrorEditContext) GlobalFlags() *flag.FlagSet { globalFlags := flag.NewFlagSet("global", flag.ExitOnError) @@ -349,8 +351,8 @@ func (m *MockDownloader) Download(ctx stdcontext.Context, url string, destinatio func (m *MockDownloader) DownloadWithChecksum(ctx stdcontext.Context, url string, destination string, expected *utils.ChecksumInfo, ignoreMismatch bool) error { return nil } -func (m *MockDownloader) GetProgress() aptly.Progress { - return &MockMirrorEditProgress{} +func (m *MockDownloader) GetProgress() aptly.Progress { + return &MockMirrorEditProgress{} } func (m *MockDownloader) GetLength(ctx stdcontext.Context, url string) (int64, error) { return 0, nil @@ -485,4 +487,4 @@ func (s *MirrorEditSuite) TestAptlyMirrorEditArchitectureHandling(c *C) { s.mockContext.architecturesChanged = false // Note: Removed fmt.Printf restoration } -} \ No newline at end of file +} diff --git a/cmd/mirror_list_test.go b/cmd/mirror_list_test.go index 56cfda77..9326ee71 100644 --- a/cmd/mirror_list_test.go +++ b/cmd/mirror_list_test.go @@ -28,7 +28,7 @@ func (s *MirrorListSuite) SetUpTest(c *C) { // Set up mock collections s.collectionFactory = &deb.CollectionFactory{ - // Note: Removed remoteRepoCollection field to fix compilation + // Note: Removed remoteRepoCollection field to fix compilation } // Set up mock context @@ -249,10 +249,12 @@ type MockMirrorListContext struct { collectionFactory *deb.CollectionFactory } -func (m *MockMirrorListContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockMirrorListContext) Progress() aptly.Progress { return m.progress } -func (m *MockMirrorListContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockMirrorListContext) CloseDatabase() error { return nil } +func (m *MockMirrorListContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockMirrorListContext) Progress() aptly.Progress { return m.progress } +func (m *MockMirrorListContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockMirrorListContext) CloseDatabase() error { return nil } type MockRemoteMirrorListCollection struct { emptyCollection bool @@ -289,7 +291,7 @@ func (m *MockRemoteMirrorListCollection) ForEach(handler func(*deb.RemoteRepo) e Distribution: "stable", Components: []string{"main"}, } - + if err := handler(repo); err != nil { return err } @@ -301,13 +303,13 @@ func (m *MockRemoteMirrorListCollection) ForEach(handler func(*deb.RemoteRepo) e Distribution: "stable", Components: []string{"main"}, } - + // Create problematic repo for marshal error testing if m.causeMarshalError { // Create a structure that can't be marshaled // Note: Removed cyclic reference as TestCyclicRef field doesn't exist } - + return handler(repo) } @@ -336,7 +338,7 @@ func (s *MirrorListSuite) TestSortingLogic(c *C) { // Test string sorting mirrors := []string{"z-mirror", "a-mirror", "m-mirror"} sort.Strings(mirrors) - + expected := []string{"a-mirror", "m-mirror", "z-mirror"} c.Check(mirrors, DeepEquals, expected) } @@ -348,11 +350,11 @@ func (s *MirrorListSuite) TestMirrorSliceSorting(c *C) { {Name: "a-mirror"}, {Name: "m-mirror"}, } - + sort.Slice(repos, func(i, j int) bool { return repos[i].Name < repos[j].Name }) - + expectedOrder := []string{"a-mirror", "m-mirror", "z-mirror"} for i, repo := range repos { c.Check(repo.Name, Equals, expectedOrder[i]) @@ -366,7 +368,7 @@ func (s *MirrorListSuite) TestFormatStrings(c *C) { ArchiveRoot: "http://example.com/debian", Distribution: "stable", } - + // Test basic repo properties c.Check(repo.Name, Equals, "test") c.Check(repo.ArchiveRoot, Equals, "http://example.com/debian") @@ -378,7 +380,7 @@ func (s *MirrorListSuite) TestEdgeCases(c *C) { // Test with mirror that has minimal configuration repo := &deb.RemoteRepo{Name: "simple-mirror"} c.Check(repo.Name, Equals, "simple-mirror") - + // Test basic repo properties c.Check(len(repo.Name) > 0, Equals, true) } @@ -404,7 +406,7 @@ func (s *MirrorListSuite) TestFlagCombinations(c *C) { for flag := range flags { s.cmd.Flag.Set(flag, "false") } - + // Note: Removed complex output mocking to fix compilation } } @@ -429,4 +431,4 @@ func (s *MirrorListSuite) TestMirrorConfigurations(c *C) { // Basic test - function should complete successfully // Note: Removed complex mocking to fix compilation } -} \ No newline at end of file +} diff --git a/cmd/mirror_show_test.go b/cmd/mirror_show_test.go index 47781bb6..30a767cd 100644 --- a/cmd/mirror_show_test.go +++ b/cmd/mirror_show_test.go @@ -234,7 +234,7 @@ func (s *MirrorShowSuite) TestAptlyMirrorShowJSONMarshalError(c *C) { args := []string{"test-mirror"} err := aptlyMirrorShow(s.cmd, args) // Note: Actual marshal errors would be runtime dependent - _ = err // May or may not error depending on implementation + _ = err // May or may not error depending on implementation s.cmd.Flag.Set("json", "false") // Reset flag } @@ -319,13 +319,15 @@ type MockMirrorShowContext struct { collectionFactory *deb.CollectionFactory } -func (m *MockMirrorShowContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockMirrorShowContext) Progress() aptly.Progress { return m.progress } -func (m *MockMirrorShowContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockMirrorShowContext) CloseDatabase() error { return nil } +func (m *MockMirrorShowContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockMirrorShowContext) Progress() aptly.Progress { return m.progress } +func (m *MockMirrorShowContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockMirrorShowContext) CloseDatabase() error { return nil } // Note: Removed complex mock structures to fix compilation issues // Tests are simplified to focus on basic command functionality // Note: Removed method definitions on non-local types and global function overrides -// to fix compilation errors. Tests are simplified to focus on basic functionality. \ No newline at end of file +// to fix compilation errors. Tests are simplified to focus on basic functionality. diff --git a/cmd/mirror_update_test.go b/cmd/mirror_update_test.go index aaee1b42..8f9882fd 100644 --- a/cmd/mirror_update_test.go +++ b/cmd/mirror_update_test.go @@ -9,18 +9,18 @@ import ( "testing" "github.com/aptly-dev/aptly/aptly" + ctx "github.com/aptly-dev/aptly/context" "github.com/aptly-dev/aptly/deb" "github.com/aptly-dev/aptly/pgp" "github.com/aptly-dev/aptly/query" - ctx "github.com/aptly-dev/aptly/context" "github.com/smira/commander" "github.com/smira/flag" . "gopkg.in/check.v1" ) type MirrorUpdateSuite struct { - cmd *commander.Command - originalContext *ctx.AptlyContext + cmd *commander.Command + originalContext *ctx.AptlyContext } var _ = Suite(&MirrorUpdateSuite{}) @@ -44,7 +44,7 @@ func (s *MirrorUpdateSuite) setupMockContext(c *C) { flags.Bool("ignore-signatures", false, "ignore signatures") flags.Bool("ignore-checksums", false, "ignore checksums") flags.Bool("skip-existing-packages", false, "skip existing") - + err := InitContext(flags) c.Assert(err, IsNil) } @@ -52,13 +52,13 @@ func (s *MirrorUpdateSuite) setupMockContext(c *C) { func (s *MirrorUpdateSuite) TestMakeCmdMirrorUpdate(c *C) { // Test that makeCmdMirrorUpdate creates a proper command cmd := makeCmdMirrorUpdate() - + c.Check(cmd, NotNil) c.Check(cmd.Run, NotNil) c.Check(cmd.UsageLine, Equals, "update ") c.Check(cmd.Short, Equals, "update mirror") c.Check(cmd.Long, Not(Equals), "") - + // Check that all expected flags are present c.Check(cmd.Flag.Lookup("force"), NotNil) c.Check(cmd.Flag.Lookup("ignore-checksums"), NotNil) @@ -73,7 +73,7 @@ func (s *MirrorUpdateSuite) TestMakeCmdMirrorUpdate(c *C) { func (s *MirrorUpdateSuite) TestAptlyMirrorUpdateNoArgs(c *C) { // Test aptlyMirrorUpdate with no arguments s.setupMockContext(c) - + err := aptlyMirrorUpdate(s.cmd, []string{}) c.Check(err, Equals, commander.ErrCommandError) } @@ -81,7 +81,7 @@ func (s *MirrorUpdateSuite) TestAptlyMirrorUpdateNoArgs(c *C) { func (s *MirrorUpdateSuite) TestAptlyMirrorUpdateMultipleArgs(c *C) { // Test aptlyMirrorUpdate with multiple arguments s.setupMockContext(c) - + err := aptlyMirrorUpdate(s.cmd, []string{"mirror1", "mirror2"}) c.Check(err, Equals, commander.ErrCommandError) } @@ -89,7 +89,7 @@ func (s *MirrorUpdateSuite) TestAptlyMirrorUpdateMultipleArgs(c *C) { func (s *MirrorUpdateSuite) TestAptlyMirrorUpdateNonexistentMirror(c *C) { // Test aptlyMirrorUpdate with nonexistent mirror s.setupMockContext(c) - + err := aptlyMirrorUpdate(s.cmd, []string{"nonexistent-mirror"}) c.Check(err, NotNil) c.Check(err.Error(), Matches, "unable to update:.*") @@ -166,32 +166,32 @@ func (m *mockRemoteRepo) FinalizeDownload(collectionFactory *deb.CollectionFacto func (s *MirrorUpdateSuite) TestMirrorUpdateFlagParsing(c *C) { // Test that command flags are properly parsed and used cmd := makeCmdMirrorUpdate() - + // Test default flag values forceFlag := cmd.Flag.Lookup("force") c.Check(forceFlag, NotNil) c.Check(forceFlag.DefValue, Equals, "false") - + ignoreChecksumsFlag := cmd.Flag.Lookup("ignore-checksums") c.Check(ignoreChecksumsFlag, NotNil) c.Check(ignoreChecksumsFlag.DefValue, Equals, "false") - + ignoreSignaturesFlag := cmd.Flag.Lookup("ignore-signatures") c.Check(ignoreSignaturesFlag, NotNil) c.Check(ignoreSignaturesFlag.DefValue, Equals, "false") - + skipExistingFlag := cmd.Flag.Lookup("skip-existing-packages") c.Check(skipExistingFlag, NotNil) c.Check(skipExistingFlag.DefValue, Equals, "false") - + downloadLimitFlag := cmd.Flag.Lookup("download-limit") c.Check(downloadLimitFlag, NotNil) c.Check(downloadLimitFlag.DefValue, Equals, "0") - + downloaderFlag := cmd.Flag.Lookup("downloader") c.Check(downloaderFlag, NotNil) c.Check(downloaderFlag.DefValue, Equals, "default") - + maxTriesFlag := cmd.Flag.Lookup("max-tries") c.Check(maxTriesFlag, NotNil) c.Check(maxTriesFlag.DefValue, Equals, "1") @@ -200,7 +200,7 @@ func (s *MirrorUpdateSuite) TestMirrorUpdateFlagParsing(c *C) { func (s *MirrorUpdateSuite) TestMirrorUpdateCommandUsage(c *C) { // Test command usage information cmd := makeCmdMirrorUpdate() - + c.Check(cmd.UsageLine, Equals, "update ") c.Check(cmd.Short, Equals, "update mirror") c.Check(cmd.Long, Matches, "(?s).*Updates remote mirror.*") @@ -220,12 +220,12 @@ func (s *MirrorUpdateSuite) TestMirrorUpdateErrorHandling(c *C) { expected: commander.ErrCommandError.Error(), }, { - name: "too many arguments", + name: "too many arguments", args: []string{"mirror1", "mirror2"}, expected: commander.ErrCommandError.Error(), }, } - + for _, tc := range testCases { s.setupMockContext(c) err := aptlyMirrorUpdate(s.cmd, tc.args) @@ -241,7 +241,7 @@ func (s *MirrorUpdateSuite) TestMirrorUpdateErrorHandling(c *C) { func (s *MirrorUpdateSuite) TestMirrorUpdateQueryParsing(c *C) { // Test that query parsing works correctly // This is an integration test for the query.Parse functionality used in the filter - + testCases := []struct { queryStr string valid bool @@ -252,7 +252,7 @@ func (s *MirrorUpdateSuite) TestMirrorUpdateQueryParsing(c *C) { {"invalid query syntax %%%", false}, {"", false}, // empty query } - + for _, tc := range testCases { _, err := query.Parse(tc.queryStr) if tc.valid { @@ -265,20 +265,20 @@ func (s *MirrorUpdateSuite) TestMirrorUpdateQueryParsing(c *C) { func (s *MirrorUpdateSuite) TestMirrorUpdateConcurrencyStructures(c *C) { // Test the concurrent download structures and patterns used in aptlyMirrorUpdate - + // Test that we can create channels and sync structures like in the function downloadQueue := make(chan int, 10) var wg sync.WaitGroup var errors []string var errLock sync.Mutex - + // Test the pushError function pattern pushError := func(err error) { errLock.Lock() errors = append(errors, err.Error()) errLock.Unlock() } - + // Test concurrent error collection numGoroutines := 5 for i := 0; i < numGoroutines; i++ { @@ -288,10 +288,10 @@ func (s *MirrorUpdateSuite) TestMirrorUpdateConcurrencyStructures(c *C) { pushError(fmt.Errorf("error from goroutine %d", id)) }(i) } - + wg.Wait() close(downloadQueue) - + // Verify error collection worked c.Check(len(errors), Equals, numGoroutines) for i, errMsg := range errors { @@ -301,25 +301,25 @@ func (s *MirrorUpdateSuite) TestMirrorUpdateConcurrencyStructures(c *C) { func (s *MirrorUpdateSuite) TestMirrorUpdateTempFileHandling(c *C) { // Test temp file creation and cleanup patterns used in the download section - + // Test temporary file creation pattern tempFile, err := os.CreateTemp("", "test-download-file") c.Check(err, IsNil) tempPath := tempFile.Name() tempFile.Close() - + // Verify file exists _, err = os.Stat(tempPath) c.Check(err, IsNil) - + // Test cleanup pattern (like in the defer function) err = os.Remove(tempPath) c.Check(err, IsNil) - + // Verify file is gone _, err = os.Stat(tempPath) c.Check(os.IsNotExist(err), Equals, true) - + // Test cleanup of nonexistent file (should not error) err = os.Remove(tempPath) c.Check(os.IsNotExist(err), Equals, true) @@ -327,7 +327,7 @@ func (s *MirrorUpdateSuite) TestMirrorUpdateTempFileHandling(c *C) { func (s *MirrorUpdateSuite) TestMirrorUpdateDownloadTaskStructure(c *C) { // Test the download task structure and patterns - + // Mock the PackageDownloadTask structure that would be used tasks := []struct { Done bool @@ -346,11 +346,11 @@ func (s *MirrorUpdateSuite) TestMirrorUpdateDownloadTaskStructure(c *C) { }, { Done: true, - TempDownPath: "/tmp/package2.deb", + TempDownPath: "/tmp/package2.deb", Additional: []interface{}{}, }, } - + // Test processing pattern similar to the import loop completedTasks := 0 for _, task := range tasks { @@ -358,30 +358,30 @@ func (s *MirrorUpdateSuite) TestMirrorUpdateDownloadTaskStructure(c *C) { completedTasks++ } } - + c.Check(completedTasks, Equals, 1) c.Check(len(tasks), Equals, 2) } func (s *MirrorUpdateSuite) TestMirrorUpdateProgressReporting(c *C) { // Test progress reporting patterns used in the function - + // Test that we can create progress reporting structures type mockProgress struct { messages []string barInit bool barShut bool } - + progress := &mockProgress{} - + // Test progress methods that would be called progress.messages = append(progress.messages, "Downloading & parsing package files...") progress.messages = append(progress.messages, "Applying filter...") progress.messages = append(progress.messages, "Building download queue...") progress.barInit = true progress.barShut = true - + c.Check(len(progress.messages), Equals, 3) c.Check(progress.barInit, Equals, true) c.Check(progress.barShut, Equals, true) @@ -392,45 +392,45 @@ func (s *MirrorUpdateSuite) TestMirrorUpdateProgressReporting(c *C) { func (s *MirrorUpdateSuite) TestMirrorUpdateDeferPatterns(c *C) { // Test defer patterns used in the function - + var cleanupCalled bool var databaseReopened bool - + func() { defer func() { // Simulate the cleanup defer cleanupCalled = true }() - + defer func() { // Simulate the database reopen defer databaseReopened = true }() - + // Simulate function execution }() - + c.Check(cleanupCalled, Equals, true) c.Check(databaseReopened, Equals, true) } func (s *MirrorUpdateSuite) TestMirrorUpdateContextCancellation(c *C) { // Test context cancellation patterns used in the goroutines - + // Create a simple context-like structure done := make(chan struct{}) queue := make(chan int, 5) - + // Put some items in queue for i := 0; i < 3; i++ { queue <- i } close(queue) - + // Test the select pattern used in the download goroutines processed := 0 cancelled := false - + for { select { case item, ok := <-queue: @@ -444,21 +444,21 @@ func (s *MirrorUpdateSuite) TestMirrorUpdateContextCancellation(c *C) { goto finished } } - + finished: c.Check(processed, Equals, 3) c.Check(cancelled, Equals, false) - + // Test early cancellation done2 := make(chan struct{}) close(done2) // Cancel immediately - + select { case <-done2: cancelled = true default: cancelled = false } - + c.Check(cancelled, Equals, true) -} \ No newline at end of file +} diff --git a/cmd/package_show_test.go b/cmd/package_show_test.go index 18e46c7d..48ef2ac0 100644 --- a/cmd/package_show_test.go +++ b/cmd/package_show_test.go @@ -132,7 +132,7 @@ func (s *PackageShowSuite) TestAptlyPackageShowWithFilesError(c *C) { args := []string{"nginx"} err := aptlyPackageShow(s.cmd, args) // Note: Actual error handling depends on real implementation - _ = err // May or may not error depending on implementation + _ = err // May or may not error depending on implementation s.cmd.Flag.Set("with-files", "false") // Reset flag } @@ -155,7 +155,7 @@ func (s *PackageShowSuite) TestAptlyPackageShowWithReferencesRemoteError(c *C) { args := []string{"nginx"} err := aptlyPackageShow(s.cmd, args) // Note: Actual error handling depends on real implementation - _ = err // May or may not error depending on implementation + _ = err // May or may not error depending on implementation s.cmd.Flag.Set("with-references", "false") // Reset flag } @@ -166,7 +166,7 @@ func (s *PackageShowSuite) TestAptlyPackageShowWithReferencesLocalError(c *C) { args := []string{"nginx"} err := aptlyPackageShow(s.cmd, args) // Note: Actual error handling depends on real implementation - _ = err // May or may not error depending on implementation + _ = err // May or may not error depending on implementation s.cmd.Flag.Set("with-references", "false") // Reset flag } @@ -177,7 +177,7 @@ func (s *PackageShowSuite) TestAptlyPackageShowWithReferencesSnapshotError(c *C) args := []string{"nginx"} err := aptlyPackageShow(s.cmd, args) // Note: Actual error handling depends on real implementation - _ = err // May or may not error depending on implementation + _ = err // May or may not error depending on implementation s.cmd.Flag.Set("with-references", "false") // Reset flag } @@ -188,7 +188,7 @@ func (s *PackageShowSuite) TestAptlyPackageShowWithReferencesLoadError(c *C) { args := []string{"nginx"} err := aptlyPackageShow(s.cmd, args) // Note: Actual error handling depends on real implementation - _ = err // May or may not error depending on implementation + _ = err // May or may not error depending on implementation s.cmd.Flag.Set("with-references", "false") // Reset flag } @@ -225,10 +225,10 @@ func (s *PackageShowSuite) TestAptlyPackageShowPackageForEachError(c *C) { func (s *PackageShowSuite) TestPrintReferencesToBasic(c *C) { // Test printReferencesTo function directly - simplified test pkg := &deb.Package{Name: "test-package"} - + // Note: Function call depends on real implementation // Basic test would check if printReferencesTo exists and doesn't panic - _ = pkg // Use variable to avoid unused warning + _ = pkg // Use variable to avoid unused warning c.Check(true, Equals, true) // Placeholder assertion } @@ -305,16 +305,18 @@ type MockPackageShowContext struct { packagePool aptly.PackagePool } -func (m *MockPackageShowContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockPackageShowContext) Progress() aptly.Progress { return m.progress } -func (m *MockPackageShowContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockPackageShowContext) PackagePool() aptly.PackagePool { return m.packagePool } -func (m *MockPackageShowContext) CloseDatabase() error { return nil } +func (m *MockPackageShowContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockPackageShowContext) Progress() aptly.Progress { return m.progress } +func (m *MockPackageShowContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockPackageShowContext) PackagePool() aptly.PackagePool { return m.packagePool } +func (m *MockPackageShowContext) CloseDatabase() error { return nil } type MockRemotePackageShowCollection struct { - shouldErrorForEach bool - shouldErrorLoadComplete bool - forEachCalled bool + shouldErrorForEach bool + shouldErrorLoadComplete bool + forEachCalled bool } func (m *MockRemotePackageShowCollection) ForEach(handler func(*deb.RemoteRepo) error) error { @@ -326,7 +328,7 @@ func (m *MockRemotePackageShowCollection) ForEach(handler func(*deb.RemoteRepo) // Create mock repo - simplified repo := &deb.RemoteRepo{Name: "test-remote-repo"} // Note: Removed access to unexported fields - + return handler(repo) } @@ -338,9 +340,9 @@ func (m *MockRemotePackageShowCollection) LoadComplete(repo *deb.RemoteRepo) err } type MockLocalPackageShowCollection struct { - shouldErrorForEach bool - shouldErrorLoadComplete bool - forEachCalled bool + shouldErrorForEach bool + shouldErrorLoadComplete bool + forEachCalled bool } func (m *MockLocalPackageShowCollection) ForEach(handler func(*deb.LocalRepo) error) error { @@ -352,7 +354,7 @@ func (m *MockLocalPackageShowCollection) ForEach(handler func(*deb.LocalRepo) er // Create mock repo - simplified repo := &deb.LocalRepo{Name: "test-local-repo"} // Note: Removed access to unexported fields - + return handler(repo) } @@ -364,9 +366,9 @@ func (m *MockLocalPackageShowCollection) LoadComplete(repo *deb.LocalRepo) error } type MockSnapshotPackageShowCollection struct { - shouldErrorForEach bool - shouldErrorLoadComplete bool - forEachCalled bool + shouldErrorForEach bool + shouldErrorLoadComplete bool + forEachCalled bool } func (m *MockSnapshotPackageShowCollection) ForEach(handler func(*deb.Snapshot) error) error { @@ -378,7 +380,7 @@ func (m *MockSnapshotPackageShowCollection) ForEach(handler func(*deb.Snapshot) // Create mock snapshot - simplified snapshot := &deb.Snapshot{Name: "test-snapshot"} // Note: Removed access to unexported fields - + return handler(snapshot) } @@ -397,11 +399,11 @@ type MockPackageQueryCollection struct { func (m *MockPackageQueryCollection) Query(query deb.PackageQuery) *deb.PackageList { m.queryCalled = true - + // Return a simple mock package list packageList := &deb.PackageList{} // Note: Simplified to avoid method assignment issues - + return packageList } @@ -469,4 +471,4 @@ func (m *MockLocalPackageShowPool) FullPath(relativePath string) string { } // Note: Removed method definitions on non-local types and global function overrides -// to fix compilation errors. Tests are simplified to focus on basic functionality. \ No newline at end of file +// to fix compilation errors. Tests are simplified to focus on basic functionality. diff --git a/cmd/publish_list_test.go b/cmd/publish_list_test.go index 361586c3..33fa0aa2 100644 --- a/cmd/publish_list_test.go +++ b/cmd/publish_list_test.go @@ -195,7 +195,7 @@ func (s *PublishListSuite) TestAptlyPublishListSorting(c *C) { foundMessages = append(foundMessages, msg) } } - + c.Check(len(foundMessages) >= 3, Equals, true) // Should be in alphabetical order c.Check(strings.Contains(foundMessages[0], "a-repo"), Equals, true) @@ -305,19 +305,21 @@ type MockPublishListContext struct { collectionFactory *deb.CollectionFactory } -func (m *MockPublishListContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockPublishListContext) Progress() aptly.Progress { return m.progress } -func (m *MockPublishListContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockPublishListContext) CloseDatabase() error { return nil } +func (m *MockPublishListContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockPublishListContext) Progress() aptly.Progress { return m.progress } +func (m *MockPublishListContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockPublishListContext) CloseDatabase() error { return nil } type MockPublishedListRepoCollection struct { - emptyCollection bool - shouldErrorForEach bool - shouldErrorLoadShallow bool - shouldErrorLoadComplete bool - causeMarshalError bool - multipleRepos bool - repoNames []string + emptyCollection bool + shouldErrorForEach bool + shouldErrorLoadShallow bool + shouldErrorLoadComplete bool + causeMarshalError bool + multipleRepos bool + repoNames []string } func (m *MockPublishedListRepoCollection) Len() int { @@ -356,12 +358,12 @@ func (m *MockPublishedListRepoCollection) ForEach(handler func(*deb.PublishedRep Prefix: "ppa", // Note: Removed components field as it's not exported } - + // Create problematic repo for marshal error testing if m.causeMarshalError { // Note: Removed TestCyclicRef as it doesn't exist } - + return handler(repo) } @@ -408,7 +410,7 @@ func (s *PublishListSuite) TestStderrOutput(c *C) { err := aptlyPublishList(s.cmd, []string{}) c.Check(err, NotNil) - + // The error should propagate from LoadShallow c.Check(err.Error(), Matches, ".*mock load shallow error.*") -} \ No newline at end of file +} diff --git a/cmd/publish_show_test.go b/cmd/publish_show_test.go index 4cbdad9c..7057120b 100644 --- a/cmd/publish_show_test.go +++ b/cmd/publish_show_test.go @@ -49,7 +49,7 @@ func (s *PublishShowSuite) TestMakeCmdPublishShow(c *C) { func (s *PublishShowSuite) TestPublishShowFlags(c *C) { err := s.cmd.Flag.Set("type", "local") c.Check(err, IsNil) - + err = s.cmd.Flag.Set("distribution", "stable") c.Check(err, IsNil) } @@ -58,18 +58,18 @@ func (s *PublishShowSuite) TestPublishShowFlags(c *C) { type MockPublishShowProgress struct{} -func (m *MockPublishShowProgress) Printf(msg string, a ...interface{}) {} -func (m *MockPublishShowProgress) ColoredPrintf(msg string, a ...interface{}) {} -func (m *MockPublishShowProgress) PrintfStdErr(msg string, a ...interface{}) {} -func (m *MockPublishShowProgress) Flush() {} -func (m *MockPublishShowProgress) Start() {} -func (m *MockPublishShowProgress) Shutdown() {} +func (m *MockPublishShowProgress) Printf(msg string, a ...interface{}) {} +func (m *MockPublishShowProgress) ColoredPrintf(msg string, a ...interface{}) {} +func (m *MockPublishShowProgress) PrintfStdErr(msg string, a ...interface{}) {} +func (m *MockPublishShowProgress) Flush() {} +func (m *MockPublishShowProgress) Start() {} +func (m *MockPublishShowProgress) Shutdown() {} func (m *MockPublishShowProgress) InitBar(count int64, isBytes bool, barType aptly.BarType) {} -func (m *MockPublishShowProgress) ShutdownBar() {} -func (m *MockPublishShowProgress) AddBar(count int) {} -func (m *MockPublishShowProgress) SetBar(count int) {} -func (m *MockPublishShowProgress) PrintfBar(msg string, a ...interface{}) {} -func (m *MockPublishShowProgress) Write(p []byte) (n int, err error) { return len(p), nil } +func (m *MockPublishShowProgress) ShutdownBar() {} +func (m *MockPublishShowProgress) AddBar(count int) {} +func (m *MockPublishShowProgress) SetBar(count int) {} +func (m *MockPublishShowProgress) PrintfBar(msg string, a ...interface{}) {} +func (m *MockPublishShowProgress) Write(p []byte) (n int, err error) { return len(p), nil } type MockPublishShowContext struct { flags *flag.FlagSet @@ -77,9 +77,11 @@ type MockPublishShowContext struct { collectionFactory *deb.CollectionFactory } -func (m *MockPublishShowContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockPublishShowContext) Progress() aptly.Progress { return m.progress } -func (m *MockPublishShowContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockPublishShowContext) Config() *utils.ConfigStructure { return &utils.ConfigStructure{} } +func (m *MockPublishShowContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockPublishShowContext) Progress() aptly.Progress { return m.progress } +func (m *MockPublishShowContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockPublishShowContext) Config() *utils.ConfigStructure { return &utils.ConfigStructure{} } -// Note: Complex integration tests have been simplified for compilation compatibility. \ No newline at end of file +// Note: Complex integration tests have been simplified for compilation compatibility. diff --git a/cmd/publish_snapshot_test.go b/cmd/publish_snapshot_test.go index b3b45206..78e59b2d 100644 --- a/cmd/publish_snapshot_test.go +++ b/cmd/publish_snapshot_test.go @@ -24,7 +24,7 @@ var _ = Suite(&PublishSnapshotSuite{}) func (s *PublishSnapshotSuite) SetUpTest(c *C) { s.cmd = makeCmdPublishSnapshot() s.mockProgress = &MockPublishProgress{} - + // Set up mock collections - simplified s.collectionFactory = deb.NewCollectionFactory(nil) @@ -138,7 +138,7 @@ func (s *PublishSnapshotSuite) TestAptlyPublishMultiComponent(c *C) { func (s *PublishSnapshotSuite) TestAptlyPublishInvalidArguments(c *C) { // Test with invalid number of arguments - simplified test s.cmd.Flag.Set("component", "main,contrib") - + // Too few arguments args := []string{"only-one-snapshot"} err := aptlyPublishSnapshotOrRepo(s.cmd, args) @@ -350,7 +350,7 @@ func (s *PublishSnapshotSuite) TestAptlyPublishOutputFormatting(c *C) { func (s *PublishSnapshotSuite) TestAptlyPublishSourceArchitecture(c *C) { // Test publishing with source architecture - simplified test s.mockContext.architecturesList = []string{"amd64", "source"} - + args := []string{"test-snapshot"} err := aptlyPublishSnapshotOrRepo(s.cmd, args) // Note: Actual behavior depends on real implementation @@ -426,26 +426,28 @@ func (m *MockPublishProgress) Write(data []byte) (int, error) { } type MockPublishContext struct { - flags *flag.FlagSet - progress *MockPublishProgress - collectionFactory *deb.CollectionFactory - architecturesList []string - packagePool aptly.PackagePool - config *MockPublishConfig - publishedStorage aptly.PublishedStorage - skelPath string + flags *flag.FlagSet + progress *MockPublishProgress + collectionFactory *deb.CollectionFactory + architecturesList []string + packagePool aptly.PackagePool + config *MockPublishConfig + publishedStorage aptly.PublishedStorage + skelPath string shouldErrorNewPublishedRepo bool - shouldErrorGetSigner bool - shouldErrorPublish bool + shouldErrorGetSigner bool + shouldErrorPublish bool } -func (m *MockPublishContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockPublishContext) Progress() aptly.Progress { return m.progress } -func (m *MockPublishContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockPublishContext) ArchitecturesList() []string { return m.architecturesList } -func (m *MockPublishContext) PackagePool() aptly.PackagePool { return m.packagePool } -func (m *MockPublishContext) Config() *utils.ConfigStructure { return &utils.ConfigStructure{} } -func (m *MockPublishContext) SkelPath() string { return m.skelPath } +func (m *MockPublishContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockPublishContext) Progress() aptly.Progress { return m.progress } +func (m *MockPublishContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockPublishContext) ArchitecturesList() []string { return m.architecturesList } +func (m *MockPublishContext) PackagePool() aptly.PackagePool { return m.packagePool } +func (m *MockPublishContext) Config() *utils.ConfigStructure { return &utils.ConfigStructure{} } +func (m *MockPublishContext) SkelPath() string { return m.skelPath } func (m *MockPublishContext) GetPublishedStorage(name string) aptly.PublishedStorage { return m.publishedStorage @@ -491,8 +493,8 @@ func (m *MockPublishPackagePool) LegacyPath(filename string, checksums *utils.Ch } type MockSnapshotPublishCollection struct { - shouldErrorByName bool - shouldErrorLoadComplete bool + shouldErrorByName bool + shouldErrorLoadComplete bool emptySnapshots bool } @@ -514,7 +516,7 @@ func (m *MockSnapshotPublishCollection) LoadComplete(snapshot *deb.Snapshot) err type MockLocalRepoPublishCollection struct { shouldErrorByName bool shouldErrorLoadComplete bool - emptyRepos bool + emptyRepos bool } func (m *MockLocalRepoPublishCollection) ByName(name string) (*deb.LocalRepo, error) { @@ -533,8 +535,8 @@ func (m *MockLocalRepoPublishCollection) LoadComplete(repo *deb.LocalRepo) error } type MockPublishedRepoPublishCollection struct { - hasDuplicate bool - shouldErrorAdd bool + hasDuplicate bool + shouldErrorAdd bool } func (m *MockPublishedRepoPublishCollection) CheckDuplicate(published *deb.PublishedRepo) *deb.PublishedRepo { @@ -611,4 +613,4 @@ func (m *MockPublishedStorage) ReadLink(path string) (string, error) { func (m *MockPublishedStorage) SymLink(src, dst string) error { return nil -} \ No newline at end of file +} diff --git a/cmd/publish_source_add_test.go b/cmd/publish_source_add_test.go index f0f1dc74..e5d54085 100644 --- a/cmd/publish_source_add_test.go +++ b/cmd/publish_source_add_test.go @@ -33,7 +33,7 @@ func (s *PublishSourceAddSuite) setupMockContext(c *C) { flags := flag.NewFlagSet("test", flag.ContinueOnError) flags.String("prefix", ".", "publishing prefix") flags.String("component", "", "component names to add") - + err := InitContext(flags) c.Assert(err, IsNil) } @@ -59,7 +59,7 @@ func (s *PublishSourceAddSuite) TestMakeCmdPublishSourceAdd(c *C) { func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddInvalidArgs(c *C) { // Test with insufficient arguments s.setupMockContext(c) - + err := aptlyPublishSourceAdd(s.cmd, []string{}) c.Check(err, Equals, commander.ErrCommandError) @@ -70,7 +70,7 @@ func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddInvalidArgs(c *C) { func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddBasic(c *C) { // Test basic source addition s.setupMockContext(c) - + context.Flags().Set("component", "contrib") args := []string{"stable", "contrib-snapshot"} @@ -82,7 +82,7 @@ func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddBasic(c *C) { func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddMismatchComponents(c *C) { // Test with mismatched number of components and sources s.setupMockContext(c) - + context.Flags().Set("component", "main,contrib") args := []string{"stable", "single-source"} // 2 components, 1 source @@ -94,7 +94,7 @@ func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddMismatchComponents(c *C func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddMultipleComponents(c *C) { // Test with multiple components s.setupMockContext(c) - + context.Flags().Set("component", "main,contrib,non-free") args := []string{"stable", "main-snapshot", "contrib-snapshot", "non-free-snapshot"} @@ -106,7 +106,7 @@ func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddMultipleComponents(c *C func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddWithPrefix(c *C) { // Test with custom prefix s.setupMockContext(c) - + context.Flags().Set("prefix", "ppa") context.Flags().Set("component", "contrib") args := []string{"stable", "contrib-snapshot"} @@ -119,7 +119,7 @@ func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddWithPrefix(c *C) { func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddWithStorage(c *C) { // Test with storage endpoint s.setupMockContext(c) - + context.Flags().Set("prefix", "s3:bucket") context.Flags().Set("component", "contrib") args := []string{"stable", "contrib-snapshot"} @@ -132,7 +132,7 @@ func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddWithStorage(c *C) { func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddRepoNotFound(c *C) { // Test with non-existent published repository s.setupMockContext(c) - + context.Flags().Set("component", "contrib") args := []string{"nonexistent-dist", "contrib-snapshot"} @@ -144,7 +144,7 @@ func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddRepoNotFound(c *C) { func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddEmptyComponent(c *C) { // Test with empty component flag s.setupMockContext(c) - + context.Flags().Set("component", "") args := []string{"stable", "contrib-snapshot"} @@ -152,4 +152,3 @@ func (s *PublishSourceAddSuite) TestAptlyPublishSourceAddEmptyComponent(c *C) { c.Check(err, NotNil) c.Check(err.Error(), Matches, ".*mismatch in number of components.*") } - diff --git a/cmd/publish_source_list_test.go b/cmd/publish_source_list_test.go index f42def53..1ea85ae0 100644 --- a/cmd/publish_source_list_test.go +++ b/cmd/publish_source_list_test.go @@ -181,7 +181,7 @@ func (s *PublishSourceListSuite) TestAptlyPublishSourceListPrefixParsing(c *C) { func (s *PublishSourceListSuite) TestAptlyPublishSourceListFormatToggle(c *C) { // Test switching between text and JSON formats formats := []struct { - jsonFlag bool + jsonFlag bool description string }{ {false, "text format"}, @@ -282,16 +282,16 @@ func (m *MockPublishSourceListProgress) Printf(msg string, a ...interface{}) { m.Messages = append(m.Messages, formatted) } -func (m *MockPublishSourceListProgress) AddBar(count int) {} -func (m *MockPublishSourceListProgress) Flush() {} +func (m *MockPublishSourceListProgress) AddBar(count int) {} +func (m *MockPublishSourceListProgress) Flush() {} func (m *MockPublishSourceListProgress) InitBar(total int64, colored bool, barType aptly.BarType) {} -func (m *MockPublishSourceListProgress) PrintfStdErr(msg string, a ...interface{}) {} -func (m *MockPublishSourceListProgress) SetBar(count int) {} -func (m *MockPublishSourceListProgress) Shutdown() {} -func (m *MockPublishSourceListProgress) ShutdownBar() {} -func (m *MockPublishSourceListProgress) Start() {} -func (m *MockPublishSourceListProgress) Write(data []byte) (int, error) { return len(data), nil } -func (m *MockPublishSourceListProgress) ColoredPrintf(msg string, a ...interface{}) {} +func (m *MockPublishSourceListProgress) PrintfStdErr(msg string, a ...interface{}) {} +func (m *MockPublishSourceListProgress) SetBar(count int) {} +func (m *MockPublishSourceListProgress) Shutdown() {} +func (m *MockPublishSourceListProgress) ShutdownBar() {} +func (m *MockPublishSourceListProgress) Start() {} +func (m *MockPublishSourceListProgress) Write(data []byte) (int, error) { return len(data), nil } +func (m *MockPublishSourceListProgress) ColoredPrintf(msg string, a ...interface{}) {} type MockPublishSourceListContext struct { flags *flag.FlagSet @@ -299,9 +299,11 @@ type MockPublishSourceListContext struct { collectionFactory *deb.CollectionFactory } -func (m *MockPublishSourceListContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockPublishSourceListContext) Progress() aptly.Progress { return m.progress } -func (m *MockPublishSourceListContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } +func (m *MockPublishSourceListContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockPublishSourceListContext) Progress() aptly.Progress { return m.progress } +func (m *MockPublishSourceListContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} type MockPublishedSourceListRepoCollection struct { shouldErrorByStoragePrefixDistribution bool @@ -536,4 +538,4 @@ func (s *PublishSourceListSuite) TestJSONMarshalError(c *C) { c.Check(err.Error(), Matches, ".*unable to list.*") } -// Note: Removed mock revision type to simplify compilation \ No newline at end of file +// Note: Removed mock revision type to simplify compilation diff --git a/cmd/publish_source_replace_test.go b/cmd/publish_source_replace_test.go index c7e71941..9e837a10 100644 --- a/cmd/publish_source_replace_test.go +++ b/cmd/publish_source_replace_test.go @@ -252,7 +252,7 @@ func (s *PublishSourceReplaceSuite) TestAptlyPublishSourceReplaceComponentValida {"main,contrib", []string{"main-source", "contrib-source"}, false}, {"main,contrib,non-free", []string{"main-source", "contrib-source", "non-free-source"}, false}, {"main,contrib", []string{"single-source"}, true}, // Mismatch - {"", []string{"source"}, true}, // Empty component + {"", []string{"source"}, true}, // Empty component } for _, test := range componentTests { @@ -393,14 +393,15 @@ func (m *MockPublishSourceReplaceProgress) Printf(msg string, a ...interface{}) } func (m *MockPublishSourceReplaceProgress) AddBar(count int) {} -func (m *MockPublishSourceReplaceProgress) Flush() {} -func (m *MockPublishSourceReplaceProgress) InitBar(total int64, colored bool, barType aptly.BarType) {} -func (m *MockPublishSourceReplaceProgress) PrintfStdErr(msg string, a ...interface{}) {} -func (m *MockPublishSourceReplaceProgress) SetBar(count int) {} -func (m *MockPublishSourceReplaceProgress) Shutdown() {} -func (m *MockPublishSourceReplaceProgress) ShutdownBar() {} -func (m *MockPublishSourceReplaceProgress) Start() {} -func (m *MockPublishSourceReplaceProgress) Write(data []byte) (int, error) { return len(data), nil } +func (m *MockPublishSourceReplaceProgress) Flush() {} +func (m *MockPublishSourceReplaceProgress) InitBar(total int64, colored bool, barType aptly.BarType) { +} +func (m *MockPublishSourceReplaceProgress) PrintfStdErr(msg string, a ...interface{}) {} +func (m *MockPublishSourceReplaceProgress) SetBar(count int) {} +func (m *MockPublishSourceReplaceProgress) Shutdown() {} +func (m *MockPublishSourceReplaceProgress) ShutdownBar() {} +func (m *MockPublishSourceReplaceProgress) Start() {} +func (m *MockPublishSourceReplaceProgress) Write(data []byte) (int, error) { return len(data), nil } func (m *MockPublishSourceReplaceProgress) ColoredPrintf(msg string, a ...interface{}) {} type MockPublishSourceReplaceContext struct { @@ -409,9 +410,11 @@ type MockPublishSourceReplaceContext struct { collectionFactory *deb.CollectionFactory } -func (m *MockPublishSourceReplaceContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockPublishSourceReplaceContext) Progress() aptly.Progress { return m.progress } -func (m *MockPublishSourceReplaceContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } +func (m *MockPublishSourceReplaceContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockPublishSourceReplaceContext) Progress() aptly.Progress { return m.progress } +func (m *MockPublishSourceReplaceContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} type MockPublishedSourceReplaceRepoCollection struct { shouldErrorByStoragePrefixDistribution bool @@ -591,4 +594,4 @@ func (s *PublishSourceReplaceSuite) TestSpecialCharactersInComponents(c *C) { } } c.Check(addingCount, Equals, 3) -} \ No newline at end of file +} diff --git a/cmd/publish_source_update_test.go b/cmd/publish_source_update_test.go index 44da4db2..317a2720 100644 --- a/cmd/publish_source_update_test.go +++ b/cmd/publish_source_update_test.go @@ -257,7 +257,7 @@ func (s *PublishSourceUpdateSuite) TestAptlyPublishSourceUpdateComponentValidati {"main,contrib", []string{"main-source", "contrib-source"}, false}, {"main,contrib,non-free", []string{"main-source", "contrib-source", "non-free-source"}, false}, {"main,contrib", []string{"single-source"}, true}, // Mismatch - {"", []string{"source"}, true}, // Empty component + {"", []string{"source"}, true}, // Empty component } for _, test := range componentTests { @@ -390,16 +390,16 @@ func (m *MockPublishSourceUpdateProgress) Printf(msg string, a ...interface{}) { m.Messages = append(m.Messages, formatted) } -func (m *MockPublishSourceUpdateProgress) AddBar(count int) {} -func (m *MockPublishSourceUpdateProgress) Flush() {} +func (m *MockPublishSourceUpdateProgress) AddBar(count int) {} +func (m *MockPublishSourceUpdateProgress) Flush() {} func (m *MockPublishSourceUpdateProgress) InitBar(total int64, colored bool, barType aptly.BarType) {} -func (m *MockPublishSourceUpdateProgress) PrintfStdErr(msg string, a ...interface{}) {} -func (m *MockPublishSourceUpdateProgress) SetBar(count int) {} -func (m *MockPublishSourceUpdateProgress) Shutdown() {} -func (m *MockPublishSourceUpdateProgress) ShutdownBar() {} -func (m *MockPublishSourceUpdateProgress) Start() {} -func (m *MockPublishSourceUpdateProgress) Write(data []byte) (int, error) { return len(data), nil } -func (m *MockPublishSourceUpdateProgress) ColoredPrintf(msg string, a ...interface{}) {} +func (m *MockPublishSourceUpdateProgress) PrintfStdErr(msg string, a ...interface{}) {} +func (m *MockPublishSourceUpdateProgress) SetBar(count int) {} +func (m *MockPublishSourceUpdateProgress) Shutdown() {} +func (m *MockPublishSourceUpdateProgress) ShutdownBar() {} +func (m *MockPublishSourceUpdateProgress) Start() {} +func (m *MockPublishSourceUpdateProgress) Write(data []byte) (int, error) { return len(data), nil } +func (m *MockPublishSourceUpdateProgress) ColoredPrintf(msg string, a ...interface{}) {} type MockPublishSourceUpdateContext struct { flags *flag.FlagSet @@ -407,9 +407,11 @@ type MockPublishSourceUpdateContext struct { collectionFactory *deb.CollectionFactory } -func (m *MockPublishSourceUpdateContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockPublishSourceUpdateContext) Progress() aptly.Progress { return m.progress } -func (m *MockPublishSourceUpdateContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } +func (m *MockPublishSourceUpdateContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockPublishSourceUpdateContext) Progress() aptly.Progress { return m.progress } +func (m *MockPublishSourceUpdateContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} type MockPublishedSourceUpdateRepoCollection struct { shouldErrorByStoragePrefixDistribution bool @@ -579,4 +581,4 @@ func (s *PublishSourceUpdateSuite) TestErrorHandlingRobustness(c *C) { c.Check(err, NotNil, Commentf("Scenario: %s", scenario.description)) c.Check(err.Error(), Matches, ".*"+scenario.expectedErr+".*", Commentf("Scenario: %s", scenario.description)) } -} \ No newline at end of file +} diff --git a/cmd/publish_switch_test.go b/cmd/publish_switch_test.go index dcea3c8e..770419f0 100644 --- a/cmd/publish_switch_test.go +++ b/cmd/publish_switch_test.go @@ -313,8 +313,8 @@ func (s *PublishSwitchSuite) TestAptlyPublishSwitchEmptyComponentDefault(c *C) { func (s *PublishSwitchSuite) TestAptlyPublishSwitchPrefixParsing(c *C) { // Test different prefix formats prefixTests := [][]string{ - {"wheezy", ".", "wheezy-snapshot"}, // Default prefix - {"wheezy", "ppa", "wheezy-snapshot"}, // Simple prefix + {"wheezy", ".", "wheezy-snapshot"}, // Default prefix + {"wheezy", "ppa", "wheezy-snapshot"}, // Simple prefix {"wheezy", "s3:bucket", "wheezy-snapshot"}, // Storage with prefix } @@ -340,21 +340,20 @@ func (m *MockPublishSwitchProgress) Printf(msg string, a ...interface{}) { m.Messages = append(m.Messages, formatted) } -func (m *MockPublishSwitchProgress) AddBar(count int) {} -func (m *MockPublishSwitchProgress) Flush() {} +func (m *MockPublishSwitchProgress) AddBar(count int) {} +func (m *MockPublishSwitchProgress) Flush() {} func (m *MockPublishSwitchProgress) InitBar(total int64, colored bool, barType aptly.BarType) {} -func (m *MockPublishSwitchProgress) PrintfStdErr(msg string, a ...interface{}) {} -func (m *MockPublishSwitchProgress) SetBar(count int) {} -func (m *MockPublishSwitchProgress) Shutdown() {} -func (m *MockPublishSwitchProgress) ShutdownBar() {} -func (m *MockPublishSwitchProgress) Start() {} -func (m *MockPublishSwitchProgress) Write(data []byte) (int, error) { return len(data), nil } +func (m *MockPublishSwitchProgress) PrintfStdErr(msg string, a ...interface{}) {} +func (m *MockPublishSwitchProgress) SetBar(count int) {} +func (m *MockPublishSwitchProgress) Shutdown() {} +func (m *MockPublishSwitchProgress) ShutdownBar() {} +func (m *MockPublishSwitchProgress) Start() {} +func (m *MockPublishSwitchProgress) Write(data []byte) (int, error) { return len(data), nil } func (m *MockPublishSwitchProgress) ColoredPrintf(msg string, a ...interface{}) { formatted := fmt.Sprintf(msg, a...) m.ColoredMessages = append(m.ColoredMessages, formatted) } - type MockPublishSwitchContext struct { flags *flag.FlagSet progress *MockPublishSwitchProgress @@ -364,22 +363,34 @@ type MockPublishSwitchContext struct { signerError bool } -func (m *MockPublishSwitchContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockPublishSwitchContext) Progress() aptly.Progress { return m.progress } -func (m *MockPublishSwitchContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockPublishSwitchContext) PackagePool() aptly.PackagePool { return m.packagePool } -func (m *MockPublishSwitchContext) SkelPath() string { return m.skelPath } +func (m *MockPublishSwitchContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockPublishSwitchContext) Progress() aptly.Progress { return m.progress } +func (m *MockPublishSwitchContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockPublishSwitchContext) PackagePool() aptly.PackagePool { return m.packagePool } +func (m *MockPublishSwitchContext) SkelPath() string { return m.skelPath } type MockPublishSwitchPackagePool struct{} func (m *MockPublishSwitchPackagePool) GeneratePackageRefs() []string { return []string{} } -func (m *MockPublishSwitchPackagePool) FilepathList(progress aptly.Progress) ([]string, error) { return []string{}, nil } -func (m *MockPublishSwitchPackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, move bool, storage aptly.ChecksumStorage) (string, error) { return "/pool/path", nil } -func (m *MockPublishSwitchPackagePool) Verify(poolPath, basename string, checksums *utils.ChecksumInfo, checksumStorage aptly.ChecksumStorage) (string, bool, error) { return poolPath, true, nil } -func (m *MockPublishSwitchPackagePool) Open(filename string) (aptly.ReadSeekerCloser, error) { return nil, nil } +func (m *MockPublishSwitchPackagePool) FilepathList(progress aptly.Progress) ([]string, error) { + return []string{}, nil +} +func (m *MockPublishSwitchPackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, move bool, storage aptly.ChecksumStorage) (string, error) { + return "/pool/path", nil +} +func (m *MockPublishSwitchPackagePool) Verify(poolPath, basename string, checksums *utils.ChecksumInfo, checksumStorage aptly.ChecksumStorage) (string, bool, error) { + return poolPath, true, nil +} +func (m *MockPublishSwitchPackagePool) Open(filename string) (aptly.ReadSeekerCloser, error) { + return nil, nil +} func (m *MockPublishSwitchPackagePool) Remove(filename string) (int64, error) { return 0, nil } -func (m *MockPublishSwitchPackagePool) Size(prefix string) (int64, error) { return 0, nil } -func (m *MockPublishSwitchPackagePool) LegacyPath(filename string, checksums *utils.ChecksumInfo) (string, error) { return "/legacy/" + filename, nil } +func (m *MockPublishSwitchPackagePool) Size(prefix string) (int64, error) { return 0, nil } +func (m *MockPublishSwitchPackagePool) LegacyPath(filename string, checksums *utils.ChecksumInfo) (string, error) { + return "/legacy/" + filename, nil +} type MockPublishedSwitchRepoCollection struct { shouldErrorByStoragePrefixDistribution bool @@ -434,8 +445,8 @@ func (m *MockPublishedSwitchRepoCollection) CleanupPrefixComponentFiles(publishe } type MockSnapshotSwitchCollection struct { - shouldErrorByName bool - shouldErrorLoadComplete bool + shouldErrorByName bool + shouldErrorLoadComplete bool } func (m *MockSnapshotSwitchCollection) ByName(name string) (*deb.Snapshot, error) { @@ -459,14 +470,14 @@ func (m *MockSnapshotSwitchCollection) LoadComplete(snapshot *deb.Snapshot) erro type MockPublishSwitchSigner struct{} -func (m *MockPublishSwitchSigner) SetKey(keyRef string) {} -func (m *MockPublishSwitchSigner) SetKeyRing(keyring, secretKeyring string) {} +func (m *MockPublishSwitchSigner) SetKey(keyRef string) {} +func (m *MockPublishSwitchSigner) SetKeyRing(keyring, secretKeyring string) {} func (m *MockPublishSwitchSigner) SetPassphrase(passphrase, passphraseFile string) {} -func (m *MockPublishSwitchSigner) SetBatch(batch bool) {} +func (m *MockPublishSwitchSigner) SetBatch(batch bool) {} // Mock utils.StrSliceHasItem function func init() { // Note: Removed package-level function assignment } -// Note: Removed package-level function assignment to fix compilation errors \ No newline at end of file +// Note: Removed package-level function assignment to fix compilation errors diff --git a/cmd/publish_test.go b/cmd/publish_test.go index 59900e10..e6e7333b 100644 --- a/cmd/publish_test.go +++ b/cmd/publish_test.go @@ -44,7 +44,7 @@ func (s *PublishSuite) setupMockContext(c *C) { flags.Bool("batch", false, "batch mode") flags.String("architectures", "", "architectures") flags.Bool("multi-dist", false, "multi distribution") - + err := InitContext(flags) c.Assert(err, IsNil) } @@ -52,13 +52,13 @@ func (s *PublishSuite) setupMockContext(c *C) { func (s *PublishSuite) TestMakeCmdPublishSnapshot(c *C) { // Test makeCmdPublishSnapshot command creation cmd := makeCmdPublishSnapshot() - + c.Check(cmd, NotNil) c.Check(cmd.Run, NotNil) c.Check(cmd.UsageLine, Equals, "snapshot [[:]]") c.Check(cmd.Short, Equals, "publish snapshot") c.Check(cmd.Long, Not(Equals), "") - + // Check that expected flags are present c.Check(cmd.Flag.Lookup("distribution"), NotNil) c.Check(cmd.Flag.Lookup("component"), NotNil) @@ -71,13 +71,13 @@ func (s *PublishSuite) TestMakeCmdPublishSnapshot(c *C) { func (s *PublishSuite) TestMakeCmdPublishRepo(c *C) { // Test makeCmdPublishRepo command creation cmd := makeCmdPublishRepo() - + c.Check(cmd, NotNil) c.Check(cmd.Run, NotNil) c.Check(cmd.UsageLine, Equals, "repo [[:]]") c.Check(cmd.Short, Equals, "publish local repository") c.Check(cmd.Long, Not(Equals), "") - + // Should use the same function as snapshot but different name c.Check(cmd.Run, Equals, aptlyPublishSnapshotOrRepo) } @@ -85,7 +85,7 @@ func (s *PublishSuite) TestMakeCmdPublishRepo(c *C) { func (s *PublishSuite) TestPublishSnapshotOrRepoNoArgs(c *C) { // Test aptlyPublishSnapshotOrRepo with no arguments s.setupMockContext(c) - + cmd := makeCmdPublishSnapshot() err := aptlyPublishSnapshotOrRepo(cmd, []string{}) c.Check(err, Equals, commander.ErrCommandError) @@ -94,11 +94,11 @@ func (s *PublishSuite) TestPublishSnapshotOrRepoNoArgs(c *C) { func (s *PublishSuite) TestPublishSnapshotOrRepoTooManyArgs(c *C) { // Test aptlyPublishSnapshotOrRepo with too many arguments s.setupMockContext(c) - + cmd := makeCmdPublishSnapshot() // Set component to "main" which means we expect 1 snapshot + optional prefix context.Flags().Set("component", "main") - + // Too many args: 3 args when we expect max 2 (1 snapshot + 1 prefix) err := aptlyPublishSnapshotOrRepo(cmd, []string{"snap1", "snap2", "snap3"}) c.Check(err, Equals, commander.ErrCommandError) @@ -107,15 +107,15 @@ func (s *PublishSuite) TestPublishSnapshotOrRepoTooManyArgs(c *C) { func (s *PublishSuite) TestPublishSnapshotOrRepoMultiComponent(c *C) { // Test aptlyPublishSnapshotOrRepo with multiple components s.setupMockContext(c) - + cmd := makeCmdPublishSnapshot() // Set multiple components context.Flags().Set("component", "main,contrib,non-free") - + // Should expect 3 snapshots (one per component) err := aptlyPublishSnapshotOrRepo(cmd, []string{"snap1"}) // Too few c.Check(err, Equals, commander.ErrCommandError) - + err = aptlyPublishSnapshotOrRepo(cmd, []string{"snap1", "snap2", "snap3", "snap4", "snap5"}) // Too many c.Check(err, Equals, commander.ErrCommandError) } @@ -123,13 +123,13 @@ func (s *PublishSuite) TestPublishSnapshotOrRepoMultiComponent(c *C) { func (s *PublishSuite) TestMakeCmdPublishList(c *C) { // Test makeCmdPublishList command creation cmd := makeCmdPublishList() - + c.Check(cmd, NotNil) c.Check(cmd.Run, NotNil) c.Check(cmd.UsageLine, Equals, "list") c.Check(cmd.Short, Equals, "list published repositories") c.Check(cmd.Long, Not(Equals), "") - + // Check that expected flags are present c.Check(cmd.Flag.Lookup("raw"), NotNil) } @@ -137,7 +137,7 @@ func (s *PublishSuite) TestMakeCmdPublishList(c *C) { func (s *PublishSuite) TestMakeCmdPublishShow(c *C) { // Test makeCmdPublishShow command creation cmd := makeCmdPublishShow() - + c.Check(cmd, NotNil) c.Check(cmd.Run, NotNil) c.Check(cmd.UsageLine, Equals, "show [[:]]") @@ -148,13 +148,13 @@ func (s *PublishSuite) TestMakeCmdPublishShow(c *C) { func (s *PublishSuite) TestMakeCmdPublishDrop(c *C) { // Test makeCmdPublishDrop command creation cmd := makeCmdPublishDrop() - + c.Check(cmd, NotNil) c.Check(cmd.Run, NotNil) c.Check(cmd.UsageLine, Equals, "drop [[:]]") c.Check(cmd.Short, Equals, "remove published repository") c.Check(cmd.Long, Not(Equals), "") - + // Check that expected flags are present c.Check(cmd.Flag.Lookup("force-drop"), NotNil) c.Check(cmd.Flag.Lookup("skip-cleanup"), NotNil) @@ -163,13 +163,13 @@ func (s *PublishSuite) TestMakeCmdPublishDrop(c *C) { func (s *PublishSuite) TestMakeCmdPublishUpdate(c *C) { // Test makeCmdPublishUpdate command creation cmd := makeCmdPublishUpdate() - + c.Check(cmd, NotNil) c.Check(cmd.Run, NotNil) c.Check(cmd.UsageLine, Equals, "update [[:]]") c.Check(cmd.Short, Equals, "update published repository") c.Check(cmd.Long, Not(Equals), "") - + // Check that expected flags are present c.Check(cmd.Flag.Lookup("force-overwrite"), NotNil) c.Check(cmd.Flag.Lookup("skip-signing"), NotNil) @@ -178,13 +178,13 @@ func (s *PublishSuite) TestMakeCmdPublishUpdate(c *C) { func (s *PublishSuite) TestMakeCmdPublishSwitch(c *C) { // Test makeCmdPublishSwitch command creation cmd := makeCmdPublishSwitch() - + c.Check(cmd, NotNil) c.Check(cmd.Run, NotNil) c.Check(cmd.UsageLine, Equals, "switch [:] [:] ... [[:]]") c.Check(cmd.Short, Equals, "update published repository by switching to new snapshot") c.Check(cmd.Long, Not(Equals), "") - + // Check that expected flags are present c.Check(cmd.Flag.Lookup("force-overwrite"), NotNil) c.Check(cmd.Flag.Lookup("skip-signing"), NotNil) @@ -194,7 +194,7 @@ func (s *PublishSuite) TestMakeCmdPublishSwitch(c *C) { func (s *PublishSuite) TestPublishListNoArgs(c *C) { // Test aptlyPublishList with no arguments (should work) s.setupMockContext(c) - + err := aptlyPublishList(makeCmdPublishList(), []string{}) // Will likely error due to no real collection factory, but tests structure c.Check(err, NotNil) @@ -203,7 +203,7 @@ func (s *PublishSuite) TestPublishListNoArgs(c *C) { func (s *PublishSuite) TestPublishListWithArgs(c *C) { // Test aptlyPublishList with arguments (should fail) s.setupMockContext(c) - + err := aptlyPublishList(makeCmdPublishList(), []string{"extra", "args"}) c.Check(err, Equals, commander.ErrCommandError) } @@ -211,7 +211,7 @@ func (s *PublishSuite) TestPublishListWithArgs(c *C) { func (s *PublishSuite) TestPublishShowNoArgs(c *C) { // Test aptlyPublishShow with no arguments s.setupMockContext(c) - + err := aptlyPublishShow(makeCmdPublishShow(), []string{}) c.Check(err, Equals, commander.ErrCommandError) } @@ -219,7 +219,7 @@ func (s *PublishSuite) TestPublishShowNoArgs(c *C) { func (s *PublishSuite) TestPublishDropNoArgs(c *C) { // Test aptlyPublishDrop with no arguments s.setupMockContext(c) - + err := aptlyPublishDrop(makeCmdPublishDrop(), []string{}) c.Check(err, Equals, commander.ErrCommandError) } @@ -227,7 +227,7 @@ func (s *PublishSuite) TestPublishDropNoArgs(c *C) { func (s *PublishSuite) TestPublishUpdateNoArgs(c *C) { // Test aptlyPublishUpdate with no arguments s.setupMockContext(c) - + err := aptlyPublishUpdate(makeCmdPublishUpdate(), []string{}) c.Check(err, Equals, commander.ErrCommandError) } @@ -235,10 +235,10 @@ func (s *PublishSuite) TestPublishUpdateNoArgs(c *C) { func (s *PublishSuite) TestPublishSwitchInsufficientArgs(c *C) { // Test aptlyPublishSwitch with insufficient arguments s.setupMockContext(c) - + err := aptlyPublishSwitch(makeCmdPublishSwitch(), []string{}) c.Check(err, Equals, commander.ErrCommandError) - + err = aptlyPublishSwitch(makeCmdPublishSwitch(), []string{"distribution-only"}) c.Check(err, Equals, commander.ErrCommandError) } @@ -254,7 +254,7 @@ func (s *PublishSuite) TestPublishCommandFlags(c *C) { makeCmdPublishUpdate(), makeCmdPublishSwitch(), } - + for _, cmd := range commands { c.Check(cmd, NotNil, Commentf("Command should not be nil: %s", cmd.Name())) c.Check(cmd.Run, NotNil, Commentf("Command run function should not be nil: %s", cmd.Name())) @@ -266,11 +266,11 @@ func (s *PublishSuite) TestPublishCommandFlags(c *C) { func (s *PublishSuite) TestPublishPrefixParsing(c *C) { // Test prefix parsing patterns used in publish commands - + // Mock prefix parsing similar to deb.ParsePrefix testCases := []struct { - input string - expectedStore string + input string + expectedStore string expectedPrefix string }{ {"", "", ""}, @@ -279,12 +279,12 @@ func (s *PublishSuite) TestPublishPrefixParsing(c *C) { {"s3:us-east-1:bucket/prefix", "s3:us-east-1", "bucket/prefix"}, {"filesystem:/path/to/dir", "filesystem", "/path/to/dir"}, } - + for _, tc := range testCases { // Simple parsing logic for testing parts := strings.SplitN(tc.input, ":", 2) var storage, prefix string - + if len(parts) == 1 { storage = "" prefix = parts[0] @@ -299,7 +299,7 @@ func (s *PublishSuite) TestPublishPrefixParsing(c *C) { prefix = parts[1] } } - + c.Check(storage, Equals, tc.expectedStore, Commentf("Input: %s", tc.input)) c.Check(prefix, Equals, tc.expectedPrefix, Commentf("Input: %s", tc.input)) } @@ -317,7 +317,7 @@ func (s *PublishSuite) TestComponentParsing(c *C) { {"", []string{""}}, {"single", []string{"single"}}, } - + for _, tc := range testCases { components := strings.Split(tc.input, ",") c.Check(components, DeepEquals, tc.expected, Commentf("Input: %s", tc.input)) @@ -327,7 +327,7 @@ func (s *PublishSuite) TestComponentParsing(c *C) { func (s *PublishSuite) TestPublishErrorHandling(c *C) { // Test error handling patterns in publish commands s.setupMockContext(c) - + // Test various error scenarios commands := []struct { name string @@ -343,7 +343,7 @@ func (s *PublishSuite) TestPublishErrorHandling(c *C) { {"publish update no args", makeCmdPublishUpdate(), aptlyPublishUpdate, []string{}, true}, {"publish switch no args", makeCmdPublishSwitch(), aptlyPublishSwitch, []string{}, true}, } - + for _, tc := range commands { err := tc.fn(tc.cmd, tc.args) if tc.wantErr { @@ -357,29 +357,29 @@ func (s *PublishSuite) TestPublishErrorHandling(c *C) { func (s *PublishSuite) TestPublishArgumentValidation(c *C) { // Test argument validation patterns s.setupMockContext(c) - + // Test component/argument count validation testCases := []struct { components string args []string valid bool }{ - {"main", []string{"snap1"}, true}, // 1 component, 1 snapshot - {"main", []string{"snap1", "prefix"}, true}, // 1 component, 1 snapshot + prefix - {"main", []string{}, false}, // 1 component, no snapshots - {"main", []string{"snap1", "snap2", "prefix"}, false}, // 1 component, too many args - {"main,contrib", []string{"snap1", "snap2"}, true}, // 2 components, 2 snapshots + {"main", []string{"snap1"}, true}, // 1 component, 1 snapshot + {"main", []string{"snap1", "prefix"}, true}, // 1 component, 1 snapshot + prefix + {"main", []string{}, false}, // 1 component, no snapshots + {"main", []string{"snap1", "snap2", "prefix"}, false}, // 1 component, too many args + {"main,contrib", []string{"snap1", "snap2"}, true}, // 2 components, 2 snapshots {"main,contrib", []string{"snap1", "snap2", "prefix"}, true}, // 2 components, 2 snapshots + prefix - {"main,contrib", []string{"snap1"}, false}, // 2 components, not enough snapshots + {"main,contrib", []string{"snap1"}, false}, // 2 components, not enough snapshots } - + for _, tc := range testCases { components := strings.Split(tc.components, ",") args := tc.args - + // Validation logic similar to aptlyPublishSnapshotOrRepo valid := len(args) >= len(components) && len(args) <= len(components)+1 - + c.Check(valid, Equals, tc.valid, Commentf("Components: %s, Args: %v", tc.components, tc.args)) } } @@ -394,7 +394,7 @@ func (s *PublishSuite) TestPublishSourceCommands(c *C) { makeCmdPublishSourceReplace(), makeCmdPublishSourceUpdate(), } - + for _, cmd := range sourceCommands { c.Check(cmd, NotNil) c.Check(cmd.Run, NotNil) @@ -402,4 +402,4 @@ func (s *PublishSuite) TestPublishSourceCommands(c *C) { c.Check(cmd.Short, Not(Equals), "") c.Check(strings.Contains(cmd.Short, "source"), Equals, true) } -} \ No newline at end of file +} diff --git a/cmd/publish_update_test.go b/cmd/publish_update_test.go index a68070ce..780b8c2a 100644 --- a/cmd/publish_update_test.go +++ b/cmd/publish_update_test.go @@ -251,10 +251,10 @@ func (s *PublishUpdateSuite) TestAptlyPublishUpdateWithUpdateResult(c *C) { func (s *PublishUpdateSuite) TestAptlyPublishUpdatePrefixParsing(c *C) { // Test different prefix formats prefixTests := [][]string{ - {"wheezy"}, // Default prefix - {"wheezy", "."}, // Explicit default prefix - {"wheezy", "ppa"}, // Simple prefix - {"wheezy", "s3:bucket"}, // Storage with prefix + {"wheezy"}, // Default prefix + {"wheezy", "."}, // Explicit default prefix + {"wheezy", "ppa"}, // Simple prefix + {"wheezy", "s3:bucket"}, // Storage with prefix } for _, args := range prefixTests { @@ -312,15 +312,15 @@ func (m *MockPublishUpdateProgress) ColoredPrintf(msg string, a ...interface{}) m.ColoredMessages = append(m.ColoredMessages, formatted) } -func (m *MockPublishUpdateProgress) AddBar(count int) {} -func (m *MockPublishUpdateProgress) Flush() {} +func (m *MockPublishUpdateProgress) AddBar(count int) {} +func (m *MockPublishUpdateProgress) Flush() {} func (m *MockPublishUpdateProgress) InitBar(total int64, colored bool, barType aptly.BarType) {} -func (m *MockPublishUpdateProgress) PrintfStdErr(msg string, a ...interface{}) {} -func (m *MockPublishUpdateProgress) SetBar(count int) {} -func (m *MockPublishUpdateProgress) Shutdown() {} -func (m *MockPublishUpdateProgress) ShutdownBar() {} -func (m *MockPublishUpdateProgress) Start() {} -func (m *MockPublishUpdateProgress) Write(data []byte) (int, error) { return len(data), nil } +func (m *MockPublishUpdateProgress) PrintfStdErr(msg string, a ...interface{}) {} +func (m *MockPublishUpdateProgress) SetBar(count int) {} +func (m *MockPublishUpdateProgress) Shutdown() {} +func (m *MockPublishUpdateProgress) ShutdownBar() {} +func (m *MockPublishUpdateProgress) Start() {} +func (m *MockPublishUpdateProgress) Write(data []byte) (int, error) { return len(data), nil } type MockPublishUpdateContext struct { flags *flag.FlagSet @@ -331,22 +331,34 @@ type MockPublishUpdateContext struct { signerError bool } -func (m *MockPublishUpdateContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockPublishUpdateContext) Progress() aptly.Progress { return m.progress } -func (m *MockPublishUpdateContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockPublishUpdateContext) PackagePool() aptly.PackagePool { return m.packagePool } -func (m *MockPublishUpdateContext) SkelPath() string { return m.skelPath } +func (m *MockPublishUpdateContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockPublishUpdateContext) Progress() aptly.Progress { return m.progress } +func (m *MockPublishUpdateContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockPublishUpdateContext) PackagePool() aptly.PackagePool { return m.packagePool } +func (m *MockPublishUpdateContext) SkelPath() string { return m.skelPath } type MockUpdatePackagePool struct{} func (m *MockUpdatePackagePool) GeneratePackageRefs() []string { return []string{} } -func (m *MockUpdatePackagePool) FilepathList(progress aptly.Progress) ([]string, error) { return []string{}, nil } -func (m *MockUpdatePackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, move bool, storage aptly.ChecksumStorage) (string, error) { return "/pool/path", nil } -func (m *MockUpdatePackagePool) Verify(poolPath, basename string, checksums *utils.ChecksumInfo, checksumStorage aptly.ChecksumStorage) (string, bool, error) { return poolPath, true, nil } -func (m *MockUpdatePackagePool) Open(filename string) (aptly.ReadSeekerCloser, error) { return nil, nil } +func (m *MockUpdatePackagePool) FilepathList(progress aptly.Progress) ([]string, error) { + return []string{}, nil +} +func (m *MockUpdatePackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, move bool, storage aptly.ChecksumStorage) (string, error) { + return "/pool/path", nil +} +func (m *MockUpdatePackagePool) Verify(poolPath, basename string, checksums *utils.ChecksumInfo, checksumStorage aptly.ChecksumStorage) (string, bool, error) { + return poolPath, true, nil +} +func (m *MockUpdatePackagePool) Open(filename string) (aptly.ReadSeekerCloser, error) { + return nil, nil +} func (m *MockUpdatePackagePool) Remove(filename string) (int64, error) { return 0, nil } -func (m *MockUpdatePackagePool) Size(prefix string) (int64, error) { return 0, nil } -func (m *MockUpdatePackagePool) LegacyPath(filename string, checksums *utils.ChecksumInfo) (string, error) { return "/legacy/" + filename, nil } +func (m *MockUpdatePackagePool) Size(prefix string) (int64, error) { return 0, nil } +func (m *MockUpdatePackagePool) LegacyPath(filename string, checksums *utils.ChecksumInfo) (string, error) { + return "/legacy/" + filename, nil +} type MockPublishedUpdateRepoCollection struct { shouldErrorByStoragePrefixDistribution bool @@ -404,12 +416,12 @@ func (m *MockPublishedUpdateRepoCollection) CleanupPrefixComponentFiles(publishe type MockUpdateSigner struct{} -func (m *MockUpdateSigner) SetKey(keyRef string) {} -func (m *MockUpdateSigner) SetKeyRing(keyring, secretKeyring string) {} +func (m *MockUpdateSigner) SetKey(keyRef string) {} +func (m *MockUpdateSigner) SetKeyRing(keyring, secretKeyring string) {} func (m *MockUpdateSigner) SetPassphrase(passphrase, passphraseFile string) {} -func (m *MockUpdateSigner) SetBatch(batch bool) {} +func (m *MockUpdateSigner) SetBatch(batch bool) {} // Mock deb.ParsePrefix function for update tests func init() { // Note: Removed package-level function assignment -} \ No newline at end of file +} diff --git a/cmd/repo_add_test.go b/cmd/repo_add_test.go index 3983f06f..f8f1dba0 100644 --- a/cmd/repo_add_test.go +++ b/cmd/repo_add_test.go @@ -7,8 +7,8 @@ import ( "testing" "github.com/aptly-dev/aptly/aptly" - "github.com/aptly-dev/aptly/deb" ctx "github.com/aptly-dev/aptly/context" + "github.com/aptly-dev/aptly/deb" "github.com/smira/commander" "github.com/smira/flag" . "gopkg.in/check.v1" @@ -40,7 +40,7 @@ func (s *RepoAddSuite) setupMockContext(c *C) { flags := flag.NewFlagSet("test", flag.ContinueOnError) flags.Bool("remove-files", false, "remove files") flags.Bool("force-replace", false, "force replace") - + err := InitContext(flags) c.Assert(err, IsNil) } @@ -48,21 +48,21 @@ func (s *RepoAddSuite) setupMockContext(c *C) { func (s *RepoAddSuite) TestMakeCmdRepoAdd(c *C) { // Test that makeCmdRepoAdd creates a proper command cmd := makeCmdRepoAdd() - + c.Check(cmd, NotNil) c.Check(cmd.Run, NotNil) c.Check(cmd.UsageLine, Equals, "add (|)...") c.Check(cmd.Short, Equals, "add packages to local repository") c.Check(cmd.Long, Not(Equals), "") - + // Check that all expected flags are present c.Check(cmd.Flag.Lookup("remove-files"), NotNil) c.Check(cmd.Flag.Lookup("force-replace"), NotNil) - + // Check flag default values removeFilesFlag := cmd.Flag.Lookup("remove-files") c.Check(removeFilesFlag.DefValue, Equals, "false") - + forceReplaceFlag := cmd.Flag.Lookup("force-replace") c.Check(forceReplaceFlag.DefValue, Equals, "false") } @@ -70,7 +70,7 @@ func (s *RepoAddSuite) TestMakeCmdRepoAdd(c *C) { func (s *RepoAddSuite) TestAptlyRepoAddNoArgs(c *C) { // Test aptlyRepoAdd with no arguments s.setupMockContext(c) - + err := aptlyRepoAdd(s.cmd, []string{}) c.Check(err, Equals, commander.ErrCommandError) } @@ -78,7 +78,7 @@ func (s *RepoAddSuite) TestAptlyRepoAddNoArgs(c *C) { func (s *RepoAddSuite) TestAptlyRepoAddOneArg(c *C) { // Test aptlyRepoAdd with only repository name (no files) s.setupMockContext(c) - + err := aptlyRepoAdd(s.cmd, []string{"test-repo"}) c.Check(err, Equals, commander.ErrCommandError) } @@ -86,7 +86,7 @@ func (s *RepoAddSuite) TestAptlyRepoAddOneArg(c *C) { func (s *RepoAddSuite) TestAptlyRepoAddNonexistentRepo(c *C) { // Test aptlyRepoAdd with nonexistent repository s.setupMockContext(c) - + err := aptlyRepoAdd(s.cmd, []string{"nonexistent-repo", "some-file.deb"}) c.Check(err, NotNil) c.Check(err.Error(), Matches, "unable to add:.*") @@ -95,7 +95,7 @@ func (s *RepoAddSuite) TestAptlyRepoAddNonexistentRepo(c *C) { func (s *RepoAddSuite) TestCommandUsage(c *C) { // Test command usage information cmd := makeCmdRepoAdd() - + c.Check(cmd.UsageLine, Equals, "add (|)...") c.Check(cmd.Short, Equals, "add packages to local repository") c.Check(cmd.Long, Matches, "(?s).*Command adds packages to local repository.*") @@ -120,7 +120,7 @@ func (s *RepoAddSuite) TestRepoAddErrorHandling(c *C) { expected: commander.ErrCommandError.Error(), }, } - + for _, tc := range testCases { s.setupMockContext(c) err := aptlyRepoAdd(s.cmd, tc.args) @@ -135,13 +135,13 @@ func (s *RepoAddSuite) TestRepoAddErrorHandling(c *C) { func (s *RepoAddSuite) TestFileHandlingPatterns(c *C) { // Test file handling patterns used in aptlyRepoAdd - + // Create test files debFile := filepath.Join(s.tempDir, "test-package.deb") udebFile := filepath.Join(s.tempDir, "test-package.udeb") dscFile := filepath.Join(s.tempDir, "test-package.dsc") txtFile := filepath.Join(s.tempDir, "readme.txt") - + // Create the files for _, filename := range []string{debFile, udebFile, dscFile, txtFile} { file, err := os.Create(filename) @@ -149,13 +149,13 @@ func (s *RepoAddSuite) TestFileHandlingPatterns(c *C) { file.WriteString("test content") file.Close() } - + // Test file collection patterns (similar to deb.CollectPackageFiles) files := []string{debFile, udebFile, dscFile, txtFile} - + packageFiles := []string{} otherFiles := []string{} - + for _, file := range files { if filepath.Ext(file) == ".deb" || filepath.Ext(file) == ".udeb" || filepath.Ext(file) == ".dsc" { packageFiles = append(packageFiles, file) @@ -163,7 +163,7 @@ func (s *RepoAddSuite) TestFileHandlingPatterns(c *C) { otherFiles = append(otherFiles, file) } } - + c.Check(len(packageFiles), Equals, 3) c.Check(len(otherFiles), Equals, 1) c.Check(packageFiles[0], Matches, ".*\\.deb$") @@ -174,30 +174,30 @@ func (s *RepoAddSuite) TestFileHandlingPatterns(c *C) { func (s *RepoAddSuite) TestFileRemovalPattern(c *C) { // Test the file removal pattern used when --remove-files is set - + // Create test files testFiles := []string{ filepath.Join(s.tempDir, "file1.deb"), filepath.Join(s.tempDir, "file2.deb"), filepath.Join(s.tempDir, "file3.deb"), } - + for _, filename := range testFiles { file, err := os.Create(filename) c.Assert(err, IsNil) file.WriteString("test content") file.Close() - + // Verify file exists _, err = os.Stat(filename) c.Check(err, IsNil) } - + // Test removal pattern for _, filename := range testFiles { err := os.Remove(filename) c.Check(err, IsNil) - + // Verify file is gone _, err = os.Stat(filename) c.Check(os.IsNotExist(err), Equals, true) @@ -206,7 +206,7 @@ func (s *RepoAddSuite) TestFileRemovalPattern(c *C) { func (s *RepoAddSuite) TestFileDeduplication(c *C) { // Test file deduplication pattern used in the function - + // Create duplicate file list files := []string{ "/path/to/file1.deb", @@ -215,28 +215,28 @@ func (s *RepoAddSuite) TestFileDeduplication(c *C) { "/path/to/file3.deb", "/path/to/file2.deb", // duplicate } - + // Simple deduplication (similar to utils.StrSliceDeduplicate) seen := make(map[string]bool) deduplicated := []string{} - + for _, file := range files { if !seen[file] { seen[file] = true deduplicated = append(deduplicated, file) } } - + c.Check(len(deduplicated), Equals, 3) c.Check(len(files), Equals, 5) - + // Verify all unique files are present expectedFiles := []string{ "/path/to/file1.deb", - "/path/to/file2.deb", + "/path/to/file2.deb", "/path/to/file3.deb", } - + for i, expected := range expectedFiles { c.Check(deduplicated[i], Equals, expected) } @@ -244,23 +244,23 @@ func (s *RepoAddSuite) TestFileDeduplication(c *C) { func (s *RepoAddSuite) TestPackageListHandling(c *C) { // Test package list creation and manipulation patterns - + // Mock package list structure type mockPackageList struct { packages []string refs []string } - + list := &mockPackageList{ packages: []string{"package1", "package2"}, refs: []string{"ref1", "ref2"}, } - + // Test adding packages to list newPackages := []string{"package3", "package4"} list.packages = append(list.packages, newPackages...) list.refs = append(list.refs, "ref3", "ref4") - + c.Check(len(list.packages), Equals, 4) c.Check(len(list.refs), Equals, 4) c.Check(list.packages[2], Equals, "package3") @@ -269,14 +269,14 @@ func (s *RepoAddSuite) TestPackageListHandling(c *C) { func (s *RepoAddSuite) TestErrorReporting(c *C) { // Test error reporting patterns used in aptlyRepoAdd - + // Test failed files collection failedFiles := []string{} processedFiles := []string{} - + // Simulate processing files with some failures files := []string{"file1.deb", "file2.deb", "file3.deb", "file4.deb"} - + for i, file := range files { if i%2 == 0 { // Simulate success @@ -286,14 +286,14 @@ func (s *RepoAddSuite) TestErrorReporting(c *C) { failedFiles = append(failedFiles, file) } } - + c.Check(len(processedFiles), Equals, 2) c.Check(len(failedFiles), Equals, 2) c.Check(processedFiles[0], Equals, "file1.deb") c.Check(processedFiles[1], Equals, "file3.deb") c.Check(failedFiles[0], Equals, "file2.deb") c.Check(failedFiles[1], Equals, "file4.deb") - + // Test error message generation if len(failedFiles) > 0 { err := errors.New("some files failed to be added") @@ -303,27 +303,27 @@ func (s *RepoAddSuite) TestErrorReporting(c *C) { func (s *RepoAddSuite) TestFlagProcessing(c *C) { // Test flag processing patterns - + cmd := makeCmdRepoAdd() - + // Test setting flags err := cmd.Flag.Set("remove-files", "true") c.Check(err, IsNil) - + err = cmd.Flag.Set("force-replace", "true") c.Check(err, IsNil) - + // Test reading flag values removeFilesFlag := cmd.Flag.Lookup("remove-files") c.Check(removeFilesFlag.Value.String(), Equals, "true") - + forceReplaceFlag := cmd.Flag.Lookup("force-replace") c.Check(forceReplaceFlag.Value.String(), Equals, "true") } func (s *RepoAddSuite) TestPackageImportFlow(c *C) { // Test the package import flow structure - + // Mock the import flow type importResult struct { processedFiles []string @@ -331,16 +331,16 @@ func (s *RepoAddSuite) TestPackageImportFlow(c *C) { otherFiles []string err error } - + // Simulate package collection inputFiles := []string{"package1.deb", "package2.deb", "readme.txt"} - + result := &importResult{ processedFiles: []string{}, failedFiles: []string{}, otherFiles: []string{}, } - + // Simulate file classification for _, file := range inputFiles { if filepath.Ext(file) == ".deb" { @@ -349,10 +349,10 @@ func (s *RepoAddSuite) TestPackageImportFlow(c *C) { result.otherFiles = append(result.otherFiles, file) } } - + // Merge processed and other files (like in the real function) allProcessed := append(result.processedFiles, result.otherFiles...) - + c.Check(len(result.processedFiles), Equals, 2) c.Check(len(result.otherFiles), Equals, 1) c.Check(len(allProcessed), Equals, 3) @@ -363,27 +363,27 @@ func (s *RepoAddSuite) TestPackageImportFlow(c *C) { func (s *RepoAddSuite) TestRepositoryUpdate(c *C) { // Test repository update patterns - + // Mock repository type mockRepo struct { name string refList []string updated bool } - + repo := &mockRepo{ name: "test-repo", refList: []string{"ref1", "ref2"}, updated: false, } - + // Simulate updating ref list newRefs := []string{"ref3", "ref4"} repo.refList = append(repo.refList, newRefs...) - + // Simulate repository update repo.updated = true - + c.Check(len(repo.refList), Equals, 4) c.Check(repo.updated, Equals, true) c.Check(repo.refList[2], Equals, "ref3") @@ -392,40 +392,40 @@ func (s *RepoAddSuite) TestRepositoryUpdate(c *C) { func (s *RepoAddSuite) TestProgressReporting(c *C) { // Test progress reporting patterns - + type mockProgress struct { messages []string } - + progress := &mockProgress{} - + // Test progress messages used in the function progress.messages = append(progress.messages, "Loading packages...") - + c.Check(len(progress.messages), Equals, 1) c.Check(progress.messages[0], Equals, "Loading packages...") } func (s *RepoAddSuite) TestVerifierUsage(c *C) { // Test verifier usage pattern - + // Mock verifier interface type mockVerifier struct { verified bool } - + verifier := &mockVerifier{verified: false} - + // Simulate verification process verifier.verified = true - + c.Check(verifier.verified, Equals, true) } func (s *RepoAddSuite) TestCollectionFactoryUsage(c *C) { // Test collection factory usage patterns - simplified // Note: Complex mocking removed for compilation simplification - + // Basic test to verify function works c.Check(true, Equals, true) -} \ No newline at end of file +} diff --git a/cmd/repo_create_test.go b/cmd/repo_create_test.go index 1d9b2062..72b9e069 100644 --- a/cmd/repo_create_test.go +++ b/cmd/repo_create_test.go @@ -39,7 +39,7 @@ func (s *RepoCreateSuite) TestMakeCmdRepoCreate(c *C) { func (s *RepoCreateSuite) TestRepoCreateBasic(c *C) { // Test basic repository creation - simplified args := []string{"test-repo"} - + err := aptlyRepoCreate(s.cmd, args) // Note: Actual behavior depends on real implementation _ = err // May or may not error depending on implementation @@ -49,7 +49,7 @@ func (s *RepoCreateSuite) TestRepoCreateWithComment(c *C) { // Test repository creation with comment - simplified s.cmd.Flag.Set("comment", "Test repository comment") args := []string{"test-repo-with-comment"} - + err := aptlyRepoCreate(s.cmd, args) // Note: Actual behavior depends on real implementation _ = err // May or may not error depending on implementation @@ -59,7 +59,7 @@ func (s *RepoCreateSuite) TestRepoCreateWithDistribution(c *C) { // Test repository creation with distribution - simplified s.cmd.Flag.Set("distribution", "trusty") args := []string{"test-repo-with-dist"} - + err := aptlyRepoCreate(s.cmd, args) // Note: Actual behavior depends on real implementation _ = err // May or may not error depending on implementation @@ -69,7 +69,7 @@ func (s *RepoCreateSuite) TestRepoCreateWithComponent(c *C) { // Test repository creation with component - simplified s.cmd.Flag.Set("component", "main") args := []string{"test-repo-with-comp"} - + err := aptlyRepoCreate(s.cmd, args) // Note: Actual behavior depends on real implementation _ = err // May or may not error depending on implementation @@ -91,7 +91,7 @@ func (s *RepoCreateSuite) TestRepoCreateWithAllFlags(c *C) { s.cmd.Flag.Set("distribution", "focal") s.cmd.Flag.Set("component", "main") args := []string{"test-repo-complete"} - + err := aptlyRepoCreate(s.cmd, args) // Note: Actual behavior depends on real implementation _ = err // May or may not error depending on implementation @@ -111,11 +111,11 @@ func (s *RepoCreateSuite) TestRepoCreateSpecialCharacters(c *C) { "test_repo_with_underscores", "test.repo.with.dots", } - + for _, name := range specialNames { args := []string{name} err := aptlyRepoCreate(s.cmd, args) // Note: Actual behavior depends on validation rules _ = err } -} \ No newline at end of file +} diff --git a/cmd/repo_include_test.go b/cmd/repo_include_test.go index 7c412de0..8a51b061 100644 --- a/cmd/repo_include_test.go +++ b/cmd/repo_include_test.go @@ -160,53 +160,69 @@ func (m *MockRepoIncludeProgress) ColoredPrintf(msg string, a ...interface{}) { // Mock implementation } -func (m *MockRepoIncludeProgress) AddBar(count int) {} -func (m *MockRepoIncludeProgress) Flush() {} +func (m *MockRepoIncludeProgress) AddBar(count int) {} +func (m *MockRepoIncludeProgress) Flush() {} func (m *MockRepoIncludeProgress) InitBar(total int64, colored bool, barType aptly.BarType) {} -func (m *MockRepoIncludeProgress) PrintfStdErr(msg string, a ...interface{}) {} -func (m *MockRepoIncludeProgress) SetBar(count int) {} -func (m *MockRepoIncludeProgress) Shutdown() {} -func (m *MockRepoIncludeProgress) ShutdownBar() {} -func (m *MockRepoIncludeProgress) Start() {} -func (m *MockRepoIncludeProgress) Write(data []byte) (int, error) { return len(data), nil } +func (m *MockRepoIncludeProgress) PrintfStdErr(msg string, a ...interface{}) {} +func (m *MockRepoIncludeProgress) SetBar(count int) {} +func (m *MockRepoIncludeProgress) Shutdown() {} +func (m *MockRepoIncludeProgress) ShutdownBar() {} +func (m *MockRepoIncludeProgress) Start() {} +func (m *MockRepoIncludeProgress) Write(data []byte) (int, error) { return len(data), nil } type MockRepoIncludeContext struct { - flags *flag.FlagSet - progress *MockRepoIncludeProgress - collectionFactory *deb.CollectionFactory - config *utils.ConfigStructure - packagePool aptly.PackagePool - verifier pgp.Verifier - verifierError bool - nilVerifier bool - uploadersError bool - uploadersQueryError bool - hasFailedFiles bool + flags *flag.FlagSet + progress *MockRepoIncludeProgress + collectionFactory *deb.CollectionFactory + config *utils.ConfigStructure + packagePool aptly.PackagePool + verifier pgp.Verifier + verifierError bool + nilVerifier bool + uploadersError bool + uploadersQueryError bool + hasFailedFiles bool } -func (m *MockRepoIncludeContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockRepoIncludeContext) Progress() aptly.Progress { return m.progress } -func (m *MockRepoIncludeContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockRepoIncludeContext) Config() *utils.ConfigStructure { return m.config } -func (m *MockRepoIncludeContext) PackagePool() aptly.PackagePool { return m.packagePool } +func (m *MockRepoIncludeContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockRepoIncludeContext) Progress() aptly.Progress { return m.progress } +func (m *MockRepoIncludeContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockRepoIncludeContext) Config() *utils.ConfigStructure { return m.config } +func (m *MockRepoIncludeContext) PackagePool() aptly.PackagePool { return m.packagePool } type MockRepoIncludePackagePool struct{} -func (m *MockRepoIncludePackagePool) FilepathList(progress aptly.Progress) ([]string, error) { return []string{}, nil } +func (m *MockRepoIncludePackagePool) FilepathList(progress aptly.Progress) ([]string, error) { + return []string{}, nil +} func (m *MockRepoIncludePackagePool) GeneratePackageRefs() []string { return []string{} } -func (m *MockRepoIncludePackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, move bool, storage aptly.ChecksumStorage) (string, error) { return "/pool/path", nil } -func (m *MockRepoIncludePackagePool) Verify(poolPath, basename string, checksums *utils.ChecksumInfo, checksumStorage aptly.ChecksumStorage) (string, bool, error) { return poolPath, true, nil } -func (m *MockRepoIncludePackagePool) Open(filename string) (aptly.ReadSeekerCloser, error) { return nil, nil } +func (m *MockRepoIncludePackagePool) Import(srcPath, basename string, checksums *utils.ChecksumInfo, move bool, storage aptly.ChecksumStorage) (string, error) { + return "/pool/path", nil +} +func (m *MockRepoIncludePackagePool) Verify(poolPath, basename string, checksums *utils.ChecksumInfo, checksumStorage aptly.ChecksumStorage) (string, bool, error) { + return poolPath, true, nil +} +func (m *MockRepoIncludePackagePool) Open(filename string) (aptly.ReadSeekerCloser, error) { + return nil, nil +} func (m *MockRepoIncludePackagePool) Remove(filename string) (int64, error) { return 0, nil } -func (m *MockRepoIncludePackagePool) Size(prefix string) (int64, error) { return 0, nil } -func (m *MockRepoIncludePackagePool) LegacyPath(filename string, checksums *utils.ChecksumInfo) (string, error) { return "/legacy/" + filename, nil } +func (m *MockRepoIncludePackagePool) Size(prefix string) (int64, error) { return 0, nil } +func (m *MockRepoIncludePackagePool) LegacyPath(filename string, checksums *utils.ChecksumInfo) (string, error) { + return "/legacy/" + filename, nil +} type MockRepoIncludeVerifier struct{} -func (m *MockRepoIncludeVerifier) AddKeyring(keyring string) {} +func (m *MockRepoIncludeVerifier) AddKeyring(keyring string) {} func (m *MockRepoIncludeVerifier) InitKeyring(verbose bool) error { return nil } -func (m *MockRepoIncludeVerifier) VerifyDetachedSignature(signature, message io.Reader, showKeyInfo bool) error { return nil } -func (m *MockRepoIncludeVerifier) ExtractClearsign(data []byte) (content []byte, err error) { return data, nil } +func (m *MockRepoIncludeVerifier) VerifyDetachedSignature(signature, message io.Reader, showKeyInfo bool) error { + return nil +} +func (m *MockRepoIncludeVerifier) ExtractClearsign(data []byte) (content []byte, err error) { + return data, nil +} func (m *MockRepoIncludeVerifier) ExtractClearsigned(clearsigned io.Reader) (text *os.File, err error) { // Create a temporary file for testing tmpFile, err := os.CreateTemp("", "mock-clearsigned") @@ -223,7 +239,11 @@ func (m *MockRepoIncludeVerifier) ExtractClearsigned(clearsigned io.Reader) (tex tmpFile.Seek(0, 0) return tmpFile, nil } -func (m *MockRepoIncludeVerifier) IsClearSigned(clearsigned io.Reader) (bool, error) { return true, nil } -func (m *MockRepoIncludeVerifier) VerifyClearsigned(clearsigned io.Reader, showKeyInfo bool) (*pgp.KeyInfo, error) { return &pgp.KeyInfo{}, nil } +func (m *MockRepoIncludeVerifier) IsClearSigned(clearsigned io.Reader) (bool, error) { + return true, nil +} +func (m *MockRepoIncludeVerifier) VerifyClearsigned(clearsigned io.Reader, showKeyInfo bool) (*pgp.KeyInfo, error) { + return &pgp.KeyInfo{}, nil +} -// Note: Removed package-level function assignments to fix compilation errors \ No newline at end of file +// Note: Removed package-level function assignments to fix compilation errors diff --git a/cmd/repo_list_test.go b/cmd/repo_list_test.go index 8fb5cb28..ea59aa43 100644 --- a/cmd/repo_list_test.go +++ b/cmd/repo_list_test.go @@ -41,4 +41,4 @@ func (s *RepoListSimpleSuite) TestAptlyRepoListBasic(c *C) { // This may fail due to missing context, but should not panic _ = aptlyRepoList(s.cmd, args) -} \ No newline at end of file +} diff --git a/cmd/repo_move_test.go b/cmd/repo_move_test.go index b1ed62cc..5e56942c 100644 --- a/cmd/repo_move_test.go +++ b/cmd/repo_move_test.go @@ -99,4 +99,4 @@ func (s *RepoMoveSimpleSuite) TestAptlyRepoMoveMultipleQueries(c *C) { // Test with multiple package queries args := []string{"src-repo", "dst-repo", "package1", "package2", "package3"} _ = aptlyRepoMoveCopyImport(s.cmd, args) -} \ No newline at end of file +} diff --git a/cmd/repo_remove_test.go b/cmd/repo_remove_test.go index 94c48b8e..ed379e5a 100644 --- a/cmd/repo_remove_test.go +++ b/cmd/repo_remove_test.go @@ -79,4 +79,4 @@ func (s *RepoRemoveSimpleSuite) TestAptlyRepoRemoveMultipleQueries(c *C) { // Test with multiple package queries args := []string{"repo-name", "package1", "package2", "package3"} _ = aptlyRepoRemove(s.cmd, args) -} \ No newline at end of file +} diff --git a/cmd/repo_show_test.go b/cmd/repo_show_test.go index 5f2bbc23..aaebc5ab 100644 --- a/cmd/repo_show_test.go +++ b/cmd/repo_show_test.go @@ -83,4 +83,4 @@ func (s *RepoShowSimpleSuite) TestAptlyRepoShowWithAllFlags(c *C) { args := []string{"repo-name"} _ = aptlyRepoShow(s.cmd, args) -} \ No newline at end of file +} diff --git a/cmd/serve_test.go b/cmd/serve_test.go index a1ab3842..bebf2f0b 100644 --- a/cmd/serve_test.go +++ b/cmd/serve_test.go @@ -70,4 +70,4 @@ func (s *ServeSimpleSuite) TestAptlyServeWithNoLock(c *C) { args := []string{} _ = aptlyServe(s.cmd, args) -} \ No newline at end of file +} diff --git a/cmd/snapshot_create_test.go b/cmd/snapshot_create_test.go index 506d55a3..29691de7 100644 --- a/cmd/snapshot_create_test.go +++ b/cmd/snapshot_create_test.go @@ -11,8 +11,8 @@ import ( ) type SnapshotCreateSuite struct { - cmd *commander.Command - origStdout *os.File + cmd *commander.Command + origStdout *os.File } var _ = Suite(&SnapshotCreateSuite{}) @@ -29,13 +29,13 @@ func (s *SnapshotCreateSuite) TearDownTest(c *C) { func (s *SnapshotCreateSuite) TestSnapshotCreateEmpty(c *C) { // Test creating empty snapshot args := []string{"empty-snapshot", "empty"} - + var buf bytes.Buffer os.Stdout = &buf - + err := aptlySnapshotCreate(s.cmd, args) c.Check(err, IsNil) - + output := buf.String() c.Check(strings.Contains(output, "Snapshot empty-snapshot successfully created"), Equals, true) c.Check(strings.Contains(output, "aptly publish snapshot empty-snapshot"), Equals, true) @@ -44,7 +44,7 @@ func (s *SnapshotCreateSuite) TestSnapshotCreateEmpty(c *C) { func (s *SnapshotCreateSuite) TestSnapshotCreateFromMirror(c *C) { // Test creating snapshot from mirror (will fail due to no context/mirror) args := []string{"mirror-snapshot", "from", "mirror", "test-mirror"} - + err := aptlySnapshotCreate(s.cmd, args) c.Check(err, NotNil) c.Check(err.Error(), Matches, ".*unable to create snapshot.*") @@ -53,7 +53,7 @@ func (s *SnapshotCreateSuite) TestSnapshotCreateFromMirror(c *C) { func (s *SnapshotCreateSuite) TestSnapshotCreateFromRepo(c *C) { // Test creating snapshot from local repo (will fail due to no context/repo) args := []string{"repo-snapshot", "from", "repo", "test-repo"} - + err := aptlySnapshotCreate(s.cmd, args) c.Check(err, NotNil) c.Check(err.Error(), Matches, ".*unable to create snapshot.*") @@ -63,27 +63,27 @@ func (s *SnapshotCreateSuite) TestSnapshotCreateInvalidArgs(c *C) { // Test with no arguments err := aptlySnapshotCreate(s.cmd, []string{}) c.Check(err, Equals, commander.ErrCommandError) - + // Test with only name err = aptlySnapshotCreate(s.cmd, []string{"test"}) c.Check(err, Equals, commander.ErrCommandError) - + // Test with wrong syntax err = aptlySnapshotCreate(s.cmd, []string{"test", "invalid"}) c.Check(err, Equals, commander.ErrCommandError) - + // Test with incomplete "from mirror" err = aptlySnapshotCreate(s.cmd, []string{"test", "from", "mirror"}) c.Check(err, Equals, commander.ErrCommandError) - + // Test with incomplete "from repo" err = aptlySnapshotCreate(s.cmd, []string{"test", "from", "repo"}) c.Check(err, Equals, commander.ErrCommandError) - + // Test with wrong "from" syntax err = aptlySnapshotCreate(s.cmd, []string{"test", "from", "invalid", "source"}) c.Check(err, Equals, commander.ErrCommandError) - + // Test with too many arguments err = aptlySnapshotCreate(s.cmd, []string{"test", "from", "mirror", "source", "extra"}) c.Check(err, Equals, commander.ErrCommandError) @@ -92,7 +92,7 @@ func (s *SnapshotCreateSuite) TestSnapshotCreateInvalidArgs(c *C) { func (s *SnapshotCreateSuite) TestSnapshotCreateValidSyntaxFromMirror(c *C) { // Test that valid "from mirror" syntax passes argument validation args := []string{"valid-mirror-snapshot", "from", "mirror", "test-mirror"} - + // This will fail at mirror loading but pass argument validation err := aptlySnapshotCreate(s.cmd, args) c.Check(err, NotNil) @@ -104,7 +104,7 @@ func (s *SnapshotCreateSuite) TestSnapshotCreateValidSyntaxFromMirror(c *C) { func (s *SnapshotCreateSuite) TestSnapshotCreateValidSyntaxFromRepo(c *C) { // Test that valid "from repo" syntax passes argument validation args := []string{"valid-repo-snapshot", "from", "repo", "test-repo"} - + // This will fail at repo loading but pass argument validation err := aptlySnapshotCreate(s.cmd, args) c.Check(err, NotNil) @@ -117,18 +117,18 @@ func (s *SnapshotCreateSuite) TestSnapshotCreateMultipleEmpty(c *C) { // Test creating multiple empty snapshots emptySnapshots := []string{ "empty-snapshot-1", - "empty-snapshot-2", + "empty-snapshot-2", "empty-snapshot-3", } - + for _, name := range emptySnapshots { var buf bytes.Buffer os.Stdout = &buf - + args := []string{name, "empty"} err := aptlySnapshotCreate(s.cmd, args) c.Check(err, IsNil, Commentf("Failed for snapshot: %s", name)) - + output := buf.String() c.Check(strings.Contains(output, "Snapshot "+name+" successfully created"), Equals, true, Commentf("Output check failed for snapshot: %s", name)) @@ -144,15 +144,15 @@ func (s *SnapshotCreateSuite) TestSnapshotCreateSpecialCharacters(c *C) { "snapshot123", "UPPERCASESNAPSHOT", } - + for _, name := range testNames { var buf bytes.Buffer os.Stdout = &buf - + args := []string{name, "empty"} err := aptlySnapshotCreate(s.cmd, args) c.Check(err, IsNil, Commentf("Failed for snapshot name: %s", name)) - + output := buf.String() c.Check(strings.Contains(output, "Snapshot "+name+" successfully created"), Equals, true, Commentf("Output check failed for snapshot name: %s", name)) @@ -162,13 +162,13 @@ func (s *SnapshotCreateSuite) TestSnapshotCreateSpecialCharacters(c *C) { func (s *SnapshotCreateSuite) TestSnapshotCreateEmptyName(c *C) { // Test creating snapshot with empty name args := []string{"", "empty"} - + var buf bytes.Buffer os.Stdout = &buf - + err := aptlySnapshotCreate(s.cmd, args) c.Check(err, IsNil) // Empty name is technically valid - + output := buf.String() c.Check(strings.Contains(output, "successfully created"), Equals, true) } @@ -176,11 +176,11 @@ func (s *SnapshotCreateSuite) TestSnapshotCreateEmptyName(c *C) { func (s *SnapshotCreateSuite) TestMakeCmdSnapshotCreate(c *C) { // Test command creation and configuration cmd := makeCmdSnapshotCreate() - + c.Check(cmd.Run, NotNil) c.Check(cmd.UsageLine, Equals, "create (from mirror | from repo | empty)") c.Check(cmd.Short, Equals, "creates snapshot of mirror (local repository) contents") - + // Test long description content c.Check(strings.Contains(cmd.Long, "Command create from mirror"), Equals, true) c.Check(strings.Contains(cmd.Long, "Command create from repo"), Equals, true) @@ -191,7 +191,7 @@ func (s *SnapshotCreateSuite) TestMakeCmdSnapshotCreate(c *C) { func (s *SnapshotCreateSuite) TestSnapshotCreateLongDescription(c *C) { // Test detailed long description content cmd := makeCmdSnapshotCreate() - + c.Check(strings.Contains(cmd.Long, "persistent immutable snapshot"), Equals, true) c.Check(strings.Contains(cmd.Long, "Snapshot could be published"), Equals, true) c.Check(strings.Contains(cmd.Long, "merge, pull and other aptly features"), Equals, true) @@ -202,14 +202,14 @@ func (s *SnapshotCreateSuite) TestSnapshotCreateLongDescription(c *C) { func (s *SnapshotCreateSuite) TestSnapshotCreateArgumentCombinations(c *C) { // Test various argument combination edge cases - + // Valid combinations that should pass argument validation validCombinations := [][]string{ {"test", "empty"}, {"test", "from", "mirror", "mirror-name"}, {"test", "from", "repo", "repo-name"}, } - + for _, args := range validCombinations { err := aptlySnapshotCreate(s.cmd, args) // These should pass argument validation (not return commander.ErrCommandError) @@ -218,19 +218,19 @@ func (s *SnapshotCreateSuite) TestSnapshotCreateArgumentCombinations(c *C) { c.Fatalf("Argument validation failed for valid combination: %v", args) } } - + // Invalid combinations that should fail argument validation invalidCombinations := [][]string{ - {}, // No arguments - {"test"}, // Missing type - {"test", "from"}, // Incomplete from - {"test", "from", "mirror"}, // Missing mirror name - {"test", "from", "repo"}, // Missing repo name + {}, // No arguments + {"test"}, // Missing type + {"test", "from"}, // Incomplete from + {"test", "from", "mirror"}, // Missing mirror name + {"test", "from", "repo"}, // Missing repo name {"test", "from", "invalid", "source"}, // Invalid source type - {"test", "invalid"}, // Invalid type - {"test", "empty", "extra"}, // Extra arguments + {"test", "invalid"}, // Invalid type + {"test", "empty", "extra"}, // Extra arguments } - + for _, args := range invalidCombinations { err := aptlySnapshotCreate(s.cmd, args) c.Check(err, Equals, commander.ErrCommandError, @@ -240,16 +240,16 @@ func (s *SnapshotCreateSuite) TestSnapshotCreateArgumentCombinations(c *C) { func (s *SnapshotCreateSuite) TestSnapshotCreateCaseSensitivity(c *C) { // Test that keywords are case sensitive - + // These should fail because keywords must be exact invalidCases := [][]string{ - {"test", "Empty"}, // Capital E - {"test", "EMPTY"}, // All caps + {"test", "Empty"}, // Capital E + {"test", "EMPTY"}, // All caps {"test", "from", "Mirror", "test"}, // Capital M - {"test", "from", "Repo", "test"}, // Capital R + {"test", "from", "Repo", "test"}, // Capital R {"test", "From", "mirror", "test"}, // Capital F } - + for _, args := range invalidCases { err := aptlySnapshotCreate(s.cmd, args) c.Check(err, Equals, commander.ErrCommandError, @@ -260,22 +260,22 @@ func (s *SnapshotCreateSuite) TestSnapshotCreateCaseSensitivity(c *C) { func (s *SnapshotCreateSuite) TestSnapshotCreateOutputFormat(c *C) { // Test the specific output format for empty snapshots args := []string{"format-test", "empty"} - + var buf bytes.Buffer os.Stdout = &buf - + err := aptlySnapshotCreate(s.cmd, args) c.Check(err, IsNil) - + output := buf.String() lines := strings.Split(strings.TrimSpace(output), "\n") - + // Should have exactly 2 lines of output c.Check(len(lines), Equals, 2) - + // First line should contain success message c.Check(lines[0], Matches, "Snapshot format-test successfully created\\.") - + // Second line should contain publish instruction c.Check(lines[1], Matches, "You can run 'aptly publish snapshot format-test' to publish snapshot as Debian repository\\.") -} \ No newline at end of file +} diff --git a/cmd/snapshot_diff_test.go b/cmd/snapshot_diff_test.go index db14c89a..97ee99e3 100644 --- a/cmd/snapshot_diff_test.go +++ b/cmd/snapshot_diff_test.go @@ -266,9 +266,11 @@ type MockSnapshotDiffContext struct { testOnlyMatchingFiltering bool } -func (m *MockSnapshotDiffContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockSnapshotDiffContext) Progress() aptly.Progress { return m.progress } -func (m *MockSnapshotDiffContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } +func (m *MockSnapshotDiffContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockSnapshotDiffContext) Progress() aptly.Progress { return m.progress } +func (m *MockSnapshotDiffContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} type MockSnapshotDiffCollection struct { shouldErrorByNameA bool @@ -290,7 +292,7 @@ func (m *MockSnapshotDiffCollection) ByName(name string) (*deb.Snapshot, error) Description: "Test snapshot", } snapshot.SetRefList(&MockSnapshotDiffRefList{name: name}) - + return snapshot, nil } @@ -331,7 +333,7 @@ func (m *MockSnapshotDiffRefList) Diff(other *deb.PackageRefList, packageCollect }) } else if context, ok := context.(*MockSnapshotDiffContext); ok && context.testAllPackageStates { // Include all types of changes - + // Package only in B (addition) diff = append(diff, &deb.PackageDiff{ Left: nil, @@ -376,11 +378,11 @@ func (s *deb.Snapshot) SetRefList(refList *deb.PackageRefList) { func (s *SnapshotDiffSuite) TestAptlySnapshotDiffScenarios(c *C) { // Test scenarios for package differences scenarios := []struct { - name string - testAllPackageStates bool - testOnlyMatching bool - identicalSnapshots bool - expectedMessages int + name string + testAllPackageStates bool + testOnlyMatching bool + identicalSnapshots bool + expectedMessages int expectedColoredMessages int }{ {"identical", false, false, true, 1, 0}, @@ -403,12 +405,12 @@ func (s *SnapshotDiffSuite) TestAptlySnapshotDiffScenarios(c *C) { err := aptlySnapshotDiff(s.cmd, args) c.Check(err, IsNil, Commentf("Scenario: %s", scenario.name)) - c.Check(len(s.mockProgress.Messages) >= scenario.expectedMessages, Equals, true, - Commentf("Scenario: %s, expected at least %d messages, got %d", + c.Check(len(s.mockProgress.Messages) >= scenario.expectedMessages, Equals, true, + Commentf("Scenario: %s, expected at least %d messages, got %d", scenario.name, scenario.expectedMessages, len(s.mockProgress.Messages))) - + c.Check(len(s.mockProgress.ColoredMessages) >= scenario.expectedColoredMessages, Equals, true, - Commentf("Scenario: %s, expected at least %d colored messages, got %d", + Commentf("Scenario: %s, expected at least %d colored messages, got %d", scenario.name, scenario.expectedColoredMessages, len(s.mockProgress.ColoredMessages))) } } @@ -436,8 +438,8 @@ func (s *SnapshotDiffSuite) TestPackageDiffStructure(c *C) { }{ {nil, &deb.Package{Name: "new", Version: "1.0", Architecture: "amd64"}, "addition"}, {&deb.Package{Name: "old", Version: "1.0", Architecture: "amd64"}, nil, "removal"}, - {&deb.Package{Name: "pkg", Version: "1.0", Architecture: "amd64"}, - &deb.Package{Name: "pkg", Version: "2.0", Architecture: "amd64"}, "update"}, + {&deb.Package{Name: "pkg", Version: "1.0", Architecture: "amd64"}, + &deb.Package{Name: "pkg", Version: "2.0", Architecture: "amd64"}, "update"}, } for _, testCase := range testCases { @@ -469,4 +471,4 @@ func (s *SnapshotDiffSuite) TestSnapshotDiffEdgeCases(c *C) { } } c.Check(foundIdenticalMessage, Equals, true) -} \ No newline at end of file +} diff --git a/cmd/snapshot_filter_test.go b/cmd/snapshot_filter_test.go index d03759f3..3ab6899b 100644 --- a/cmd/snapshot_filter_test.go +++ b/cmd/snapshot_filter_test.go @@ -39,9 +39,9 @@ func (s *SnapshotFilterSuite) SetUpTest(c *C) { collectionFactory: s.collectionFactory, architectures: []string{"amd64", "i386"}, dependencyOptions: aptly.DependencyOptions{ - FollowRecommends: false, - FollowSuggests: false, - FollowSource: false, + FollowRecommends: false, + FollowSuggests: false, + FollowSource: false, FollowAllVariants: false, }, } @@ -283,7 +283,7 @@ func (s *SnapshotFilterSuite) TestAptlySnapshotFilterArchitectureHandling(c *C) for _, testCase := range testCases { s.mockContext.architectures = testCase.contextArchs - + args := []string{"source-snapshot", "dest-snapshot", "nginx"} err := aptlySnapshotFilter(s.cmd, args) c.Check(err, IsNil, Commentf("Context architectures: %v", testCase.contextArchs)) @@ -330,11 +330,13 @@ type MockSnapshotFilterContext struct { dependencyOptions int } -func (m *MockSnapshotFilterContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockSnapshotFilterContext) Progress() aptly.Progress { return m.progress } -func (m *MockSnapshotFilterContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockSnapshotFilterContext) ArchitecturesList() []string { return m.architectures } -func (m *MockSnapshotFilterContext) DependencyOptions() int { return m.dependencyOptions } +func (m *MockSnapshotFilterContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockSnapshotFilterContext) Progress() aptly.Progress { return m.progress } +func (m *MockSnapshotFilterContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockSnapshotFilterContext) ArchitecturesList() []string { return m.architectures } +func (m *MockSnapshotFilterContext) DependencyOptions() int { return m.dependencyOptions } type MockSnapshotFilterCollection struct { shouldErrorByName bool @@ -352,7 +354,7 @@ func (m *MockSnapshotFilterCollection) ByName(name string) (*deb.Snapshot, error Description: "Test snapshot", } snapshot.SetRefList(&MockSnapshotFilterRefList{}) - + return snapshot, nil } @@ -410,7 +412,7 @@ func (m *MockSnapshotFilterPackageList) Filter(options deb.FilterOptions) (*deb. if m.collection != nil && m.collection.shouldErrorFilter { return nil, fmt.Errorf("mock filter error") } - + // Return a filtered package list return &MockSnapshotFilterPackageList{}, nil } @@ -504,4 +506,4 @@ func (s *SnapshotFilterSuite) TestAptlySnapshotFilterSnapshotCreation(c *C) { expectedDesc := fmt.Sprintf("Filtered '%s', query was: '%s'", "test-source", "nginx") c.Check(len(expectedDesc) > 0, Equals, true) c.Check(strings.Contains(expectedDesc, "Filtered"), Equals, true) -} \ No newline at end of file +} diff --git a/cmd/snapshot_list_test.go b/cmd/snapshot_list_test.go index cb263634..4f7e3920 100644 --- a/cmd/snapshot_list_test.go +++ b/cmd/snapshot_list_test.go @@ -159,7 +159,7 @@ func (s *SnapshotListSuite) TestAptlySnapshotListJSON(c *C) { outputStr := output.String() c.Check(strings.Contains(outputStr, "{"), Equals, true) c.Check(strings.Contains(outputStr, "}"), Equals, true) - + // Verify it's valid JSON var snapshots []interface{} err = json.Unmarshal([]byte(strings.TrimSpace(outputStr)), &snapshots) @@ -281,7 +281,7 @@ func (s *SnapshotListSuite) TestAptlySnapshotListJSONSorting(c *C) { // Should complete successfully with sorted JSON output outputStr := output.String() c.Check(len(outputStr) > 0, Equals, true) - + // Verify it's valid JSON var snapshots []map[string]interface{} err = json.Unmarshal([]byte(strings.TrimSpace(outputStr)), &snapshots) @@ -306,16 +306,18 @@ type MockSnapshotListContext struct { collectionFactory *deb.CollectionFactory } -func (m *MockSnapshotListContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockSnapshotListContext) Progress() aptly.Progress { return m.progress } -func (m *MockSnapshotListContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } +func (m *MockSnapshotListContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockSnapshotListContext) Progress() aptly.Progress { return m.progress } +func (m *MockSnapshotListContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} type MockSnapshotListCollection struct { - emptyCollection bool - shouldErrorForEach bool - causeMarshalError bool - multipleSnapshots bool - snapshotNames []string + emptyCollection bool + shouldErrorForEach bool + causeMarshalError bool + multipleSnapshots bool + snapshotNames []string } func (m *MockSnapshotListCollection) Len() int { @@ -341,7 +343,7 @@ func (m *MockSnapshotListCollection) ForEachSorted(sortMethod string, handler fu // Sort snapshots based on method names := make([]string, len(m.snapshotNames)) copy(names, m.snapshotNames) - + if sortMethod == "name" { // Sort alphabetically for name sorting for i := 0; i < len(names)-1; i++ { @@ -359,7 +361,7 @@ func (m *MockSnapshotListCollection) ForEachSorted(sortMethod string, handler fu Name: name, Description: "Test snapshot", } - + if err := handler(snapshot); err != nil { return err } @@ -369,13 +371,13 @@ func (m *MockSnapshotListCollection) ForEachSorted(sortMethod string, handler fu Name: "test-snapshot", Description: "Test snapshot", } - + // Create problematic snapshot for marshal error testing if m.causeMarshalError { // Create a cyclic structure that can't be marshaled snapshot.TestCyclicRef = snapshot } - + return handler(snapshot) } @@ -468,7 +470,7 @@ func (s *SnapshotListSuite) TestFlagCombinations(c *C) { s.cmd.Flag.Set(flag, "false") } } - + fmt.Printf = originalPrintf fmt.Println = originalPrintln } @@ -524,8 +526,8 @@ func (s *SnapshotListSuite) TestEdgeCases(c *C) { // Test with snapshot that has minimal configuration snapshot := &deb.Snapshot{Name: "simple-snapshot"} c.Check(snapshot.Name, Equals, "simple-snapshot") - + // Test string representation with minimal data stringRep := snapshot.String() c.Check(strings.Contains(stringRep, "simple-snapshot"), Equals, true) -} \ No newline at end of file +} diff --git a/cmd/snapshot_merge_test.go b/cmd/snapshot_merge_test.go index 4ecffce6..19854adb 100644 --- a/cmd/snapshot_merge_test.go +++ b/cmd/snapshot_merge_test.go @@ -306,13 +306,13 @@ func (s *SnapshotMergeSuite) TestAptlySnapshotMergeFilterLatestRefs(c *C) { func (s *SnapshotMergeSuite) TestAptlySnapshotMergeOverrideMatching(c *C) { // Test override matching behavior with different flag combinations scenarios := []struct { - latest bool - noRemove bool + latest bool + noRemove bool expectOverride bool }{ - {false, false, true}, // Default: override matching - {true, false, false}, // Latest: no override matching - {false, true, false}, // No-remove: no override matching + {false, false, true}, // Default: override matching + {true, false, false}, // Latest: no override matching + {false, true, false}, // No-remove: no override matching } for _, scenario := range scenarios { @@ -345,9 +345,11 @@ type MockSnapshotMergeContext struct { collectionFactory *deb.CollectionFactory } -func (m *MockSnapshotMergeContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockSnapshotMergeContext) Progress() aptly.Progress { return m.progress } -func (m *MockSnapshotMergeContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } +func (m *MockSnapshotMergeContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockSnapshotMergeContext) Progress() aptly.Progress { return m.progress } +func (m *MockSnapshotMergeContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} type MockSnapshotMergeCollection struct { shouldErrorByName bool @@ -496,8 +498,8 @@ func (s *SnapshotMergeSuite) TestErrorMessageFormatting(c *C) { func (s *SnapshotMergeSuite) TestMergeStrategyValidation(c *C) { // Test different merge strategies strategies := []struct { - latest bool - noRemove bool + latest bool + noRemove bool expectedStrategy string }{ {false, false, "override"}, @@ -535,4 +537,4 @@ func (s *SnapshotMergeSuite) TestDescriptionWithManySources(c *C) { } } c.Check(foundSuccessMessage, Equals, true) -} \ No newline at end of file +} diff --git a/cmd/snapshot_pull_test.go b/cmd/snapshot_pull_test.go index 0a497e16..0e1840e9 100644 --- a/cmd/snapshot_pull_test.go +++ b/cmd/snapshot_pull_test.go @@ -226,7 +226,7 @@ func (s *SnapshotPullSuite) TestAptlySnapshotPullFilterError(c *C) { func (s *SnapshotPullSuite) TestAptlySnapshotPullWithFlags(c *C) { // Test pull with various flags flagTests := []struct { - flag string + flag string value string }{ {"no-deps", "true"}, @@ -300,7 +300,7 @@ func (s *SnapshotPullSuite) TestAptlySnapshotPullArchitectureFiltering(c *C) { func (s *SnapshotPullSuite) TestAptlySnapshotPullPackageProcessing(c *C) { // Test package addition and removal logic - s.cmd.Flag.Set("no-remove", "false") // Allow removal + s.cmd.Flag.Set("no-remove", "false") // Allow removal s.cmd.Flag.Set("all-matches", "false") // Only first match args := []string{"target-snapshot", "source-snapshot", "dest-snapshot", "package-name"} @@ -396,18 +396,20 @@ type MockSnapshotPullContext struct { dependencyOptions int } -func (m *MockSnapshotPullContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockSnapshotPullContext) Progress() aptly.Progress { return m.progress } -func (m *MockSnapshotPullContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockSnapshotPullContext) ArchitecturesList() []string { return m.architecturesList } -func (m *MockSnapshotPullContext) DependencyOptions() int { return m.dependencyOptions } +func (m *MockSnapshotPullContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockSnapshotPullContext) Progress() aptly.Progress { return m.progress } +func (m *MockSnapshotPullContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockSnapshotPullContext) ArchitecturesList() []string { return m.architecturesList } +func (m *MockSnapshotPullContext) DependencyOptions() int { return m.dependencyOptions } type MockSnapshotPullCollection struct { - shouldErrorByName bool - shouldErrorLoadComplete bool - shouldErrorSourceByName bool - shouldErrorSourceLoadComplete bool - shouldErrorAdd bool + shouldErrorByName bool + shouldErrorLoadComplete bool + shouldErrorSourceByName bool + shouldErrorSourceLoadComplete bool + shouldErrorAdd bool } func (m *MockSnapshotPullCollection) ByName(name string) (*deb.Snapshot, error) { @@ -516,7 +518,7 @@ func init() { type MockSnapshotPullQuery struct{} func (m *MockSnapshotPullQuery) Matches(pkg *deb.Package) bool { return true } -func (m *MockSnapshotPullQuery) String() string { return "mock-query" } +func (m *MockSnapshotPullQuery) String() string { return "mock-query" } // Mock field query for architecture filtering type MockFieldQuery struct { @@ -526,7 +528,9 @@ type MockFieldQuery struct { } func (m *MockFieldQuery) Matches(pkg *deb.Package) bool { return true } -func (m *MockFieldQuery) String() string { return fmt.Sprintf("%s %s %s", m.Field, m.Relation, m.Value) } +func (m *MockFieldQuery) String() string { + return fmt.Sprintf("%s %s %s", m.Field, m.Relation, m.Value) +} // Mock OR query type MockOrQuery struct { @@ -535,7 +539,7 @@ type MockOrQuery struct { } func (m *MockOrQuery) Matches(pkg *deb.Package) bool { return m.L.Matches(pkg) || m.R.Matches(pkg) } -func (m *MockOrQuery) String() string { return fmt.Sprintf("(%s | %s)", m.L.String(), m.R.String()) } +func (m *MockOrQuery) String() string { return fmt.Sprintf("(%s | %s)", m.L.String(), m.R.String()) } // Mock AND query type MockAndQuery struct { @@ -544,7 +548,7 @@ type MockAndQuery struct { } func (m *MockAndQuery) Matches(pkg *deb.Package) bool { return m.L.Matches(pkg) && m.R.Matches(pkg) } -func (m *MockAndQuery) String() string { return fmt.Sprintf("(%s, %s)", m.L.String(), m.R.String()) } +func (m *MockAndQuery) String() string { return fmt.Sprintf("(%s, %s)", m.L.String(), m.R.String()) } // Mock dependency struct type MockDependency struct { @@ -555,4 +559,4 @@ type MockDependency struct { // Mock package struct methods func (p *deb.Package) String() string { return fmt.Sprintf("%s_%s", p.Name, p.Architecture) -} \ No newline at end of file +} diff --git a/cmd/snapshot_search_test.go b/cmd/snapshot_search_test.go index 5987bc7a..e6c579a9 100644 --- a/cmd/snapshot_search_test.go +++ b/cmd/snapshot_search_test.go @@ -370,17 +370,19 @@ type MockSnapshotSearchContext struct { dependencyOptions int } -func (m *MockSnapshotSearchContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockSnapshotSearchContext) Progress() aptly.Progress { return m.progress } -func (m *MockSnapshotSearchContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockSnapshotSearchContext) ArchitecturesList() []string { return m.architecturesList } -func (m *MockSnapshotSearchContext) DependencyOptions() int { return m.dependencyOptions } +func (m *MockSnapshotSearchContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockSnapshotSearchContext) Progress() aptly.Progress { return m.progress } +func (m *MockSnapshotSearchContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockSnapshotSearchContext) ArchitecturesList() []string { return m.architecturesList } +func (m *MockSnapshotSearchContext) DependencyOptions() int { return m.dependencyOptions } type MockSnapshotSearchCollection struct { - shouldErrorByName bool - shouldErrorLoadComplete bool - byNameCalled bool - loadCompleteCalled bool + shouldErrorByName bool + shouldErrorLoadComplete bool + byNameCalled bool + loadCompleteCalled bool } func (m *MockSnapshotSearchCollection) ByName(name string) (*deb.Snapshot, error) { @@ -403,10 +405,10 @@ func (m *MockSnapshotSearchCollection) LoadComplete(snapshot *deb.Snapshot) erro } type MockRemoteSearchRepoCollection struct { - shouldErrorByName bool - shouldErrorLoadComplete bool - byNameCalled bool - loadCompleteCalled bool + shouldErrorByName bool + shouldErrorLoadComplete bool + byNameCalled bool + loadCompleteCalled bool } func (m *MockRemoteSearchRepoCollection) ByName(name string) (*deb.RemoteRepo, error) { @@ -429,10 +431,10 @@ func (m *MockRemoteSearchRepoCollection) LoadComplete(repo *deb.RemoteRepo) erro } type MockLocalSearchRepoCollection struct { - shouldErrorByName bool - shouldErrorLoadComplete bool - byNameCalled bool - loadCompleteCalled bool + shouldErrorByName bool + shouldErrorLoadComplete bool + byNameCalled bool + loadCompleteCalled bool } func (m *MockLocalSearchRepoCollection) ByName(name string) (*deb.LocalRepo, error) { @@ -487,7 +489,7 @@ func NewPackageListFromRefListSearch(refList *deb.PackageRefList, packageCollect if collection, ok := packageCollection.(*MockPackageSearchCollection); ok && collection.shouldErrorFilter { return nil, fmt.Errorf("mock filter error") } - + resultList := &deb.PackageList{} if collection, ok := packageCollection.(*MockPackageSearchCollection); ok && collection.emptyResults { resultList.Len = func() int { return 0 } @@ -517,7 +519,7 @@ func (r *deb.LocalRepo) RefList() *deb.PackageRefList { type MockMatchAllQuery struct{} func (m *MockMatchAllQuery) Matches(pkg *deb.Package) bool { return true } -func (m *MockMatchAllQuery) String() string { return "*" } +func (m *MockMatchAllQuery) String() string { return "*" } // Override deb.MatchAllQuery for testing func init() { @@ -548,6 +550,6 @@ func init() { type MockSearchQuery struct{} func (m *MockSearchQuery) Matches(pkg *deb.Package) bool { return true } -func (m *MockSearchQuery) String() string { return "mock-search-query" } +func (m *MockSearchQuery) String() string { return "mock-search-query" } -// GetStringOrFileContent function is already defined in string_or_file_flag.go \ No newline at end of file +// GetStringOrFileContent function is already defined in string_or_file_flag.go diff --git a/cmd/snapshot_show_test.go b/cmd/snapshot_show_test.go index 99f75538..c6d3c1b6 100644 --- a/cmd/snapshot_show_test.go +++ b/cmd/snapshot_show_test.go @@ -253,8 +253,8 @@ func (s *SnapshotShowSuite) TestAptlySnapshotShowJSONRemoteRepoSources(c *C) { func (s *SnapshotShowSuite) TestAptlySnapshotShowSourceErrors(c *C) { // Test handling of source lookup errors (should continue gracefully) mockSnapshotCollection := &MockSnapshotShowCollection{ - hasSnapshotSources: true, - shouldErrorByUUID: true, + hasSnapshotSources: true, + shouldErrorByUUID: true, } s.collectionFactory.snapshotCollection = mockSnapshotCollection @@ -269,7 +269,7 @@ func (s *SnapshotShowSuite) TestAptlySnapshotShowSourceErrors(c *C) { func (s *SnapshotShowSuite) TestAptlySnapshotShowJSONMarshalError(c *C) { // Test JSON marshal error handling s.cmd.Flag.Set("json", "true") - + // Create a snapshot that will cause JSON marshal error mockCollection := &MockSnapshotShowCollection{causeMarshalError: true} s.collectionFactory.snapshotCollection = mockCollection @@ -323,22 +323,24 @@ type MockSnapshotShowContext struct { collectionFactory *deb.CollectionFactory } -func (m *MockSnapshotShowContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockSnapshotShowContext) Progress() aptly.Progress { return m.progress } -func (m *MockSnapshotShowContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } +func (m *MockSnapshotShowContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockSnapshotShowContext) Progress() aptly.Progress { return m.progress } +func (m *MockSnapshotShowContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} type MockSnapshotShowCollection struct { - shouldErrorByName bool - shouldErrorLoadComplete bool - shouldErrorByUUID bool - hasSnapshotSources bool - hasLocalRepoSources bool - hasRemoteRepoSources bool - causeMarshalError bool - emptyRefList bool - byNameCalled bool - loadCompleteCalled bool - byUUIDCalled bool + shouldErrorByName bool + shouldErrorLoadComplete bool + shouldErrorByUUID bool + hasSnapshotSources bool + hasLocalRepoSources bool + hasRemoteRepoSources bool + causeMarshalError bool + emptyRefList bool + byNameCalled bool + loadCompleteCalled bool + byUUIDCalled bool } func (m *MockSnapshotShowCollection) ByName(name string) (*deb.Snapshot, error) { @@ -491,4 +493,4 @@ func (s *SnapshotShowSuite) SetUpSuite(c *C) { // Redirect stdout to capture output for testing originalStdout := os.Stdout _ = originalStdout // Prevent unused variable warning -} \ No newline at end of file +} diff --git a/cmd/snapshot_verify_test.go b/cmd/snapshot_verify_test.go index 1866079c..e0b411b7 100644 --- a/cmd/snapshot_verify_test.go +++ b/cmd/snapshot_verify_test.go @@ -202,7 +202,7 @@ func (s *SnapshotVerifySuite) TestAptlySnapshotVerifyArchitectureHandling(c *C) for _, testCase := range testCases { s.mockContext.architectures = testCase.contextArchs - + args := []string{"test-snapshot"} err := aptlySnapshotVerify(s.cmd, args) c.Check(err, IsNil, Commentf("Context architectures: %v", testCase.contextArchs)) @@ -270,11 +270,13 @@ type MockSnapshotVerifyContext struct { dependencyOptions int } -func (m *MockSnapshotVerifyContext) Flags() *flag.FlagSet { return m.flags } -func (m *MockSnapshotVerifyContext) Progress() aptly.Progress { return m.progress } -func (m *MockSnapshotVerifyContext) NewCollectionFactory() *deb.CollectionFactory { return m.collectionFactory } -func (m *MockSnapshotVerifyContext) ArchitecturesList() []string { return m.architectures } -func (m *MockSnapshotVerifyContext) DependencyOptions() int { return m.dependencyOptions } +func (m *MockSnapshotVerifyContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockSnapshotVerifyContext) Progress() aptly.Progress { return m.progress } +func (m *MockSnapshotVerifyContext) NewCollectionFactory() *deb.CollectionFactory { + return m.collectionFactory +} +func (m *MockSnapshotVerifyContext) ArchitecturesList() []string { return m.architectures } +func (m *MockSnapshotVerifyContext) DependencyOptions() int { return m.dependencyOptions } type MockSnapshotVerifyCollection struct { shouldErrorByName bool @@ -291,7 +293,7 @@ func (m *MockSnapshotVerifyCollection) ByName(name string) (*deb.Snapshot, error Description: "Test snapshot", } snapshot.SetRefList(&MockSnapshotVerifyRefList{}) - + return snapshot, nil } @@ -319,7 +321,7 @@ type MockSnapshotVerifyPackageCollection struct { func (m *MockSnapshotVerifyPackageCollection) NewPackageListFromRefList(refList *deb.PackageRefList, progress aptly.Progress) (*deb.PackageList, error) { m.callCount++ - + if m.shouldErrorNewPackageList && m.callCount == 1 { return nil, fmt.Errorf("mock new package list error") } @@ -328,11 +330,11 @@ func (m *MockSnapshotVerifyPackageCollection) NewPackageListFromRefList(refList } packageList := &MockSnapshotVerifyPackageList{ - collection: m, - emptyArchitectures: m.emptyArchitectures, - hasMissingDependencies: m.hasMissingDependencies, + collection: m, + emptyArchitectures: m.emptyArchitectures, + hasMissingDependencies: m.hasMissingDependencies, shouldErrorVerifyDependencies: m.shouldErrorVerifyDependencies, - isFirstCall: m.callCount == 1, + isFirstCall: m.callCount == 1, } return packageList, nil } @@ -371,7 +373,7 @@ func (m *MockSnapshotVerifyPackageList) VerifyDependencies(options int, architec if m.shouldErrorVerifyDependencies { return nil, fmt.Errorf("mock verify dependencies error") } - + if m.hasMissingDependencies { // Return some mock missing dependencies missing := []*deb.Dependency{ @@ -380,7 +382,7 @@ func (m *MockSnapshotVerifyPackageList) VerifyDependencies(options int, architec } return missing, nil } - + // No missing dependencies return []*deb.Dependency{}, nil } @@ -419,7 +421,7 @@ func (s *SnapshotVerifySuite) TestDependencySorting(c *C) { // Test that missing dependencies are sorted correctly deps := []string{"z-package", "a-package", "m-package"} sort.Strings(deps) - + expected := []string{"a-package", "m-package", "z-package"} c.Check(deps, DeepEquals, expected) } @@ -435,7 +437,7 @@ func (s *SnapshotVerifySuite) TestArchitectureCombinations(c *C) { for _, archs := range testArchs { s.mockContext.architectures = archs - + args := []string{"test-snapshot"} err := aptlySnapshotVerify(s.cmd, args) c.Check(err, IsNil, Commentf("Architectures: %v", archs)) @@ -476,7 +478,7 @@ func (s *SnapshotVerifySuite) TestDependencyOptionsCombinations(c *C) { for _, options := range optionTests { s.mockContext.dependencyOptions = options - + args := []string{"test-snapshot"} err := aptlySnapshotVerify(s.cmd, args) c.Check(err, IsNil, Commentf("Options: %+v", options)) @@ -508,4 +510,4 @@ func (s *SnapshotVerifySuite) TestMultipleMissingDependenciesDisplay(c *C) { } c.Check(foundMissingCount, Equals, true) c.Check(foundDependencyList, Equals, true) -} \ No newline at end of file +} diff --git a/cmd/task_run_test.go b/cmd/task_run_test.go index 4f00ffe7..707b5483 100644 --- a/cmd/task_run_test.go +++ b/cmd/task_run_test.go @@ -15,10 +15,10 @@ import ( ) type TaskRunSuite struct { - cmd *commander.Command - mockProgress *MockTaskRunProgress - mockContext *MockTaskRunContext - tempFile *os.File + cmd *commander.Command + mockProgress *MockTaskRunProgress + mockContext *MockTaskRunContext + tempFile *os.File } var _ = Suite(&TaskRunSuite{}) @@ -360,13 +360,13 @@ func (m *MockTaskRunProgress) Flush() { } type MockTaskRunContext struct { - flags *flag.FlagSet - progress *MockTaskRunProgress + flags *flag.FlagSet + progress *MockTaskRunProgress shouldErrorReOpenDB bool shouldErrorRun bool } -func (m *MockTaskRunContext) Flags() *flag.FlagSet { return m.flags } +func (m *MockTaskRunContext) Flags() *flag.FlagSet { return m.flags } func (m *MockTaskRunContext) Progress() aptly.Progress { return m.progress } func (m *MockTaskRunContext) ReOpenDatabase() error { @@ -455,4 +455,4 @@ func (s *TaskRunSuite) TestAptlyTaskRunScannerError(c *C) { // Should succeed with /dev/null but have no commands c.Check(err, NotNil) c.Check(err.Error(), Matches, ".*the file is empty.*") -} \ No newline at end of file +} diff --git a/console/progress_test.go b/console/progress_test.go index 2b412eca..92780122 100644 --- a/console/progress_test.go +++ b/console/progress_test.go @@ -11,7 +11,7 @@ func Test(t *testing.T) { TestingT(t) } -type ProgressSuite struct {} +type ProgressSuite struct{} var _ = Suite(&ProgressSuite{}) diff --git a/context/context.go b/context/context.go index 0ffc3f72..5a4c65ca 100644 --- a/context/context.go +++ b/context/context.go @@ -308,6 +308,11 @@ func (context *AptlyContext) _database() (database.Storage, error) { } context.database, err = goleveldb.NewDB(dbPath) case "etcd": + // Configure etcd from config values + etcddb.ConfigureFromDBConfig( + context.config().DatabaseBackend.Timeout, + context.config().DatabaseBackend.WriteRetries, + ) context.database, err = etcddb.NewDB(context.config().DatabaseBackend.URL) default: context.database, err = goleveldb.NewDB(context.dbPath()) @@ -408,22 +413,42 @@ func (context *AptlyContext) PackagePool() aptly.PackagePool { // GetPublishedStorage returns instance of PublishedStorage func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedStorage { + // Fast path: check if already exists without lock + context.Lock() + publishedStorage, ok := context.publishedStorages[name] + context.Unlock() + + if ok { + return publishedStorage + } + + // Slow path: need to create storage context.Lock() defer context.Unlock() - publishedStorage, ok := context.publishedStorages[name] - if !ok { + // Double-check after acquiring lock + publishedStorage, ok = context.publishedStorages[name] + if ok { + return publishedStorage + } + + // Now safe to create new storage + if true { // Keep original indentation if name == "" { publishedStorage = files.NewPublishedStorage(filepath.Join(context.config().GetRootDir(), "public"), "hardlink", "") } else if strings.HasPrefix(name, "filesystem:") { - params, ok := context.config().FileSystemPublishRoots[name[11:]] + // Get a safe copy of the map + fileSystemRoots := context.config().GetFileSystemPublishRoots() + params, ok := fileSystemRoots[name[11:]] if !ok { Fatal(fmt.Errorf("published local storage %v not configured", name[11:])) } publishedStorage = files.NewPublishedStorage(params.RootDir, params.LinkMethod, params.VerifyMethod) } else if strings.HasPrefix(name, "s3:") { - params, ok := context.config().S3PublishRoots[name[3:]] + // Get a safe copy of the map + s3Roots := context.config().GetS3PublishRoots() + params, ok := s3Roots[name[3:]] if !ok { Fatal(fmt.Errorf("published S3 storage %v not configured", name[3:])) } @@ -433,12 +458,15 @@ func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedSto params.AccessKeyID, params.SecretAccessKey, params.SessionToken, params.Region, params.Endpoint, params.Bucket, params.ACL, params.Prefix, params.StorageClass, params.EncryptionMethod, params.PlusWorkaround, params.DisableMultiDel, - params.ForceSigV2, params.ForceVirtualHostedStyle, params.Debug) + params.ForceSigV2, params.ForceVirtualHostedStyle, params.Debug, params.ConcurrentUploads, + params.UploadQueueSize) if err != nil { Fatal(err) } } else if strings.HasPrefix(name, "swift:") { - params, ok := context.config().SwiftPublishRoots[name[6:]] + // Get a safe copy of the map + swiftRoots := context.config().GetSwiftPublishRoots() + params, ok := swiftRoots[name[6:]] if !ok { Fatal(fmt.Errorf("published Swift storage %v not configured", name[6:])) } @@ -450,7 +478,9 @@ func (context *AptlyContext) GetPublishedStorage(name string) aptly.PublishedSto Fatal(err) } } else if strings.HasPrefix(name, "azure:") { - params, ok := context.config().AzurePublishRoots[name[6:]] + // Get a safe copy of the map + azureRoots := context.config().GetAzurePublishRoots() + params, ok := azureRoots[name[6:]] if !ok { Fatal(fmt.Errorf("published Azure storage %v not configured", name[6:])) } diff --git a/context/context_race_test.go b/context/context_race_test.go index 0efb7802..048447ea 100644 --- a/context/context_race_test.go +++ b/context/context_race_test.go @@ -5,7 +5,7 @@ import ( "sync" "testing" "time" - + "github.com/aptly-dev/aptly/aptly" "github.com/aptly-dev/aptly/utils" ) @@ -15,7 +15,7 @@ func TestPublishedStorageMapRace(t *testing.T) { // Create a context with empty config context := &AptlyContext{} // publishedStorages is now sync.Map, initialized by zero value - + // Mock config utils.Config = utils.ConfigStructure{ RootDir: "/tmp/aptly-test", @@ -23,10 +23,10 @@ func TestPublishedStorageMapRace(t *testing.T) { "test": {RootDir: "/tmp/test", LinkMethod: "hardlink"}, }, } - + var wg sync.WaitGroup errors := make(chan error, 100) - + // Simulate concurrent access to the same storage for i := 0; i < 50; i++ { wg.Add(1) @@ -37,7 +37,7 @@ func TestPublishedStorageMapRace(t *testing.T) { errors <- fmt.Errorf("panic in goroutine %d: %v", id, r) } }() - + // All goroutines try to access the same storage storage := context.GetPublishedStorage("filesystem:test") if storage == nil { @@ -45,7 +45,7 @@ func TestPublishedStorageMapRace(t *testing.T) { } }(i) } - + // Also test different storages to trigger map growth for i := 0; i < 10; i++ { wg.Add(1) @@ -56,24 +56,24 @@ func TestPublishedStorageMapRace(t *testing.T) { errors <- fmt.Errorf("panic in storage %d: %v", id, r) } }() - + // Add new storage configurations storageName := fmt.Sprintf("filesystem:test%d", id) utils.Config.FileSystemPublishRoots[fmt.Sprintf("test%d", id)] = utils.FileSystemPublishRoot{ - RootDir: fmt.Sprintf("/tmp/test%d", id), + RootDir: fmt.Sprintf("/tmp/test%d", id), LinkMethod: "hardlink", } - + storage := context.GetPublishedStorage(storageName) if storage == nil { errors <- fmt.Errorf("got nil storage for %s", storageName) } }(i) } - + wg.Wait() close(errors) - + // Check for any errors or panics for err := range errors { t.Errorf("Race condition error: %v", err) @@ -83,15 +83,15 @@ func TestPublishedStorageMapRace(t *testing.T) { // Test for concurrent map writes func TestPublishedStorageConcurrentWrites(t *testing.T) { context := &AptlyContext{} - + utils.Config = utils.ConfigStructure{ - RootDir: "/tmp/aptly-test", + RootDir: "/tmp/aptly-test", FileSystemPublishRoots: make(map[string]utils.FileSystemPublishRoot), } - + var wg sync.WaitGroup panics := make(chan string, 100) - + // Multiple goroutines trying to create different storages simultaneously for i := 0; i < 20; i++ { wg.Add(1) @@ -102,19 +102,19 @@ func TestPublishedStorageConcurrentWrites(t *testing.T) { panics <- fmt.Sprintf("goroutine %d panicked: %v", id, r) } }() - + storageName := fmt.Sprintf("filesystem:concurrent%d", id) utils.Config.FileSystemPublishRoots[fmt.Sprintf("concurrent%d", id)] = utils.FileSystemPublishRoot{ - RootDir: fmt.Sprintf("/tmp/concurrent%d", id), + RootDir: fmt.Sprintf("/tmp/concurrent%d", id), LinkMethod: "hardlink", } - + // This should trigger concurrent map writes _ = context.GetPublishedStorage(storageName) - + // Add some delay to increase chance of race time.Sleep(time.Millisecond) - + // Access again to ensure consistency storage2 := context.GetPublishedStorage(storageName) if storage2 == nil { @@ -122,10 +122,10 @@ func TestPublishedStorageConcurrentWrites(t *testing.T) { } }(i) } - + wg.Wait() close(panics) - + // Check for panics (indicating race condition) for panic := range panics { t.Errorf("Concurrent map access issue: %s", panic) @@ -137,17 +137,17 @@ func TestPublishedStorageInitRace(t *testing.T) { // Run this test multiple times to increase chance of catching race for attempt := 0; attempt < 10; attempt++ { context := &AptlyContext{} - + utils.Config = utils.ConfigStructure{ RootDir: "/tmp/aptly-test", FileSystemPublishRoots: map[string]utils.FileSystemPublishRoot{ "race": {RootDir: "/tmp/race", LinkMethod: "hardlink"}, }, } - + var wg sync.WaitGroup storages := make([]aptly.PublishedStorage, 10) - + // Multiple goroutines accessing the same non-existent storage for i := 0; i < 10; i++ { wg.Add(1) @@ -156,9 +156,9 @@ func TestPublishedStorageInitRace(t *testing.T) { storages[idx] = context.GetPublishedStorage("filesystem:race") }(i) } - + wg.Wait() - + // All should get the same storage instance firstStorage := storages[0] for i := 1; i < len(storages); i++ { @@ -168,4 +168,4 @@ func TestPublishedStorageInitRace(t *testing.T) { } } } -} \ No newline at end of file +} diff --git a/database/database_test.go b/database/database_test.go index feb4c4cd..f43b88a1 100644 --- a/database/database_test.go +++ b/database/database_test.go @@ -18,15 +18,15 @@ func (s *DatabaseSuite) TestErrNotFound(c *check.C) { // Test that ErrNotFound is properly defined c.Check(ErrNotFound, check.NotNil) c.Check(ErrNotFound.Error(), check.Equals, "key not found") - + // Test that it's an actual error var err error = ErrNotFound c.Check(err, check.NotNil) - + // Test comparison with errors.New newErr := errors.New("key not found") c.Check(ErrNotFound.Error(), check.Equals, newErr.Error()) - + // Test that it's not equal to other errors otherErr := errors.New("other error") c.Check(ErrNotFound.Error(), check.Not(check.Equals), otherErr.Error()) @@ -41,7 +41,7 @@ func (s *DatabaseSuite) TestStorageProcessor(c *check.C) { c.Check(value, check.DeepEquals, []byte("test-value")) return nil } - + err := processor([]byte("test-key"), []byte("test-value")) c.Check(err, check.IsNil) c.Check(called, check.Equals, true) @@ -53,7 +53,7 @@ func (s *DatabaseSuite) TestStorageProcessorWithError(c *check.C) { var processor StorageProcessor = func(key []byte, value []byte) error { return testError } - + err := processor([]byte("key"), []byte("value")) c.Check(err, check.Equals, testError) } @@ -65,7 +65,7 @@ func (s *DatabaseSuite) TestStorageProcessorNilInputs(c *check.C) { c.Check(value, check.DeepEquals, []byte("value")) return nil } - + err := processor(nil, []byte("value")) c.Check(err, check.IsNil) } @@ -77,7 +77,7 @@ func (s *DatabaseSuite) TestStorageProcessorEmptyInputs(c *check.C) { c.Check(len(value), check.Equals, 0) return nil } - + err := processor([]byte{}, []byte{}) c.Check(err, check.IsNil) } @@ -119,14 +119,14 @@ func (s *DatabaseSuite) TestReaderInterface(c *check.C) { "key1": []byte("value1"), "key2": []byte("value2"), } - + var reader Reader = &mockReader{data: data} - + // Test existing key value, err := reader.Get([]byte("key1")) c.Check(err, check.IsNil) c.Check(value, check.DeepEquals, []byte("value1")) - + // Test non-existing key value, err = reader.Get([]byte("nonexistent")) c.Check(err, check.Equals, ErrNotFound) @@ -137,12 +137,12 @@ func (s *DatabaseSuite) TestWriterInterface(c *check.C) { // Test Writer interface implementation data := make(map[string][]byte) var writer Writer = &mockWriter{data: data} - + // Test Put err := writer.Put([]byte("key1"), []byte("value1")) c.Check(err, check.IsNil) c.Check(data["key1"], check.DeepEquals, []byte("value1")) - + // Test Delete err = writer.Delete([]byte("key1")) c.Check(err, check.IsNil) @@ -153,24 +153,24 @@ func (s *DatabaseSuite) TestWriterInterface(c *check.C) { func (s *DatabaseSuite) TestReaderWriterInterface(c *check.C) { // Test ReaderWriter interface implementation data := make(map[string][]byte) - + var rw ReaderWriter = &mockReaderWriter{ mockReader: &mockReader{data: data}, mockWriter: &mockWriter{data: data}, } - + // Test write then read err := rw.Put([]byte("test"), []byte("value")) c.Check(err, check.IsNil) - + value, err := rw.Get([]byte("test")) c.Check(err, check.IsNil) c.Check(value, check.DeepEquals, []byte("value")) - + // Test delete err = rw.Delete([]byte("test")) c.Check(err, check.IsNil) - + value, err = rw.Get([]byte("test")) c.Check(err, check.Equals, ErrNotFound) c.Check(value, check.IsNil) @@ -180,7 +180,7 @@ func (s *DatabaseSuite) TestReaderWriterInterface(c *check.C) { func (s *DatabaseSuite) TestInterfaceDefinitions(c *check.C) { // This test ensures that all interfaces are properly defined // and can be used as interface types - + var reader Reader var prefixReader PrefixReader var writer Writer @@ -188,7 +188,7 @@ func (s *DatabaseSuite) TestInterfaceDefinitions(c *check.C) { var storage Storage var batch Batch var transaction Transaction - + // Test that they are nil by default c.Check(reader, check.IsNil) c.Check(prefixReader, check.IsNil) @@ -203,8 +203,8 @@ func (s *DatabaseSuite) TestErrorConstants(c *check.C) { // Test that error constants are immutable and consistently defined original := ErrNotFound c.Check(original, check.NotNil) - + // Verify it maintains its identity c.Check(ErrNotFound, check.Equals, original) c.Check(ErrNotFound.Error(), check.Equals, original.Error()) -} \ No newline at end of file +} diff --git a/database/etcddb/batch.go b/database/etcddb/batch.go index 3b7d1658..6dc030de 100644 --- a/database/etcddb/batch.go +++ b/database/etcddb/batch.go @@ -44,7 +44,7 @@ func (b *EtcDBatch) Write() (err error) { } batch := b.ops[i:end] - + // Retry logic with exponential backoff var lastErr error for retry := 0; retry <= DefaultWriteRetries; retry++ { @@ -53,27 +53,27 @@ func (b *EtcDBatch) Write() (err error) { txn.Then(batch...) _, err = txn.Commit() cancel() - + if err == nil { // Success, move to next batch break } - + lastErr = err - + // Check if error is retryable if !isRetryableError(err) { log.Error().Err(err).Int("batch_start", i).Int("batch_end", end).Msg("etcd: non-retryable error during batch write") return fmt.Errorf("etcd batch write failed: %w", err) } - + if retry < DefaultWriteRetries { // Calculate exponential backoff backoff := time.Duration(math.Pow(2, float64(retry))) * 100 * time.Millisecond if backoff > 5*time.Second { backoff = 5 * time.Second } - + log.Warn().Err(err). Int("retry", retry+1). Int("max_retries", DefaultWriteRetries). @@ -81,11 +81,11 @@ func (b *EtcDBatch) Write() (err error) { Int("batch_start", i). Int("batch_end", end). Msg("etcd: batch write failed, retrying") - + time.Sleep(backoff) } } - + // All retries exhausted if lastErr != nil { log.Error().Err(lastErr). @@ -105,7 +105,7 @@ func isRetryableError(err error) bool { if err == nil { return false } - + // Check for gRPC status errors if s, ok := status.FromError(err); ok { switch s.Code() { @@ -113,28 +113,28 @@ func isRetryableError(err error) bool { return true } } - + // Check for context errors if err == context.DeadlineExceeded || err == context.Canceled { return true } - + // Check for timeout errors in error message if errStr := err.Error(); errStr != "" { if contains(errStr, "timeout") || contains(errStr, "timed out") || - contains(errStr, "unavailable") || contains(errStr, "connection refused") { + contains(errStr, "unavailable") || contains(errStr, "connection refused") { return true } } - + return false } // contains is a simple string contains helper func contains(s, substr string) bool { - return len(substr) > 0 && len(s) >= len(substr) && + return len(substr) > 0 && len(s) >= len(substr) && (s == substr || s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || - len(s) > len(substr) && findSubstring(s, substr)) + len(s) > len(substr) && findSubstring(s, substr)) } func findSubstring(s, substr string) bool { diff --git a/database/etcddb/database.go b/database/etcddb/database.go index 37a222e6..38335e36 100644 --- a/database/etcddb/database.go +++ b/database/etcddb/database.go @@ -1,24 +1,83 @@ package etcddb import ( - "context" + "os" + "strconv" "time" "github.com/aptly-dev/aptly/database" + "github.com/rs/zerolog/log" clientv3 "go.etcd.io/etcd/client/v3" ) -var Ctx = context.TODO() +// Default timeout for etcd operations +var DefaultTimeout = 120 * time.Second + +// Default write retry count +var DefaultWriteRetries = 3 + +func init() { + // Allow timeout configuration via environment variable + if timeout := os.Getenv("APTLY_ETCD_TIMEOUT"); timeout != "" { + if d, err := time.ParseDuration(timeout); err == nil { + DefaultTimeout = d + log.Info().Dur("timeout", d).Msg("etcd: using custom timeout") + } else { + log.Warn().Str("value", timeout).Err(err).Msg("etcd: invalid timeout value, using default") + } + } + + // Allow write retry configuration via environment variable + if retries := os.Getenv("APTLY_ETCD_WRITE_RETRIES"); retries != "" { + if r, err := strconv.Atoi(retries); err == nil && r >= 0 { + DefaultWriteRetries = r + log.Info().Int("retries", r).Msg("etcd: using custom write retry count") + } else { + log.Warn().Str("value", retries).Err(err).Msg("etcd: invalid write retry value, using default") + } + } +} func internalOpen(url string) (cli *clientv3.Client, err error) { + // Configure dial timeout + dialTimeout := 60 * time.Second + if dt := os.Getenv("APTLY_ETCD_DIAL_TIMEOUT"); dt != "" { + if d, err := time.ParseDuration(dt); err == nil { + dialTimeout = d + } + } + + // Configure keep alive timeout + keepAliveTimeout := 7200 * time.Second + if ka := os.Getenv("APTLY_ETCD_KEEPALIVE"); ka != "" { + if d, err := time.ParseDuration(ka); err == nil { + keepAliveTimeout = d + } + } + + // Configure message size + maxMsgSize := 50 * 1024 * 1024 // 50MiB default + if size := os.Getenv("APTLY_ETCD_MAX_MSG_SIZE"); size != "" { + if s, err := strconv.Atoi(size); err == nil && s > 0 { + maxMsgSize = s + } + } + cfg := clientv3.Config{ Endpoints: []string{url}, - DialTimeout: 30 * time.Second, - MaxCallSendMsgSize: 2147483647, // (2048 * 1024 * 1024) - 1 - MaxCallRecvMsgSize: 2147483647, - DialKeepAliveTimeout: 7200 * time.Second, + DialTimeout: dialTimeout, + MaxCallSendMsgSize: maxMsgSize, + MaxCallRecvMsgSize: maxMsgSize, + DialKeepAliveTimeout: keepAliveTimeout, } + log.Info(). + Str("endpoint", url). + Dur("dialTimeout", dialTimeout). + Dur("keepAlive", keepAliveTimeout). + Int("maxMsgSize", maxMsgSize). + Msg("etcd: opening connection") + cli, err = clientv3.New(cfg) return } @@ -30,3 +89,22 @@ func NewDB(url string) (database.Storage, error) { } return &EtcDStorage{url, cli, ""}, nil } + +// ConfigureFromDBConfig applies configuration from DBConfig +func ConfigureFromDBConfig(timeout string, writeRetries int) { + // Configure timeout if provided + if timeout != "" { + if d, err := time.ParseDuration(timeout); err == nil { + DefaultTimeout = d + log.Info().Dur("timeout", d).Msg("etcd: configured timeout from config") + } else { + log.Warn().Str("value", timeout).Err(err).Msg("etcd: invalid timeout in config, keeping current value") + } + } + + // Configure write retries if provided + if writeRetries > 0 { + DefaultWriteRetries = writeRetries + log.Info().Int("retries", writeRetries).Msg("etcd: configured write retries from config") + } +} diff --git a/database/etcddb/database_test.go b/database/etcddb/database_test.go index ce88209c..c22faa17 100644 --- a/database/etcddb/database_test.go +++ b/database/etcddb/database_test.go @@ -14,7 +14,7 @@ func Test(t *testing.T) { } type EtcDDBSuite struct { - db database.Storage + db database.Storage } var _ = Suite(&EtcDDBSuite{}) @@ -133,7 +133,7 @@ func (s *EtcDDBSuite) TestTransactionCommit(c *C) { v, err := s.db.Get(key) c.Assert(err, IsNil) c.Check(v, DeepEquals, value) - err = transaction.Delete(key) + err = transaction.Delete(key) c.Assert(err, IsNil) _, err = transaction.Get(key2) @@ -156,4 +156,3 @@ func (s *EtcDDBSuite) TestTransactionCommit(c *C) { _, err = transaction.Get(key) c.Assert(err, NotNil) } - diff --git a/database/etcddb/storage.go b/database/etcddb/storage.go index 1937dcac..acd83f03 100644 --- a/database/etcddb/storage.go +++ b/database/etcddb/storage.go @@ -1,10 +1,14 @@ package etcddb import ( + "context" "fmt" + "strings" + "time" "github.com/aptly-dev/aptly/database" "github.com/google/uuid" + "github.com/rs/zerolog/log" clientv3 "go.etcd.io/etcd/client/v3" ) @@ -31,11 +35,66 @@ func (s *EtcDStorage) applyPrefix(key []byte) []byte { return key } +// getContext returns a context with timeout for etcd operations +func (s *EtcDStorage) getContext() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), DefaultTimeout) +} + +// isTemporary checks if error is temporary and can be retried +func isTemporary(err error) bool { + if err == nil { + return false + } + + // Check for context deadline exceeded + if err == context.DeadlineExceeded { + return true + } + + // Check for etcd specific temporary errors + switch err { + case clientv3.ErrNoAvailableEndpoints: + return true + default: + // Check if error string contains temporary indicators + errStr := err.Error() + return strings.Contains(errStr, "temporary") || + strings.Contains(errStr, "timeout") || + strings.Contains(errStr, "unavailable") || + strings.Contains(errStr, "connection refused") + } +} + // Get key value from etcd func (s *EtcDStorage) Get(key []byte) (value []byte, err error) { realKey := s.applyPrefix(key) - getResp, err := s.db.Get(Ctx, string(realKey)) - if err != nil { + + var getResp *clientv3.GetResponse + maxRetries := 3 + + for i := 0; i < maxRetries; i++ { + ctx, cancel := s.getContext() + getResp, err = s.db.Get(ctx, string(realKey)) + cancel() + + if err == nil { + break + } + + // Only retry on temporary errors and not on last attempt + if i < maxRetries-1 && isTemporary(err) { + backoff := time.Duration(i+1) * 100 * time.Millisecond + log.Warn(). + Err(err). + Str("key", string(realKey)). + Int("attempt", i+1). + Dur("backoff", backoff). + Msg("etcd: get failed, retrying") + time.Sleep(backoff) + continue + } + + log.Error().Err(err).Str("key", string(realKey)).Msg("etcd: get failed") return } for _, kv := range getResp.Kvs { @@ -52,8 +111,13 @@ func (s *EtcDStorage) Get(key []byte) (value []byte, err error) { // Put saves key to etcd, if key has the same value in DB already, it is not saved func (s *EtcDStorage) Put(key []byte, value []byte) (err error) { realKey := s.applyPrefix(key) - _, err = s.db.Put(Ctx, string(realKey), string(value)) + + ctx, cancel := s.getContext() + defer cancel() + + _, err = s.db.Put(ctx, string(realKey), string(value)) if err != nil { + log.Error().Err(err).Str("key", string(realKey)).Msg("etcd: put failed") return } return @@ -62,8 +126,13 @@ func (s *EtcDStorage) Put(key []byte, value []byte) (err error) { // Delete removes key from etcd func (s *EtcDStorage) Delete(key []byte) (err error) { realKey := s.applyPrefix(key) - _, err = s.db.Delete(Ctx, string(realKey)) + + ctx, cancel := s.getContext() + defer cancel() + + _, err = s.db.Delete(ctx, string(realKey)) if err != nil { + log.Error().Err(err).Str("key", string(realKey)).Msg("etcd: delete failed") return } return @@ -73,8 +142,13 @@ func (s *EtcDStorage) Delete(key []byte) (err error) { func (s *EtcDStorage) KeysByPrefix(prefix []byte) [][]byte { realPrefix := s.applyPrefix(prefix) result := make([][]byte, 0, 20) - getResp, err := s.db.Get(Ctx, string(realPrefix), clientv3.WithPrefix()) + + ctx, cancel := s.getContext() + defer cancel() + + getResp, err := s.db.Get(ctx, string(realPrefix), clientv3.WithPrefix()) if err != nil { + log.Error().Err(err).Str("prefix", string(realPrefix)).Msg("etcd: keys by prefix failed") return nil } for _, ev := range getResp.Kvs { @@ -90,8 +164,13 @@ func (s *EtcDStorage) KeysByPrefix(prefix []byte) [][]byte { func (s *EtcDStorage) FetchByPrefix(prefix []byte) [][]byte { realPrefix := s.applyPrefix(prefix) result := make([][]byte, 0, 20) - getResp, err := s.db.Get(Ctx, string(realPrefix), clientv3.WithPrefix()) + + ctx, cancel := s.getContext() + defer cancel() + + getResp, err := s.db.Get(ctx, string(realPrefix), clientv3.WithPrefix()) if err != nil { + log.Error().Err(err).Str("prefix", string(realPrefix)).Msg("etcd: fetch by prefix failed") return nil } for _, kv := range getResp.Kvs { @@ -106,8 +185,13 @@ func (s *EtcDStorage) FetchByPrefix(prefix []byte) [][]byte { // HasPrefix checks whether it can find any key with given prefix and returns true if one exists func (s *EtcDStorage) HasPrefix(prefix []byte) bool { realPrefix := s.applyPrefix(prefix) - getResp, err := s.db.Get(Ctx, string(realPrefix), clientv3.WithPrefix()) + + ctx, cancel := s.getContext() + defer cancel() + + getResp, err := s.db.Get(ctx, string(realPrefix), clientv3.WithPrefix()) if err != nil { + log.Error().Err(err).Str("prefix", string(realPrefix)).Msg("etcd: has prefix failed") return false } return getResp.Count > 0 @@ -117,8 +201,13 @@ func (s *EtcDStorage) HasPrefix(prefix []byte) bool { // StorageProcessor on key value pair func (s *EtcDStorage) ProcessByPrefix(prefix []byte, proc database.StorageProcessor) error { realPrefix := s.applyPrefix(prefix) - getResp, err := s.db.Get(Ctx, string(realPrefix), clientv3.WithPrefix()) + + ctx, cancel := s.getContext() + defer cancel() + + getResp, err := s.db.Get(ctx, string(realPrefix), clientv3.WithPrefix()) if err != nil { + log.Error().Err(err).Str("prefix", string(realPrefix)).Msg("etcd: process by prefix failed") return err } @@ -182,12 +271,15 @@ func (s *EtcDStorage) CompactDB() error { // Drop removes only temporary DBs with etcd (i.e. remove all prefixed keys) func (s *EtcDStorage) Drop() error { if len(s.tmpPrefix) != 0 { - getResp, err := s.db.Get(Ctx, s.tmpPrefix, clientv3.WithPrefix()) + ctx, cancel := s.getContext() + defer cancel() + + getResp, err := s.db.Get(ctx, s.tmpPrefix, clientv3.WithPrefix()) if err != nil { return nil } for _, kv := range getResp.Kvs { - _, err = s.db.Delete(Ctx, string(kv.Key)) + _, err = s.db.Delete(ctx, string(kv.Key)) if err != nil { return fmt.Errorf("cannot delete tempdb entry: %s", kv.Key) } diff --git a/database/etcddb/storage_test.go b/database/etcddb/storage_test.go index d60d78bf..22a3b506 100644 --- a/database/etcddb/storage_test.go +++ b/database/etcddb/storage_test.go @@ -17,14 +17,14 @@ func Test(t *testing.T) { TestingT(t) } func (s *StorageSuite) TestGetContext(c *C) { storage := &EtcDStorage{} - + // Test default timeout ctx, cancel := storage.getContext() defer cancel() - + deadline, ok := ctx.Deadline() c.Assert(ok, Equals, true) - + // Should have a deadline set remaining := time.Until(deadline) c.Assert(remaining > 0, Equals, true) @@ -42,7 +42,7 @@ func (s *StorageSuite) TestEnvironmentVariables(c *C) { originalDialTimeout := os.Getenv("APTLY_ETCD_DIAL_TIMEOUT") originalKeepAlive := os.Getenv("APTLY_ETCD_KEEPALIVE") originalMaxMsg := os.Getenv("APTLY_ETCD_MAX_MSG_SIZE") - + defer func() { // Restore original values os.Setenv("APTLY_ETCD_TIMEOUT", originalTimeout) @@ -50,12 +50,12 @@ func (s *StorageSuite) TestEnvironmentVariables(c *C) { os.Setenv("APTLY_ETCD_KEEPALIVE", originalKeepAlive) os.Setenv("APTLY_ETCD_MAX_MSG_SIZE", originalMaxMsg) }() - + // Test valid timeout os.Setenv("APTLY_ETCD_TIMEOUT", "30s") // Would need to reinitialize to test, but we can't easily do that // This test mainly ensures the env vars are recognized - + // Test invalid timeout (should use default) os.Setenv("APTLY_ETCD_TIMEOUT", "invalid") timeout := os.Getenv("APTLY_ETCD_TIMEOUT") @@ -65,10 +65,10 @@ func (s *StorageSuite) TestEnvironmentVariables(c *C) { func (s *StorageSuite) TestIsTemporary(c *C) { // Test nil error c.Assert(isTemporary(nil), Equals, false) - + // Test context deadline exceeded c.Assert(isTemporary(context.DeadlineExceeded), Equals, true) - + // Test timeout error ctx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond) defer cancel() @@ -83,7 +83,7 @@ func (s *StorageSuite) TestApplyPrefix(c *C) { key := []byte("test-key") result := storage.applyPrefix(key) c.Assert(result, DeepEquals, key) - + // Test with temp prefix storage.tmpPrefix = "temp123" result = storage.applyPrefix(key) @@ -96,15 +96,15 @@ func (s *StorageSuite) TestGetRetryLogic(c *C) { // This would require mocking etcd client, which is complex // The test verifies the retry logic exists and compiles // In production, this would be tested with integration tests - + // Verify retry count maxRetries := 3 c.Assert(maxRetries, Equals, 3) - + // Verify backoff calculation for i := 0; i < maxRetries; i++ { backoff := time.Duration(i+1) * 100 * time.Millisecond c.Assert(backoff >= 100*time.Millisecond, Equals, true) c.Assert(backoff <= 300*time.Millisecond, Equals, true) } -} \ No newline at end of file +} diff --git a/database/etcddb/transaction.go b/database/etcddb/transaction.go index 45c2c5f9..31662695 100644 --- a/database/etcddb/transaction.go +++ b/database/etcddb/transaction.go @@ -1,6 +1,8 @@ package etcddb import ( + "context" + "github.com/aptly-dev/aptly/database" clientv3 "go.etcd.io/etcd/client/v3" ) @@ -46,7 +48,9 @@ func (t *transaction) Commit() (err error) { batchSize := 128 for i := 0; i < len(t.ops); i += batchSize { - txn := kv.Txn(Ctx) + ctx, cancel := context.WithTimeout(context.Background(), DefaultTimeout) + defer cancel() + txn := kv.Txn(ctx) end := i + batchSize if end > len(t.ops) { end = len(t.ops) diff --git a/database/goleveldb/database_extended_test.go b/database/goleveldb/database_extended_test.go index c1024df0..25636254 100644 --- a/database/goleveldb/database_extended_test.go +++ b/database/goleveldb/database_extended_test.go @@ -24,23 +24,23 @@ func (s *ExtendedLevelDBSuite) SetUpTest(c *C) { func (s *ExtendedLevelDBSuite) TestNewDB(c *C) { // Test NewDB function dbPath := filepath.Join(s.tempDir, "test-db") - + db, err := goleveldb.NewDB(dbPath) c.Check(err, IsNil) c.Check(db, NotNil) - + // DB should not be open yet _, err = db.Get([]byte("test")) c.Check(err, NotNil) // Should error because DB is not open - + // Open the database err = db.Open() c.Check(err, IsNil) - + // Now should work _, err = db.Get([]byte("test")) c.Check(err, Equals, database.ErrNotFound) // Key not found but no open error - + err = db.Close() c.Check(err, IsNil) } @@ -48,15 +48,15 @@ func (s *ExtendedLevelDBSuite) TestNewDB(c *C) { func (s *ExtendedLevelDBSuite) TestNewOpenDB(c *C) { // Test NewOpenDB function dbPath := filepath.Join(s.tempDir, "test-open-db") - + db, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) c.Check(db, NotNil) - + // DB should be open and ready to use _, err = db.Get([]byte("test")) c.Check(err, Equals, database.ErrNotFound) // Key not found but no open error - + err = db.Close() c.Check(err, IsNil) } @@ -64,7 +64,7 @@ func (s *ExtendedLevelDBSuite) TestNewOpenDB(c *C) { func (s *ExtendedLevelDBSuite) TestRecoverDBError(c *C) { // Test RecoverDB with invalid path invalidPath := "/invalid/nonexistent/path" - + err := goleveldb.RecoverDB(invalidPath) c.Check(err, NotNil) // Should error with invalid path } @@ -72,30 +72,30 @@ func (s *ExtendedLevelDBSuite) TestRecoverDBError(c *C) { func (s *ExtendedLevelDBSuite) TestRecoverDBValidPath(c *C) { // Test RecoverDB with valid database dbPath := filepath.Join(s.tempDir, "recover-test") - + // First create a database db, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) - + // Add some data err = db.Put([]byte("key1"), []byte("value1")) c.Check(err, IsNil) - + err = db.Close() c.Check(err, IsNil) - + // Now recover it err = goleveldb.RecoverDB(dbPath) c.Check(err, IsNil) - + // Verify data is still there after recovery db2, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) - + value, err := db2.Get([]byte("key1")) c.Check(err, IsNil) c.Check(value, DeepEquals, []byte("value1")) - + err = db2.Close() c.Check(err, IsNil) } @@ -103,28 +103,28 @@ func (s *ExtendedLevelDBSuite) TestRecoverDBValidPath(c *C) { func (s *ExtendedLevelDBSuite) TestCreateTemporaryError(c *C) { // Test CreateTemporary with limited permissions (if possible) dbPath := filepath.Join(s.tempDir, "test-temp") - + db, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) - + tempDB, err := db.CreateTemporary() c.Check(err, IsNil) c.Check(tempDB, NotNil) - + // Temporary DB should be usable err = tempDB.Put([]byte("temp-key"), []byte("temp-value")) c.Check(err, IsNil) - + value, err := tempDB.Get([]byte("temp-key")) c.Check(err, IsNil) c.Check(value, DeepEquals, []byte("temp-value")) - + err = tempDB.Close() c.Check(err, IsNil) - + err = tempDB.Drop() c.Check(err, IsNil) - + err = db.Close() c.Check(err, IsNil) } @@ -132,31 +132,31 @@ func (s *ExtendedLevelDBSuite) TestCreateTemporaryError(c *C) { func (s *ExtendedLevelDBSuite) TestStoragePutOptimization(c *C) { // Test Put optimization (doesn't save if value is same) dbPath := filepath.Join(s.tempDir, "put-optimization") - + db, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) - + key := []byte("optimization-key") value := []byte("same-value") - + // First put err = db.Put(key, value) c.Check(err, IsNil) - + // Second put with same value (should be optimized) err = db.Put(key, value) c.Check(err, IsNil) - + // Third put with different value newValue := []byte("different-value") err = db.Put(key, newValue) c.Check(err, IsNil) - + // Verify final value result, err := db.Get(key) c.Check(err, IsNil) c.Check(result, DeepEquals, newValue) - + err = db.Close() c.Check(err, IsNil) } @@ -164,18 +164,18 @@ func (s *ExtendedLevelDBSuite) TestStoragePutOptimization(c *C) { func (s *ExtendedLevelDBSuite) TestStorageCloseMultiple(c *C) { // Test calling Close multiple times dbPath := filepath.Join(s.tempDir, "close-multiple") - + db, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) - + // First close should work err = db.Close() c.Check(err, IsNil) - + // Second close should not error err = db.Close() c.Check(err, IsNil) - + // Third close should not error err = db.Close() c.Check(err, IsNil) @@ -184,22 +184,22 @@ func (s *ExtendedLevelDBSuite) TestStorageCloseMultiple(c *C) { func (s *ExtendedLevelDBSuite) TestStorageOpenMultiple(c *C) { // Test calling Open multiple times dbPath := filepath.Join(s.tempDir, "open-multiple") - + db, err := goleveldb.NewDB(dbPath) c.Check(err, IsNil) - + // First open should work err = db.Open() c.Check(err, IsNil) - + // Second open should not error (already open) err = db.Open() c.Check(err, IsNil) - + // Should still be functional err = db.Put([]byte("test"), []byte("value")) c.Check(err, IsNil) - + err = db.Close() c.Check(err, IsNil) } @@ -207,22 +207,22 @@ func (s *ExtendedLevelDBSuite) TestStorageOpenMultiple(c *C) { func (s *ExtendedLevelDBSuite) TestStorageDropError(c *C) { // Test Drop when database is still open dbPath := filepath.Join(s.tempDir, "drop-error") - + db, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) - + // Try to drop while DB is open (should error) err = db.Drop() c.Check(err, NotNil) c.Check(err.Error(), Equals, "DB is still open") - + // Close and then drop should work err = db.Close() c.Check(err, IsNil) - + err = db.Drop() c.Check(err, IsNil) - + // Verify directory is gone _, err = os.Stat(dbPath) c.Check(os.IsNotExist(err), Equals, true) @@ -231,40 +231,40 @@ func (s *ExtendedLevelDBSuite) TestStorageDropError(c *C) { func (s *ExtendedLevelDBSuite) TestTransactionInterface(c *C) { // Test transaction functionality dbPath := filepath.Join(s.tempDir, "transaction-test") - + db, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) - + // Create transaction tx, err := db.OpenTransaction() c.Check(err, IsNil) c.Check(tx, NotNil) - + // Test transaction operations key := []byte("tx-key") value := []byte("tx-value") - + err = tx.Put(key, value) c.Check(err, IsNil) - + // Value should not be visible outside transaction yet _, err = db.Get(key) c.Check(err, Equals, database.ErrNotFound) - + // But should be visible within transaction txValue, err := tx.Get(key) c.Check(err, IsNil) c.Check(txValue, DeepEquals, value) - + // Commit transaction err = tx.Commit() c.Check(err, IsNil) - + // Now value should be visible finalValue, err := db.Get(key) c.Check(err, IsNil) c.Check(finalValue, DeepEquals, value) - + err = db.Close() c.Check(err, IsNil) } @@ -272,27 +272,27 @@ func (s *ExtendedLevelDBSuite) TestTransactionInterface(c *C) { func (s *ExtendedLevelDBSuite) TestTransactionDiscard(c *C) { // Test transaction discard functionality dbPath := filepath.Join(s.tempDir, "transaction-discard") - + db, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) - + // Create transaction tx, err := db.OpenTransaction() c.Check(err, IsNil) - + key := []byte("discard-key") value := []byte("discard-value") - + err = tx.Put(key, value) c.Check(err, IsNil) - + // Discard transaction tx.Discard() - + // Value should not be visible _, err = db.Get(key) c.Check(err, Equals, database.ErrNotFound) - + err = db.Close() c.Check(err, IsNil) } @@ -300,21 +300,21 @@ func (s *ExtendedLevelDBSuite) TestTransactionDiscard(c *C) { func (s *ExtendedLevelDBSuite) TestProcessByPrefixError(c *C) { // Test ProcessByPrefix with processor that returns error dbPath := filepath.Join(s.tempDir, "process-error") - + db, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) - + // Add some data prefix := []byte("error-") err = db.Put(append(prefix, []byte("key1")...), []byte("value1")) c.Check(err, IsNil) err = db.Put(append(prefix, []byte("key2")...), []byte("value2")) c.Check(err, IsNil) - + // Process with error-returning function testError := errors.New("processing error") processedCount := 0 - + err = db.ProcessByPrefix(prefix, func(key, value []byte) error { processedCount++ if processedCount == 1 { @@ -322,10 +322,10 @@ func (s *ExtendedLevelDBSuite) TestProcessByPrefixError(c *C) { } return nil }) - + c.Check(err, Equals, testError) c.Check(processedCount, Equals, 1) // Should stop at first error - + err = db.Close() c.Check(err, IsNil) } @@ -333,17 +333,17 @@ func (s *ExtendedLevelDBSuite) TestProcessByPrefixError(c *C) { func (s *ExtendedLevelDBSuite) TestPrefixOperationsEmptyDB(c *C) { // Test prefix operations on empty database dbPath := filepath.Join(s.tempDir, "empty-prefix") - + db, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) - + prefix := []byte("empty") - + // All prefix operations should return empty results c.Check(db.HasPrefix(prefix), Equals, false) c.Check(db.KeysByPrefix(prefix), DeepEquals, [][]byte{}) c.Check(db.FetchByPrefix(prefix), DeepEquals, [][]byte{}) - + processedCount := 0 err = db.ProcessByPrefix(prefix, func(key, value []byte) error { processedCount++ @@ -351,7 +351,7 @@ func (s *ExtendedLevelDBSuite) TestPrefixOperationsEmptyDB(c *C) { }) c.Check(err, IsNil) c.Check(processedCount, Equals, 0) - + err = db.Close() c.Check(err, IsNil) } @@ -359,14 +359,14 @@ func (s *ExtendedLevelDBSuite) TestPrefixOperationsEmptyDB(c *C) { func (s *ExtendedLevelDBSuite) TestBatchOperations(c *C) { // Test batch operations in detail dbPath := filepath.Join(s.tempDir, "batch-ops") - + db, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) - + // Create batch batch := db.CreateBatch() c.Check(batch, NotNil) - + // Add multiple operations to batch keys := [][]byte{ []byte("batch-key-1"), @@ -378,29 +378,29 @@ func (s *ExtendedLevelDBSuite) TestBatchOperations(c *C) { []byte("batch-value-2"), []byte("batch-value-3"), } - + for i, key := range keys { err = batch.Put(key, values[i]) c.Check(err, IsNil) } - + // Values should not be visible before Write for _, key := range keys { _, err = db.Get(key) c.Check(err, Equals, database.ErrNotFound) } - + // Write batch err = batch.Write() c.Check(err, IsNil) - + // Now all values should be visible for i, key := range keys { value, err := db.Get(key) c.Check(err, IsNil) c.Check(value, DeepEquals, values[i]) } - + err = db.Close() c.Check(err, IsNil) } @@ -408,10 +408,10 @@ func (s *ExtendedLevelDBSuite) TestBatchOperations(c *C) { func (s *ExtendedLevelDBSuite) TestIteratorEdgeCases(c *C) { // Test iterator edge cases in prefix operations dbPath := filepath.Join(s.tempDir, "iterator-edge") - + db, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) - + // Add data with similar but different prefixes prefixes := [][]byte{ []byte("test"), @@ -421,19 +421,19 @@ func (s *ExtendedLevelDBSuite) TestIteratorEdgeCases(c *C) { []byte("testing"), []byte("totally-different"), } - + for i, prefix := range prefixes { key := append(prefix, []byte("key")...) value := []byte{byte(i)} err = db.Put(key, value) c.Check(err, IsNil) } - + // Test exact prefix matching targetPrefix := []byte("test-") keys := db.KeysByPrefix(targetPrefix) values := db.FetchByPrefix(targetPrefix) - + // Should only match keys that start with "test-" expectedCount := 0 for _, prefix := range prefixes { @@ -444,10 +444,10 @@ func (s *ExtendedLevelDBSuite) TestIteratorEdgeCases(c *C) { } } } - + c.Check(len(keys), Equals, expectedCount) c.Check(len(values), Equals, expectedCount) - + err = db.Close() c.Check(err, IsNil) } @@ -455,14 +455,14 @@ func (s *ExtendedLevelDBSuite) TestIteratorEdgeCases(c *C) { func (s *ExtendedLevelDBSuite) TestCompactDBError(c *C) { // Test CompactDB on closed database dbPath := filepath.Join(s.tempDir, "compact-error") - + db, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) - + // Close database err = db.Close() c.Check(err, IsNil) - + // CompactDB should error on closed database err = db.CompactDB() c.Check(err, NotNil) @@ -471,49 +471,49 @@ func (s *ExtendedLevelDBSuite) TestCompactDBError(c *C) { func (s *ExtendedLevelDBSuite) TestInterface(c *C) { // Test that storage implements database.Storage interface dbPath := filepath.Join(s.tempDir, "interface-test") - + var storage database.Storage storage, err := goleveldb.NewOpenDB(dbPath) c.Check(err, IsNil) c.Check(storage, NotNil) - + // Test that all interface methods are available _, err = storage.Get([]byte("test")) c.Check(err, Equals, database.ErrNotFound) - + err = storage.Put([]byte("test"), []byte("value")) c.Check(err, IsNil) - + err = storage.Delete([]byte("test")) c.Check(err, IsNil) - + c.Check(storage.HasPrefix([]byte("test")), Equals, false) c.Check(storage.KeysByPrefix([]byte("test")), DeepEquals, [][]byte{}) c.Check(storage.FetchByPrefix([]byte("test")), DeepEquals, [][]byte{}) - + err = storage.ProcessByPrefix([]byte("test"), func(k, v []byte) error { return nil }) c.Check(err, IsNil) - + batch := storage.CreateBatch() c.Check(batch, NotNil) - + tx, err := storage.OpenTransaction() c.Check(err, IsNil) c.Check(tx, NotNil) tx.Discard() - + temp, err := storage.CreateTemporary() c.Check(err, IsNil) c.Check(temp, NotNil) temp.Close() temp.Drop() - + err = storage.CompactDB() c.Check(err, IsNil) - + err = storage.Close() c.Check(err, IsNil) - + err = storage.Drop() c.Check(err, IsNil) -} \ No newline at end of file +} diff --git a/database/goleveldb/storage_race_test.go b/database/goleveldb/storage_race_test.go index c3140351..f089fbbb 100644 --- a/database/goleveldb/storage_race_test.go +++ b/database/goleveldb/storage_race_test.go @@ -16,24 +16,24 @@ func TestStorageCloseRace(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(tempdir) - + db, err := internalOpen(tempdir, true) if err != nil { t.Fatal(err) } - + storage := &storage{db: db, path: tempdir} - + // Put some initial data err = storage.Put([]byte("test-key"), []byte("test-value")) if err != nil { t.Fatal(err) } - + var wg sync.WaitGroup errors := make(chan error, 100) panics := make(chan string, 100) - + // Start multiple goroutines doing database operations for i := 0; i < 10; i++ { wg.Add(1) @@ -44,7 +44,7 @@ func TestStorageCloseRace(t *testing.T) { panics <- fmt.Sprintf("goroutine %d panicked: %v", id, r) } }() - + // Continuously perform operations for j := 0; j < 100; j++ { // Try Get operation @@ -53,20 +53,20 @@ func TestStorageCloseRace(t *testing.T) { errors <- fmt.Errorf("get error in goroutine %d: %v", id, err) return } - + // Try Put operation err = storage.Put([]byte(fmt.Sprintf("key-%d-%d", id, j)), []byte("value")) if err != nil && err.Error() != "database is nil" { errors <- fmt.Errorf("put error in goroutine %d: %v", id, err) return } - + // Small delay to increase race window time.Sleep(time.Microsecond) } }(i) } - + // Start goroutines that close the database for i := 0; i < 5; i++ { wg.Add(1) @@ -77,7 +77,7 @@ func TestStorageCloseRace(t *testing.T) { panics <- fmt.Sprintf("close goroutine %d panicked: %v", id, r) } }() - + // Wait a bit then close time.Sleep(time.Duration(id*10) * time.Millisecond) err := storage.Close() @@ -86,16 +86,16 @@ func TestStorageCloseRace(t *testing.T) { } }(i) } - + wg.Wait() close(errors) close(panics) - + // Check for panics (indicates race condition bug) for panic := range panics { t.Errorf("Race condition caused panic: %s", panic) } - + // Some errors are expected (database closed), but panics are not errorCount := 0 for err := range errors { @@ -115,17 +115,17 @@ func TestStorageConcurrentOpsVsClose(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(tempdir) - + db, err := internalOpen(tempdir, true) if err != nil { t.Fatal(err) } - + storage := &storage{db: db, path: tempdir} - + var wg sync.WaitGroup panicked := make(chan bool, 1) - + // Goroutine performing operations wg.Add(1) go func() { @@ -135,14 +135,14 @@ func TestStorageConcurrentOpsVsClose(t *testing.T) { panicked <- true } }() - + for i := 0; i < 1000; i++ { storage.Get([]byte("key")) storage.Put([]byte("key"), []byte("value")) storage.Delete([]byte("key")) } }() - + // Goroutine closing database wg.Add(1) go func() { @@ -150,9 +150,9 @@ func TestStorageConcurrentOpsVsClose(t *testing.T) { time.Sleep(50 * time.Millisecond) // Let operations start storage.Close() }() - + wg.Wait() - + // Check if panic occurred select { case <-panicked: @@ -160,7 +160,7 @@ func TestStorageConcurrentOpsVsClose(t *testing.T) { default: // No panic - good } - + close(panicked) } } @@ -172,17 +172,17 @@ func TestStorageMultipleClose(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(tempdir) - + db, err := internalOpen(tempdir, true) if err != nil { t.Fatal(err) } - + storage := &storage{db: db, path: tempdir} - + var wg sync.WaitGroup panics := make(chan string, 20) - + // Multiple goroutines trying to close for i := 0; i < 20; i++ { wg.Add(1) @@ -193,7 +193,7 @@ func TestStorageMultipleClose(t *testing.T) { panics <- fmt.Sprintf("close %d panicked: %v", id, r) } }() - + err := storage.Close() if err != nil { // Error is ok, panic is not @@ -201,10 +201,10 @@ func TestStorageMultipleClose(t *testing.T) { } }(i) } - + wg.Wait() close(panics) - + // Check for panics for panic := range panics { t.Errorf("Multiple close caused panic: %s", panic) @@ -218,22 +218,22 @@ func TestStorageIteratorRace(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(tempdir) - + db, err := internalOpen(tempdir, true) if err != nil { t.Fatal(err) } - + storage := &storage{db: db, path: tempdir} - + // Add some data for i := 0; i < 100; i++ { storage.Put([]byte(fmt.Sprintf("key-%03d", i)), []byte("value")) } - + var wg sync.WaitGroup panics := make(chan string, 10) - + // Goroutines using iterators for i := 0; i < 5; i++ { wg.Add(1) @@ -244,14 +244,14 @@ func TestStorageIteratorRace(t *testing.T) { panics <- fmt.Sprintf("iterator %d panicked: %v", id, r) } }() - + // Use methods that create iterators storage.KeysByPrefix([]byte("key-")) storage.FetchByPrefix([]byte("key-")) storage.HasPrefix([]byte("key-")) }(i) } - + // Close database while iterators are running wg.Add(1) go func() { @@ -259,12 +259,12 @@ func TestStorageIteratorRace(t *testing.T) { time.Sleep(10 * time.Millisecond) storage.Close() }() - + wg.Wait() close(panics) - + // Check for panics for panic := range panics { t.Errorf("Iterator race caused panic: %s", panic) } -} \ No newline at end of file +} diff --git a/database/goleveldb/storage_test.go b/database/goleveldb/storage_test.go index 436f3bf2..cbf98f62 100644 --- a/database/goleveldb/storage_test.go +++ b/database/goleveldb/storage_test.go @@ -28,7 +28,7 @@ func (s *LevelDBStorageSuite) TestCreateTemporary(c *C) { c.Check(err, NotNil) return } - + c.Check(tempStorage, NotNil) levelStorage, ok := tempStorage.(*storage) c.Check(ok, Equals, true) @@ -118,4 +118,4 @@ func (s *LevelDBStorageSuite) TestProcessByPrefixNilDB(c *C) { processor := func(key, value []byte) error { return nil } err := s.storage.ProcessByPrefix([]byte("prefix/"), processor) c.Check(err, NotNil) // Expected to fail with nil DB -} \ No newline at end of file +} diff --git a/database/goleveldb/transaction_test.go b/database/goleveldb/transaction_test.go index 60235c04..71aece8a 100644 --- a/database/goleveldb/transaction_test.go +++ b/database/goleveldb/transaction_test.go @@ -31,8 +31,8 @@ func (s *TransactionSuite) TestInterfaceCompliance(c *C) { // Test that storage implements the transaction interface var storageInterface database.Storage = &storage{} c.Check(storageInterface, NotNil) - + // Test that we can call OpenTransaction method _, err := storageInterface.OpenTransaction() c.Check(err, NotNil) // Expected to fail without proper setup -} \ No newline at end of file +} diff --git a/deb/collections_test.go b/deb/collections_test.go index f8e8f0b1..b15606f5 100644 --- a/deb/collections_test.go +++ b/deb/collections_test.go @@ -33,7 +33,7 @@ func (s *CollectionsSuite) TestTemporaryDB(c *C) { tempDB, err := s.factory.TemporaryDB() c.Check(err, IsNil) c.Check(tempDB, NotNil) - + // Clean up tempDB.Close() tempDB.Drop() @@ -43,7 +43,7 @@ func (s *CollectionsSuite) TestPackageCollection(c *C) { // First call creates the collection collection1 := s.factory.PackageCollection() c.Check(collection1, NotNil) - + // Second call returns the same instance collection2 := s.factory.PackageCollection() c.Check(collection2, Equals, collection1) @@ -53,7 +53,7 @@ func (s *CollectionsSuite) TestRemoteRepoCollection(c *C) { // First call creates the collection collection1 := s.factory.RemoteRepoCollection() c.Check(collection1, NotNil) - + // Second call returns the same instance collection2 := s.factory.RemoteRepoCollection() c.Check(collection2, Equals, collection1) @@ -63,7 +63,7 @@ func (s *CollectionsSuite) TestSnapshotCollection(c *C) { // First call creates the collection collection1 := s.factory.SnapshotCollection() c.Check(collection1, NotNil) - + // Second call returns the same instance collection2 := s.factory.SnapshotCollection() c.Check(collection2, Equals, collection1) @@ -73,7 +73,7 @@ func (s *CollectionsSuite) TestLocalRepoCollection(c *C) { // First call creates the collection collection1 := s.factory.LocalRepoCollection() c.Check(collection1, NotNil) - + // Second call returns the same instance collection2 := s.factory.LocalRepoCollection() c.Check(collection2, Equals, collection1) @@ -83,7 +83,7 @@ func (s *CollectionsSuite) TestPublishedRepoCollection(c *C) { // First call creates the collection collection1 := s.factory.PublishedRepoCollection() c.Check(collection1, NotNil) - + // Second call returns the same instance collection2 := s.factory.PublishedRepoCollection() c.Check(collection2, Equals, collection1) @@ -93,7 +93,7 @@ func (s *CollectionsSuite) TestChecksumCollectionWithNilDB(c *C) { // First call with nil DB creates the collection collection1 := s.factory.ChecksumCollection(nil) c.Check(collection1, NotNil) - + // Second call with nil DB returns the same instance collection2 := s.factory.ChecksumCollection(nil) c.Check(collection2, Equals, collection1) @@ -105,11 +105,11 @@ func (s *CollectionsSuite) TestChecksumCollectionWithDB(c *C) { c.Check(err, IsNil) defer tempDB.Close() defer tempDB.Drop() - + // Call with specific DB creates new collection collection1 := s.factory.ChecksumCollection(tempDB) c.Check(collection1, NotNil) - + // Call with different DB creates different collection collection2 := s.factory.ChecksumCollection(s.db) c.Check(collection2, NotNil) @@ -124,17 +124,17 @@ func (s *CollectionsSuite) TestFlush(c *C) { localRepos := s.factory.LocalRepoCollection() publishedRepos := s.factory.PublishedRepoCollection() checksums := s.factory.ChecksumCollection(nil) - + c.Check(packages, NotNil) c.Check(remoteRepos, NotNil) c.Check(snapshots, NotNil) c.Check(localRepos, NotNil) c.Check(publishedRepos, NotNil) c.Check(checksums, NotNil) - + // Flush all collections s.factory.Flush() - + // After flush, new calls should create new instances newPackages := s.factory.PackageCollection() newRemoteRepos := s.factory.RemoteRepoCollection() @@ -142,7 +142,7 @@ func (s *CollectionsSuite) TestFlush(c *C) { newLocalRepos := s.factory.LocalRepoCollection() newPublishedRepos := s.factory.PublishedRepoCollection() newChecksums := s.factory.ChecksumCollection(nil) - + c.Check(newPackages, Not(Equals), packages) c.Check(newRemoteRepos, Not(Equals), remoteRepos) c.Check(newSnapshots, Not(Equals), snapshots) @@ -154,7 +154,7 @@ func (s *CollectionsSuite) TestFlush(c *C) { func (s *CollectionsSuite) TestConcurrentAccess(c *C) { // Test that concurrent access to collections works properly done := make(chan bool, 10) - + for i := 0; i < 10; i++ { go func() { // Each goroutine should get the same instances @@ -164,23 +164,23 @@ func (s *CollectionsSuite) TestConcurrentAccess(c *C) { localRepos := s.factory.LocalRepoCollection() publishedRepos := s.factory.PublishedRepoCollection() checksums := s.factory.ChecksumCollection(nil) - + c.Check(packages, NotNil) c.Check(remoteRepos, NotNil) c.Check(snapshots, NotNil) c.Check(localRepos, NotNil) c.Check(publishedRepos, NotNil) c.Check(checksums, NotNil) - + done <- true }() } - + // Wait for all goroutines to complete for i := 0; i < 10; i++ { <-done } - + // Verify that all collections are still accessible packages := s.factory.PackageCollection() c.Check(packages, NotNil) @@ -190,22 +190,22 @@ func (s *CollectionsSuite) TestFlushAndRecreate(c *C) { // Create collections, use them, flush, then recreate originalPackages := s.factory.PackageCollection() c.Check(originalPackages, NotNil) - + // Add a package to test that it exists pkg := NewPackageFromControlFile(packageStanza.Copy()) err := originalPackages.Update(pkg) c.Check(err, IsNil) - + // Flush s.factory.Flush() - + // Get new collection newPackages := s.factory.PackageCollection() c.Check(newPackages, NotNil) c.Check(newPackages, Not(Equals), originalPackages) - + // The package should still exist in the database retrievedPkg, err := newPackages.ByKey(pkg.Key("")) c.Check(err, IsNil) c.Check(retrievedPkg.Name, Equals, pkg.Name) -} \ No newline at end of file +} diff --git a/deb/contents_test.go b/deb/contents_test.go index 0a01c766..ef1b87d0 100644 --- a/deb/contents_test.go +++ b/deb/contents_test.go @@ -24,7 +24,7 @@ func (s *ContentsIndexSuite) SetUpTest(c *C) { func (s *ContentsIndexSuite) TestNewContentsIndex(c *C) { // Test ContentsIndex creation index := NewContentsIndex(s.mockDB) - + c.Check(index, NotNil) c.Check(index.db, Equals, s.mockDB) c.Check(len(index.prefix), Equals, 36) // UUID length @@ -33,13 +33,13 @@ func (s *ContentsIndexSuite) TestNewContentsIndex(c *C) { func (s *ContentsIndexSuite) TestContentsIndexEmpty(c *C) { // Test Empty method index := NewContentsIndex(s.mockDB) - + // Should be empty initially c.Check(index.Empty(), Equals, true) - + // Add some data s.mockDB.prefixes[string(index.prefix)] = true - + // Should not be empty now c.Check(index.Empty(), Equals, false) } @@ -48,20 +48,20 @@ func (s *ContentsIndexSuite) TestContentsIndexPush(c *C) { // Test Push method index := NewContentsIndex(s.mockDB) writer := &MockWriter{storage: s.mockDB} - + qualifiedName := []byte("package_1.0_amd64") contents := []string{ "/usr/bin/program", "/usr/share/doc/package/README", "/etc/package.conf", } - + err := index.Push(qualifiedName, contents, writer) c.Check(err, IsNil) - + // Verify data was written c.Check(len(s.mockDB.data), Equals, 3) - + // Check that keys contain the expected format for path := range contents { expectedKey := string(index.prefix) + contents[path] + "\x00" + string(qualifiedName) @@ -74,10 +74,10 @@ func (s *ContentsIndexSuite) TestContentsIndexPushError(c *C) { // Test Push method with writer error index := NewContentsIndex(s.mockDB) writer := &MockWriter{storage: s.mockDB, shouldError: true} - + qualifiedName := []byte("package_1.0_amd64") contents := []string{"/usr/bin/program"} - + err := index.Push(qualifiedName, contents, writer) c.Check(err, NotNil) c.Check(err.Error(), Equals, "mock writer error") @@ -87,24 +87,24 @@ func (s *ContentsIndexSuite) TestContentsIndexWriteTo(c *C) { // Test WriteTo method index := NewContentsIndex(s.mockDB) writer := &MockWriter{storage: s.mockDB} - + // Add some packages err := index.Push([]byte("package1_1.0_amd64"), []string{"/usr/bin/prog1", "/usr/share/file1"}, writer) c.Check(err, IsNil) - + err = index.Push([]byte("package2_2.0_amd64"), []string{"/usr/bin/prog2", "/usr/share/file1"}, writer) c.Check(err, IsNil) - + // Set up processor to simulate database iteration s.mockDB.processor = func(prefix []byte, fn database.StorageProcessor) error { // Simulate database keys in sorted order keys := []string{ string(prefix) + "/usr/bin/prog1\x00package1_1.0_amd64", - string(prefix) + "/usr/bin/prog2\x00package2_2.0_amd64", + string(prefix) + "/usr/bin/prog2\x00package2_2.0_amd64", string(prefix) + "/usr/share/file1\x00package1_1.0_amd64", string(prefix) + "/usr/share/file1\x00package2_2.0_amd64", } - + for _, key := range keys { err := fn([]byte(key), nil) if err != nil { @@ -113,15 +113,15 @@ func (s *ContentsIndexSuite) TestContentsIndexWriteTo(c *C) { } return nil } - + var buf bytes.Buffer n, err := index.WriteTo(&buf) c.Check(err, IsNil) c.Check(n, Equals, int64(buf.Len())) - + output := buf.String() lines := strings.Split(strings.TrimSpace(output), "\n") - + // Should have header plus content lines c.Check(len(lines), Equals, 4) c.Check(lines[0], Equals, "FILE LOCATION") @@ -133,17 +133,17 @@ func (s *ContentsIndexSuite) TestContentsIndexWriteTo(c *C) { func (s *ContentsIndexSuite) TestContentsIndexWriteToEmpty(c *C) { // Test WriteTo with empty index index := NewContentsIndex(s.mockDB) - + s.mockDB.processor = func(prefix []byte, fn database.StorageProcessor) error { // No entries return nil } - + var buf bytes.Buffer n, err := index.WriteTo(&buf) c.Check(err, IsNil) c.Check(n, Equals, int64(buf.Len())) - + output := buf.String() c.Check(output, Equals, "FILE LOCATION\n") } @@ -151,13 +151,13 @@ func (s *ContentsIndexSuite) TestContentsIndexWriteToEmpty(c *C) { func (s *ContentsIndexSuite) TestContentsIndexWriteToCorruptedEntry(c *C) { // Test WriteTo with corrupted database entry index := NewContentsIndex(s.mockDB) - + s.mockDB.processor = func(prefix []byte, fn database.StorageProcessor) error { // Corrupted key without null byte separator corruptedKey := string(prefix) + "/usr/bin/prog1package_name" return fn([]byte(corruptedKey), nil) } - + var buf bytes.Buffer _, err := index.WriteTo(&buf) c.Check(err, NotNil) @@ -168,7 +168,7 @@ func (s *ContentsIndexSuite) TestContentsIndexPushMultiplePackages(c *C) { // Test pushing multiple packages index := NewContentsIndex(s.mockDB) writer := &MockWriter{storage: s.mockDB} - + packages := []struct { name string contents []string @@ -177,12 +177,12 @@ func (s *ContentsIndexSuite) TestContentsIndexPushMultiplePackages(c *C) { {"package2_2.0_amd64", []string{"/usr/bin/prog2", "/usr/share/doc2"}}, {"package3_3.0_amd64", []string{"/usr/bin/prog3"}}, } - + for _, pkg := range packages { err := index.Push([]byte(pkg.name), pkg.contents, writer) c.Check(err, IsNil, Commentf("Failed to push package: %s", pkg.name)) } - + // Verify all entries were written expectedEntries := 2 + 2 + 1 // Total files across all packages c.Check(len(s.mockDB.data), Equals, expectedEntries) @@ -192,10 +192,10 @@ func (s *ContentsIndexSuite) TestContentsIndexPushEmptyContents(c *C) { // Test pushing package with no contents index := NewContentsIndex(s.mockDB) writer := &MockWriter{storage: s.mockDB} - + err := index.Push([]byte("empty_package"), []string{}, writer) c.Check(err, IsNil) - + // Should not add any entries c.Check(len(s.mockDB.data), Equals, 0) } @@ -204,17 +204,17 @@ func (s *ContentsIndexSuite) TestContentsIndexSpecialCharacters(c *C) { // Test with special characters in paths and package names index := NewContentsIndex(s.mockDB) writer := &MockWriter{storage: s.mockDB} - + qualifiedName := []byte("special-package_1.0+build1_amd64") contents := []string{ "/usr/bin/prog-with-dashes", "/usr/share/file with spaces", "/etc/config.d/file.conf", } - + err := index.Push(qualifiedName, contents, writer) c.Check(err, IsNil) - + c.Check(len(s.mockDB.data), Equals, 3) } @@ -222,14 +222,14 @@ func (s *ContentsIndexSuite) TestContentsIndexBinaryData(c *C) { // Test with binary data in paths (edge case) index := NewContentsIndex(s.mockDB) writer := &MockWriter{storage: s.mockDB} - + // Path with binary data binaryPath := "/usr/bin/prog\x00\xFF\xFE" qualifiedName := []byte("binary_package_1.0_amd64") - + err := index.Push(qualifiedName, []string{binaryPath}, writer) c.Check(err, IsNil) - + c.Check(len(s.mockDB.data), Equals, 1) } @@ -261,7 +261,7 @@ func (m *MockStorage) HasPrefix(prefix []byte) bool { if exists, ok := m.prefixes[string(prefix)]; ok { return exists } - + // Check if any key has this prefix for key := range m.data { if strings.HasPrefix(key, string(prefix)) { @@ -275,7 +275,7 @@ func (m *MockStorage) ProcessByPrefix(prefix []byte, fn database.StorageProcesso if m.processor != nil { return m.processor(prefix, fn) } - + // Default implementation - process matching keys for key, value := range m.data { if strings.HasPrefix(key, string(prefix)) { @@ -403,4 +403,4 @@ type MockError struct { func (e *MockError) Error() string { return e.message -} \ No newline at end of file +} diff --git a/deb/graph_test.go b/deb/graph_test.go index c2ef5c2a..626dbd73 100644 --- a/deb/graph_test.go +++ b/deb/graph_test.go @@ -39,4 +39,4 @@ func (s *GraphSuite) TestBuildGraphUnknownLayout(c *C) { graph, err := BuildGraph(s.collectionFactory, "unknown") c.Check(err, IsNil) c.Check(graph, NotNil) -} \ No newline at end of file +} diff --git a/deb/import_test.go b/deb/import_test.go index 1936d66b..6c4a4109 100644 --- a/deb/import_test.go +++ b/deb/import_test.go @@ -48,7 +48,7 @@ func (s *ImportSuite) TestCollectPackageFilesEmpty(c *C) { // Test with empty locations list reporter := &MockResultReporter{} packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{}, reporter) - + c.Check(len(packageFiles), Equals, 0) c.Check(len(otherFiles), Equals, 0) c.Check(len(failedFiles), Equals, 0) @@ -59,9 +59,9 @@ func (s *ImportSuite) TestCollectPackageFilesNonExistentLocation(c *C) { // Test with non-existent location reporter := &MockResultReporter{} nonExistentPath := filepath.Join(s.tempDir, "nonexistent") - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{nonExistentPath}, reporter) - + c.Check(len(packageFiles), Equals, 0) c.Check(len(otherFiles), Equals, 0) c.Check(len(failedFiles), Equals, 1) @@ -74,13 +74,13 @@ func (s *ImportSuite) TestCollectPackageFilesSingleDebFile(c *C) { // Test with single .deb file reporter := &MockResultReporter{} debFile := filepath.Join(s.tempDir, "package.deb") - + // Create dummy .deb file err := ioutil.WriteFile(debFile, []byte("dummy deb content"), 0644) c.Assert(err, IsNil) - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{debFile}, reporter) - + c.Check(len(packageFiles), Equals, 1) c.Check(packageFiles[0], Equals, debFile) c.Check(len(otherFiles), Equals, 0) @@ -92,12 +92,12 @@ func (s *ImportSuite) TestCollectPackageFilesSingleUdebFile(c *C) { // Test with single .udeb file reporter := &MockResultReporter{} udebFile := filepath.Join(s.tempDir, "package.udeb") - + err := ioutil.WriteFile(udebFile, []byte("dummy udeb content"), 0644) c.Assert(err, IsNil) - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{udebFile}, reporter) - + c.Check(len(packageFiles), Equals, 1) c.Check(packageFiles[0], Equals, udebFile) c.Check(len(otherFiles), Equals, 0) @@ -108,12 +108,12 @@ func (s *ImportSuite) TestCollectPackageFilesSingleDscFile(c *C) { // Test with single .dsc file reporter := &MockResultReporter{} dscFile := filepath.Join(s.tempDir, "package.dsc") - + err := ioutil.WriteFile(dscFile, []byte("dummy dsc content"), 0644) c.Assert(err, IsNil) - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{dscFile}, reporter) - + c.Check(len(packageFiles), Equals, 1) c.Check(packageFiles[0], Equals, dscFile) c.Check(len(otherFiles), Equals, 0) @@ -124,12 +124,12 @@ func (s *ImportSuite) TestCollectPackageFilesSingleDdebFile(c *C) { // Test with single .ddeb file reporter := &MockResultReporter{} ddebFile := filepath.Join(s.tempDir, "package.ddeb") - + err := ioutil.WriteFile(ddebFile, []byte("dummy ddeb content"), 0644) c.Assert(err, IsNil) - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{ddebFile}, reporter) - + c.Check(len(packageFiles), Equals, 1) c.Check(packageFiles[0], Equals, ddebFile) c.Check(len(otherFiles), Equals, 0) @@ -140,12 +140,12 @@ func (s *ImportSuite) TestCollectPackageFilesBuildInfoFile(c *C) { // Test with .buildinfo file reporter := &MockResultReporter{} buildinfoFile := filepath.Join(s.tempDir, "package.buildinfo") - + err := ioutil.WriteFile(buildinfoFile, []byte("dummy buildinfo content"), 0644) c.Assert(err, IsNil) - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{buildinfoFile}, reporter) - + c.Check(len(packageFiles), Equals, 0) c.Check(len(otherFiles), Equals, 1) c.Check(otherFiles[0], Equals, buildinfoFile) @@ -157,12 +157,12 @@ func (s *ImportSuite) TestCollectPackageFilesUnknownExtension(c *C) { // Test with unknown file extension reporter := &MockResultReporter{} unknownFile := filepath.Join(s.tempDir, "package.unknown") - + err := ioutil.WriteFile(unknownFile, []byte("dummy content"), 0644) c.Assert(err, IsNil) - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{unknownFile}, reporter) - + c.Check(len(packageFiles), Equals, 0) c.Check(len(otherFiles), Equals, 0) c.Check(len(failedFiles), Equals, 1) @@ -177,23 +177,23 @@ func (s *ImportSuite) TestCollectPackageFilesDirectory(c *C) { subDir := filepath.Join(s.tempDir, "packages") err := os.MkdirAll(subDir, 0755) c.Assert(err, IsNil) - + // Create various file types files := map[string]string{ "package1.deb": "deb content", - "package2.udeb": "udeb content", + "package2.udeb": "udeb content", "source.dsc": "dsc content", "debug.ddeb": "ddeb content", "build.buildinfo": "buildinfo content", "readme.txt": "text content", "subdir/nested.deb": "nested deb", } - + // Create nested subdirectory nestedDir := filepath.Join(subDir, "subdir") err = os.MkdirAll(nestedDir, 0755) c.Assert(err, IsNil) - + for filename, content := range files { fullPath := filepath.Join(subDir, filename) err := os.MkdirAll(filepath.Dir(fullPath), 0755) @@ -201,9 +201,9 @@ func (s *ImportSuite) TestCollectPackageFilesDirectory(c *C) { err = ioutil.WriteFile(fullPath, []byte(content), 0644) c.Assert(err, IsNil) } - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{subDir}, reporter) - + // Should find package files (sorted) expectedPackageFiles := []string{ filepath.Join(subDir, "debug.ddeb"), @@ -213,14 +213,14 @@ func (s *ImportSuite) TestCollectPackageFilesDirectory(c *C) { filepath.Join(subDir, "subdir", "nested.deb"), } sort.Strings(expectedPackageFiles) - + c.Check(len(packageFiles), Equals, 5) c.Check(packageFiles, DeepEquals, expectedPackageFiles) - + // Should find other files c.Check(len(otherFiles), Equals, 1) c.Check(otherFiles[0], Equals, filepath.Join(subDir, "build.buildinfo")) - + // No failed files c.Check(len(failedFiles), Equals, 0) c.Check(len(reporter.warnings), Equals, 0) @@ -229,26 +229,26 @@ func (s *ImportSuite) TestCollectPackageFilesDirectory(c *C) { func (s *ImportSuite) TestCollectPackageFilesMixedLocations(c *C) { // Test with mix of files and directories reporter := &MockResultReporter{} - + // Create individual file debFile := filepath.Join(s.tempDir, "single.deb") err := ioutil.WriteFile(debFile, []byte("single deb"), 0644) c.Assert(err, IsNil) - + // Create directory with files subDir := filepath.Join(s.tempDir, "multi") err = os.MkdirAll(subDir, 0755) c.Assert(err, IsNil) - + dscFile := filepath.Join(subDir, "source.dsc") err = ioutil.WriteFile(dscFile, []byte("dsc content"), 0644) c.Assert(err, IsNil) - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{debFile, subDir}, reporter) - + expectedFiles := []string{debFile, dscFile} sort.Strings(expectedFiles) - + c.Check(len(packageFiles), Equals, 2) c.Check(packageFiles, DeepEquals, expectedFiles) c.Check(len(otherFiles), Equals, 0) @@ -261,26 +261,26 @@ func (s *ImportSuite) TestCollectPackageFilesConcurrency(c *C) { subDir := filepath.Join(s.tempDir, "concurrent") err := os.MkdirAll(subDir, 0755) c.Assert(err, IsNil) - + // Create many files to test concurrent access for i := 0; i < 100; i++ { filename := filepath.Join(subDir, fmt.Sprintf("package%d.deb", i)) err := ioutil.WriteFile(filename, []byte(fmt.Sprintf("content %d", i)), 0644) c.Assert(err, IsNil) - + if i%10 == 0 { buildinfoFile := filepath.Join(subDir, fmt.Sprintf("build%d.buildinfo", i)) err = ioutil.WriteFile(buildinfoFile, []byte(fmt.Sprintf("buildinfo %d", i)), 0644) c.Assert(err, IsNil) } } - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{subDir}, reporter) - + c.Check(len(packageFiles), Equals, 100) c.Check(len(otherFiles), Equals, 10) // Every 10th file is buildinfo c.Check(len(failedFiles), Equals, 0) - + // Check that files are sorted c.Check(sort.StringsAreSorted(packageFiles), Equals, true) } @@ -288,26 +288,26 @@ func (s *ImportSuite) TestCollectPackageFilesConcurrency(c *C) { func (s *ImportSuite) TestCollectPackageFilesPermissionDenied(c *C) { // Test handling of permission denied errors reporter := &MockResultReporter{} - + // Create directory and remove read permission (if running as non-root) subDir := filepath.Join(s.tempDir, "noperm") err := os.MkdirAll(subDir, 0755) c.Assert(err, IsNil) - + // Create a file inside testFile := filepath.Join(subDir, "test.deb") err = ioutil.WriteFile(testFile, []byte("test"), 0644) c.Assert(err, IsNil) - + // Remove read permission from directory err = os.Chmod(subDir, 0000) if err != nil { c.Skip("Cannot remove permissions, likely running as root") } defer os.Chmod(subDir, 0755) // Restore for cleanup - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{subDir}, reporter) - + // Should handle permission error gracefully c.Check(len(packageFiles), Equals, 0) c.Check(len(otherFiles), Equals, 0) @@ -323,9 +323,9 @@ func (s *ImportSuite) TestCollectPackageFilesEmptyDirectory(c *C) { emptyDir := filepath.Join(s.tempDir, "empty") err := os.MkdirAll(emptyDir, 0755) c.Assert(err, IsNil) - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{emptyDir}, reporter) - + c.Check(len(packageFiles), Equals, 0) c.Check(len(otherFiles), Equals, 0) c.Check(len(failedFiles), Equals, 0) @@ -335,31 +335,31 @@ func (s *ImportSuite) TestCollectPackageFilesEmptyDirectory(c *C) { func (s *ImportSuite) TestCollectPackageFilesNestedDirectories(c *C) { // Test deeply nested directory structure reporter := &MockResultReporter{} - + // Create nested structure: base/level1/level2/level3/ deepDir := filepath.Join(s.tempDir, "base", "level1", "level2", "level3") err := os.MkdirAll(deepDir, 0755) c.Assert(err, IsNil) - + // Place files at different levels files := map[string]string{ - filepath.Join(s.tempDir, "base", "root.deb"): "root", - filepath.Join(s.tempDir, "base", "level1", "level1.deb"): "level1", - filepath.Join(s.tempDir, "base", "level1", "level2", "level2.deb"): "level2", + filepath.Join(s.tempDir, "base", "root.deb"): "root", + filepath.Join(s.tempDir, "base", "level1", "level1.deb"): "level1", + filepath.Join(s.tempDir, "base", "level1", "level2", "level2.deb"): "level2", filepath.Join(s.tempDir, "base", "level1", "level2", "level3", "deep.deb"): "deep", } - + for path, content := range files { err := ioutil.WriteFile(path, []byte(content), 0644) c.Assert(err, IsNil) } - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{filepath.Join(s.tempDir, "base")}, reporter) - + c.Check(len(packageFiles), Equals, 4) c.Check(len(otherFiles), Equals, 0) c.Check(len(failedFiles), Equals, 0) - + // Verify all nested files were found for expectedPath := range files { found := false @@ -376,7 +376,7 @@ func (s *ImportSuite) TestCollectPackageFilesNestedDirectories(c *C) { func (s *ImportSuite) TestCollectPackageFilesCaseInsensitive(c *C) { // Test case sensitivity of file extensions reporter := &MockResultReporter{} - + // Create files with various case extensions files := []string{ "package.deb", @@ -389,41 +389,41 @@ func (s *ImportSuite) TestCollectPackageFilesCaseInsensitive(c *C) { "debug.ddeb", "debug.DDEB", } - + for _, filename := range files { path := filepath.Join(s.tempDir, filename) err := ioutil.WriteFile(path, []byte("content"), 0644) c.Assert(err, IsNil) } - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{s.tempDir}, reporter) - + // Only lowercase extensions should be recognized c.Check(len(packageFiles), Equals, 4) // .deb, .dsc, .udeb, .ddeb (lowercase only) c.Check(len(otherFiles), Equals, 0) // Uppercase extensions are silently ignored by the file walker, not reported as failed - c.Check(len(failedFiles), Equals, 0) + c.Check(len(failedFiles), Equals, 0) c.Check(len(reporter.warnings), Equals, 0) } func (s *ImportSuite) TestCollectPackageFilesSymlinks(c *C) { // Test handling of symbolic links reporter := &MockResultReporter{} - + // Create a real file realFile := filepath.Join(s.tempDir, "real.deb") err := ioutil.WriteFile(realFile, []byte("real content"), 0644) c.Assert(err, IsNil) - + // Create a symlink to it linkFile := filepath.Join(s.tempDir, "link.deb") err = os.Symlink(realFile, linkFile) if err != nil { c.Skip("Cannot create symlinks on this filesystem") } - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{s.tempDir}, reporter) - + // Both real file and symlink should be found c.Check(len(packageFiles), Equals, 2) c.Check(len(otherFiles), Equals, 0) @@ -433,7 +433,7 @@ func (s *ImportSuite) TestCollectPackageFilesSymlinks(c *C) { func (s *ImportSuite) TestCollectPackageFilesSpecialCharacters(c *C) { // Test files with special characters in names reporter := &MockResultReporter{} - + // Create files with various special characters specialFiles := []string{ "package with spaces.deb", @@ -443,15 +443,15 @@ func (s *ImportSuite) TestCollectPackageFilesSpecialCharacters(c *C) { "package+plus.deb", "package@at.deb", } - + for _, filename := range specialFiles { path := filepath.Join(s.tempDir, filename) err := ioutil.WriteFile(path, []byte("content"), 0644) c.Assert(err, IsNil) } - + packageFiles, otherFiles, failedFiles := CollectPackageFiles([]string{s.tempDir}, reporter) - + c.Check(len(packageFiles), Equals, len(specialFiles)) c.Check(len(otherFiles), Equals, 0) c.Check(len(failedFiles), Equals, 0) @@ -545,7 +545,6 @@ func (m *MockVerifier) ExtractClearsigned(clearsigned io.Reader) (*os.File, erro return tmpFile, err } - type MockPackageCollection struct { updateFunc func(*Package) error packages map[string]*Package @@ -601,10 +600,10 @@ func (s *ImportSuite) TestImportPackageFilesEmptyList(c *C) { checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage { return &MockChecksumStorage{} } - + processedFiles, failedFiles, err := ImportPackageFiles( list, []string{}, false, verifier, pool, collection, reporter, nil, checksumProvider) - + c.Check(err, IsNil) c.Check(len(processedFiles), Equals, 0) c.Check(len(failedFiles), Equals, 0) @@ -622,12 +621,12 @@ func (s *ImportSuite) TestImportPackageFilesNonExistentFile(c *C) { checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage { return &MockChecksumStorage{} } - + nonExistentFile := filepath.Join(s.tempDir, "nonexistent.deb") - + processedFiles, failedFiles, err := ImportPackageFiles( list, []string{nonExistentFile}, false, verifier, pool, collection, reporter, nil, checksumProvider) - + c.Check(err, IsNil) c.Check(len(processedFiles), Equals, 0) c.Check(len(failedFiles), Equals, 1) @@ -646,15 +645,15 @@ func (s *ImportSuite) TestImportPackageFilesInvalidPackageFile(c *C) { checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage { return &MockChecksumStorage{} } - + // Create invalid .deb file invalidDeb := filepath.Join(s.tempDir, "invalid.deb") err := ioutil.WriteFile(invalidDeb, []byte("not a valid deb file"), 0644) c.Assert(err, IsNil) - + processedFiles, failedFiles, err := ImportPackageFiles( list, []string{invalidDeb}, false, verifier, pool, collection, reporter, nil, checksumProvider) - + c.Check(err, IsNil) c.Check(len(processedFiles), Equals, 0) c.Check(len(failedFiles), Equals, 1) @@ -667,28 +666,28 @@ func (s *ImportSuite) TestImportPackageFilesPoolImportError(c *C) { // Test ImportPackageFiles with pool import error list := NewPackageList() reporter := &MockResultReporter{} - + // Mock pool that fails to import pool := &MockPackagePool{ importFunc: func(string, string, *utils.ChecksumInfo, bool, aptly.ChecksumStorage) (string, error) { return "", fmt.Errorf("pool import error") }, } - + collection := NewPackageCollection(nil) verifier := &MockVerifier{} checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage { return &MockChecksumStorage{} } - + // Create a simple .deb file debFile := filepath.Join(s.tempDir, "test.deb") err := ioutil.WriteFile(debFile, []byte("simple deb"), 0644) c.Assert(err, IsNil) - + processedFiles, failedFiles, err := ImportPackageFiles( list, []string{debFile}, false, verifier, pool, collection, reporter, nil, checksumProvider) - + c.Check(err, IsNil) c.Check(len(processedFiles), Equals, 0) c.Check(len(failedFiles), Equals, 1) @@ -701,23 +700,23 @@ func (s *ImportSuite) TestImportPackageFilesCollectionUpdateError(c *C) { list := NewPackageList() reporter := &MockResultReporter{} pool := &MockPackagePool{} - + // Use real collection for testing collection := NewPackageCollection(nil) - + verifier := &MockVerifier{} checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage { return &MockChecksumStorage{} } - + // Create a simple .deb file debFile := filepath.Join(s.tempDir, "test.deb") err := ioutil.WriteFile(debFile, []byte("simple deb"), 0644) c.Assert(err, IsNil) - + processedFiles, failedFiles, err := ImportPackageFiles( list, []string{debFile}, false, verifier, pool, collection, reporter, nil, checksumProvider) - + c.Check(err, IsNil) c.Check(len(processedFiles), Equals, 0) c.Check(len(failedFiles), Equals, 1) @@ -734,16 +733,16 @@ func (s *ImportSuite) TestImportPackageFilesForceReplace(c *C) { checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage { return &MockChecksumStorage{} } - + // Test that forceReplace calls PrepareIndex on the list debFile := filepath.Join(s.tempDir, "test.deb") err := ioutil.WriteFile(debFile, []byte("simple deb"), 0644) c.Assert(err, IsNil) - + // With forceReplace = true processedFiles, failedFiles, err := ImportPackageFiles( list, []string{debFile}, true, verifier, pool, collection, reporter, nil, checksumProvider) - + c.Check(err, IsNil) c.Check(len(processedFiles), Equals, 0) // No files should be processed due to invalid file // Even though the file is invalid, the function should handle forceReplace logic @@ -760,26 +759,26 @@ func (s *ImportSuite) TestImportPackageFilesErrorHandling(c *C) { checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage { return &MockChecksumStorage{} } - + // Test with multiple files, some valid some invalid validDeb := filepath.Join(s.tempDir, "valid.deb") invalidDeb := filepath.Join(s.tempDir, "invalid.deb") nonExistent := filepath.Join(s.tempDir, "nonexistent.deb") - + err := ioutil.WriteFile(validDeb, []byte("valid deb content"), 0644) c.Assert(err, IsNil) - + err = ioutil.WriteFile(invalidDeb, []byte("invalid content"), 0644) c.Assert(err, IsNil) - + files := []string{validDeb, invalidDeb, nonExistent} - + processedFiles, failedFiles, err := ImportPackageFiles( list, files, false, verifier, pool, collection, reporter, nil, checksumProvider) - + c.Check(err, IsNil) - c.Check(len(processedFiles), Equals, 0) // No files should be processed successfully - c.Check(len(failedFiles), Equals, 3) // All files should fail + c.Check(len(processedFiles), Equals, 0) // No files should be processed successfully + c.Check(len(failedFiles), Equals, 3) // All files should fail c.Check(len(reporter.warnings), Equals, 3) // Should have warnings for all failures } @@ -793,21 +792,21 @@ func (s *ImportSuite) TestImportPackageFilesRestrictionFilter(c *C) { checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage { return &MockChecksumStorage{} } - + // Create mock restriction that rejects all packages restriction := &MockPackageQuery{ matchesFunc: func(*Package) bool { return false // Reject all packages }, } - + debFile := filepath.Join(s.tempDir, "test.deb") err := ioutil.WriteFile(debFile, []byte("test deb"), 0644) c.Assert(err, IsNil) - + processedFiles, failedFiles, err := ImportPackageFiles( list, []string{debFile}, false, verifier, pool, collection, reporter, restriction, checksumProvider) - + c.Check(err, IsNil) c.Check(len(processedFiles), Equals, 0) c.Check(len(failedFiles), Equals, 1) // Should fail due to restriction + invalid file @@ -850,7 +849,7 @@ func (s *ImportSuite) TestImportPackageFilesFileTypes(c *C) { checksumProvider := func(database.ReaderWriter) aptly.ChecksumStorage { return &MockChecksumStorage{} } - + // Create files of different types files := map[string]string{ "package.deb": "deb content", @@ -858,7 +857,7 @@ func (s *ImportSuite) TestImportPackageFilesFileTypes(c *C) { "source.dsc": "dsc content", "debug.ddeb": "ddeb content", } - + var fileList []string for filename, content := range files { path := filepath.Join(s.tempDir, filename) @@ -866,12 +865,12 @@ func (s *ImportSuite) TestImportPackageFilesFileTypes(c *C) { c.Assert(err, IsNil) fileList = append(fileList, path) } - + processedFiles, failedFiles, err := ImportPackageFiles( list, fileList, false, verifier, pool, collection, reporter, nil, checksumProvider) - + c.Check(err, IsNil) // All files should fail due to invalid format, but function should handle different types c.Check(len(failedFiles), Equals, len(fileList)) c.Check(len(processedFiles), Equals, 0) -} \ No newline at end of file +} diff --git a/deb/index_files_test.go b/deb/index_files_test.go index 0fae0851..cd1c0682 100644 --- a/deb/index_files_test.go +++ b/deb/index_files_test.go @@ -680,11 +680,11 @@ type MockSigner struct { ClearSignCalled bool } -func (m *MockSigner) Init() error { return nil } -func (m *MockSigner) SetKey(keyRef string) {} -func (m *MockSigner) SetKeyRing(keyring, secretKeyring string) {} +func (m *MockSigner) Init() error { return nil } +func (m *MockSigner) SetKey(keyRef string) {} +func (m *MockSigner) SetKeyRing(keyring, secretKeyring string) {} func (m *MockSigner) SetPassphrase(passphrase, passphraseFile string) {} -func (m *MockSigner) SetBatch(batch bool) {} +func (m *MockSigner) SetBatch(batch bool) {} func (m *MockSigner) DetachedSign(source, signature string) error { m.DetachedSignCalled = true @@ -734,4 +734,4 @@ func (m *MockProgress) Shutdown() {} func (m *MockProgress) Write(p []byte) (n int, err error) { return len(p), nil -} \ No newline at end of file +} diff --git a/deb/package_deps_test.go b/deb/package_deps_test.go index a125f14c..3be3e4fd 100644 --- a/deb/package_deps_test.go +++ b/deb/package_deps_test.go @@ -13,10 +13,10 @@ func (s *PackageDependenciesSuite) TestParseDependenciesBasic(c *C) { stanza := Stanza{ "Depends": "package1", } - + result := parseDependencies(stanza, "Depends") c.Check(result, DeepEquals, []string{"package1"}) - + // Check that key was removed from stanza _, exists := stanza["Depends"] c.Check(exists, Equals, false) @@ -27,7 +27,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesMultiple(c *C) { stanza := Stanza{ "Depends": "package1, package2, package3", } - + result := parseDependencies(stanza, "Depends") c.Check(result, DeepEquals, []string{"package1", "package2", "package3"}) } @@ -37,7 +37,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesWithVersions(c *C) { stanza := Stanza{ "Depends": "package1 (>= 1.0), package2 (<< 2.0), package3 (= 1.5)", } - + result := parseDependencies(stanza, "Depends") c.Check(result, DeepEquals, []string{"package1 (>= 1.0)", "package2 (<< 2.0)", "package3 (= 1.5)"}) } @@ -47,7 +47,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesWithWhitespace(c *C) { stanza := Stanza{ "Depends": " package1 , package2 ,package3, package4 ", } - + result := parseDependencies(stanza, "Depends") c.Check(result, DeepEquals, []string{"package1", "package2", "package3", "package4"}) } @@ -57,10 +57,10 @@ func (s *PackageDependenciesSuite) TestParseDependenciesEmpty(c *C) { stanza := Stanza{ "Depends": "", } - + result := parseDependencies(stanza, "Depends") c.Check(result, IsNil) - + // Check that key was removed from stanza _, exists := stanza["Depends"] c.Check(exists, Equals, false) @@ -71,7 +71,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesWhitespaceOnly(c *C) { stanza := Stanza{ "Depends": " \t \n ", } - + result := parseDependencies(stanza, "Depends") c.Check(result, IsNil) } @@ -81,10 +81,10 @@ func (s *PackageDependenciesSuite) TestParseDependenciesMissingKey(c *C) { stanza := Stanza{ "SomeOtherField": "value", } - + result := parseDependencies(stanza, "Depends") c.Check(result, IsNil) - + // Check that original stanza is unchanged _, exists := stanza["SomeOtherField"] c.Check(exists, Equals, true) @@ -95,7 +95,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesComplexFormat(c *C) { stanza := Stanza{ "Depends": "libc6 (>= 2.17), libgcc1 (>= 1:4.1.1), libstdc++6 (>= 4.8.1)", } - + result := parseDependencies(stanza, "Depends") expected := []string{ "libc6 (>= 2.17)", @@ -110,7 +110,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesAlternatives(c *C) { stanza := Stanza{ "Depends": "mail-transport-agent | postfix, libc6 (>= 2.17)", } - + result := parseDependencies(stanza, "Depends") expected := []string{ "mail-transport-agent | postfix", @@ -124,7 +124,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesSpecialCharacters(c *C) stanza := Stanza{ "Depends": "lib-package++-dev, package.name, package_underscore", } - + result := parseDependencies(stanza, "Depends") expected := []string{ "lib-package++-dev", @@ -139,7 +139,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesArchitectures(c *C) { stanza := Stanza{ "Depends": "package1 [amd64], package2 [!arm64], package3 [i386 amd64]", } - + result := parseDependencies(stanza, "Depends") expected := []string{ "package1 [amd64]", @@ -154,7 +154,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesProfiles(c *C) { stanza := Stanza{ "Depends": "package1 , package2 , package3 ", } - + result := parseDependencies(stanza, "Depends") expected := []string{ "package1 ", @@ -168,11 +168,11 @@ func (s *PackageDependenciesSuite) TestParseDependenciesLongLine(c *C) { // Test parsing very long dependency line longDeps := "pkg1, pkg2, pkg3, pkg4, pkg5, pkg6, pkg7, pkg8, pkg9, pkg10, " + "pkg11, pkg12, pkg13, pkg14, pkg15, pkg16, pkg17, pkg18, pkg19, pkg20" - + stanza := Stanza{ "Depends": longDeps, } - + result := parseDependencies(stanza, "Depends") c.Check(len(result), Equals, 20) c.Check(result[0], Equals, "pkg1") @@ -184,7 +184,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesSingleComma(c *C) { stanza := Stanza{ "Depends": ",", } - + result := parseDependencies(stanza, "Depends") c.Check(result, DeepEquals, []string{"", ""}) } @@ -194,7 +194,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesTrailingComma(c *C) { stanza := Stanza{ "Depends": "package1, package2,", } - + result := parseDependencies(stanza, "Depends") c.Check(result, DeepEquals, []string{"package1", "package2", ""}) } @@ -204,7 +204,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesLeadingComma(c *C) { stanza := Stanza{ "Depends": ", package1, package2", } - + result := parseDependencies(stanza, "Depends") c.Check(result, DeepEquals, []string{"", "package1", "package2"}) } @@ -214,7 +214,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesMultipleCommas(c *C) { stanza := Stanza{ "Depends": "package1,, package2,,, package3", } - + result := parseDependencies(stanza, "Depends") c.Check(result, DeepEquals, []string{"package1", "", "package2", "", "", "package3"}) } @@ -224,7 +224,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesRealWorld(c *C) { stanza := Stanza{ "Depends": "debconf (>= 0.5) | debconf-2.0, libc6 (>= 2.14), libgcc1 (>= 1:3.0), libstdc++6 (>= 5.2)", } - + result := parseDependencies(stanza, "Depends") expected := []string{ "debconf (>= 0.5) | debconf-2.0", @@ -238,33 +238,33 @@ func (s *PackageDependenciesSuite) TestParseDependenciesRealWorld(c *C) { func (s *PackageDependenciesSuite) TestParseDependenciesDifferentKeys(c *C) { // Test parsing different dependency types stanza := Stanza{ - "Depends": "runtime-dep", - "Build-Depends": "build-dep", + "Depends": "runtime-dep", + "Build-Depends": "build-dep", "Build-Depends-Indep": "build-indep-dep", - "Pre-Depends": "pre-dep", - "Suggests": "suggest-dep", - "Recommends": "recommend-dep", + "Pre-Depends": "pre-dep", + "Suggests": "suggest-dep", + "Recommends": "recommend-dep", } - + // Test each dependency type depends := parseDependencies(stanza, "Depends") c.Check(depends, DeepEquals, []string{"runtime-dep"}) - + buildDepends := parseDependencies(stanza, "Build-Depends") c.Check(buildDepends, DeepEquals, []string{"build-dep"}) - + buildDependsIndep := parseDependencies(stanza, "Build-Depends-Indep") c.Check(buildDependsIndep, DeepEquals, []string{"build-indep-dep"}) - + preDepends := parseDependencies(stanza, "Pre-Depends") c.Check(preDepends, DeepEquals, []string{"pre-dep"}) - + suggests := parseDependencies(stanza, "Suggests") c.Check(suggests, DeepEquals, []string{"suggest-dep"}) - + recommends := parseDependencies(stanza, "Recommends") c.Check(recommends, DeepEquals, []string{"recommend-dep"}) - + // Verify all keys were removed c.Check(len(stanza), Equals, 0) } @@ -279,7 +279,7 @@ func (s *PackageDependenciesSuite) TestPackageDependenciesStruct(c *C) { Suggests: []string{"suggest1", "suggest2"}, Recommends: []string{"recommend1"}, } - + c.Check(deps.Depends, DeepEquals, []string{"dep1", "dep2"}) c.Check(deps.BuildDepends, DeepEquals, []string{"build-dep1", "build-dep2"}) c.Check(deps.BuildDependsInDep, DeepEquals, []string{"build-indep-dep1"}) @@ -293,7 +293,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesUnicodeCharacters(c *C) stanza := Stanza{ "Depends": "libμ-package, package-ñoño, 中文-package", } - + result := parseDependencies(stanza, "Depends") expected := []string{ "libμ-package", @@ -309,16 +309,16 @@ func (s *PackageDependenciesSuite) TestParseDependenciesStanzaImmutability(c *C) "Depends": "package1, package2", "Other": "value", } - + // Make a copy to compare stanza := Stanza{ "Depends": original["Depends"], "Other": original["Other"], } - + result := parseDependencies(stanza, "Depends") c.Check(result, DeepEquals, []string{"package1", "package2"}) - + // Check that Depends key was removed but Other remains unchanged _, dependsExists := stanza["Depends"] c.Check(dependsExists, Equals, false) @@ -328,7 +328,7 @@ func (s *PackageDependenciesSuite) TestParseDependenciesStanzaImmutability(c *C) func (s *PackageDependenciesSuite) TestParseDependenciesEmptyStanza(c *C) { // Test with completely empty stanza stanza := Stanza{} - + result := parseDependencies(stanza, "Depends") c.Check(result, IsNil) c.Check(len(stanza), Equals, 0) @@ -339,10 +339,10 @@ func (s *PackageDependenciesSuite) TestParseDependenciesTabsAndNewlines(c *C) { stanza := Stanza{ "Depends": "package1,\n\tpackage2,\t package3\n,package4", } - + result := parseDependencies(stanza, "Depends") // The function should handle tabs and newlines as whitespace c.Check(len(result), Equals, 4) c.Check(result[0], Equals, "package1") c.Check(result[3], Equals, "package4") -} \ No newline at end of file +} diff --git a/deb/publish.go b/deb/publish.go index 46c9557f..cae33506 100644 --- a/deb/publish.go +++ b/deb/publish.go @@ -1174,6 +1174,12 @@ func (p *PublishedRepo) Publish(packagePool aptly.PackagePool, publishedStorageP return err } + // Flush any pending uploads before renaming files + err = publishedStorage.Flush() + if err != nil { + return fmt.Errorf("error flushing pending uploads: %s", err) + } + return indexes.RenameFiles() } diff --git a/deb/reflist_test.go b/deb/reflist_test.go index a3f035db..65a9794a 100644 --- a/deb/reflist_test.go +++ b/deb/reflist_test.go @@ -65,7 +65,7 @@ func (s *PackageRefListSuite) TestNewPackageListFromRefList(c *C) { list, err := NewPackageListFromRefList(reflist, coll, nil) c.Assert(err, IsNil) c.Check(list.Len(), Equals, 4) - c.Check(list.Add(s.p4), ErrorMatches, "package already exists and is different: .*") + c.Check(list.Add(s.p4), ErrorMatches, "package already exists and is different: .*") list, err = NewPackageListFromRefList(nil, coll, nil) c.Assert(err, IsNil) diff --git a/deb/snapshot_bench_test.go b/deb/snapshot_bench_test.go index c7137eb1..afa835ba 100644 --- a/deb/snapshot_bench_test.go +++ b/deb/snapshot_bench_test.go @@ -31,8 +31,7 @@ func BenchmarkSnapshotCollectionForEach(b *testing.B) { for i := 0; i < b.N; i++ { collection = NewSnapshotCollection(db) - - _ = collection.ForEach(func(s *Snapshot) error { + _ = collection.ForEach(func(s *Snapshot) error { return nil }) } diff --git a/deb/version.go b/deb/version.go index a88ccd54..47c6998b 100644 --- a/deb/version.go +++ b/deb/version.go @@ -50,7 +50,7 @@ func compareLexicographic(s1, s2 string) int { i := 0 l1, l2 := len(s1), len(s2) - for !(i == l1 && i == l2) { // break if s1 equal to s2 + for !(i == l1 && i == l2) { // break if s1 equal to s2 if i == l2 { // s1 is longer than s2 diff --git a/debian/aptly.conf b/debian/aptly.conf index c233118f..924126ab 100644 --- a/debian/aptly.conf +++ b/debian/aptly.conf @@ -92,17 +92,27 @@ async_api: false # Database backend # Type must be one of: -# * leveldb (default) -# * etcd +# * leveldb (default) - local embedded database +# * etcd - distributed key-value store for multi-node setups database_backend: type: leveldb # Path to leveldb files # empty dbPath defaults to `rootDir`/db db_path: "" + # Etcd Configuration Example: + # Etcd allows multiple aptly instances to share the same database + # for high availability and load balancing scenarios + # # type: etcd - # # URL to db server + # # URL to etcd server # url: "127.0.0.1:2379" + # + # # Additional etcd configuration via environment variables: + # # APTLY_ETCD_TIMEOUT - Operation timeout (default: 60s) + # # APTLY_ETCD_DIAL_TIMEOUT - Connection timeout (default: 60s) + # # APTLY_ETCD_KEEPALIVE - Keep-alive timeout (default: 7200s) + # # APTLY_ETCD_MAX_MSG_SIZE - Maximum message size in bytes (default: 52428800) # Mirroring @@ -252,6 +262,16 @@ s3_publish_endpoints: # # Debug (optional) # # Enables detailed request/response dump for each S3 operation # debug: false + # # Concurrent Uploads (optional) + # # Number of concurrent upload workers for S3 operations. + # # * 0 (default) - uploads are performed sequentially + # # * >0 - enables concurrent uploads with specified number of workers + # concurrent_uploads: 0 + # # Upload Queue Size (optional) + # # Multiplier for upload queue size (queue_size = concurrent_uploads * upload_queue_size) + # # * 2 (default) - queue holds 2x the number of workers + # # * higher values allow more uploads to be queued before blocking + # upload_queue_size: 2 # Swift Endpoint Support # diff --git a/files/package_pool.go b/files/package_pool.go index b50811c2..0aa27fdf 100644 --- a/files/package_pool.go +++ b/files/package_pool.go @@ -241,7 +241,7 @@ func (pool *PackagePool) Import(srcPath, basename string, checksums *utils.Check return "", err } defer func() { - _ = source.Close() + _ = source.Close() }() sourceInfo, err := source.Stat() diff --git a/files/public.go b/files/public.go index f3756aeb..4609f902 100644 --- a/files/public.go +++ b/files/public.go @@ -208,7 +208,10 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath, } // forced, so remove destination - err = os.Remove(filepath.Join(poolPath, baseName)) + destPath := filepath.Join(poolPath, baseName) + unlock := utils.LockFile(destPath) + err = os.Remove(destPath) + unlock() if err != nil { return err } @@ -223,14 +226,18 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath, } var dst *os.File - dst, err = os.Create(filepath.Join(poolPath, baseName)) + destPath := filepath.Join(poolPath, baseName) + unlock := utils.LockFile(destPath) + dst, err = os.Create(destPath) if err != nil { + unlock() _ = r.Close() return err } _, err = io.Copy(dst, r) if err != nil { + unlock() _ = r.Close() _ = dst.Close() return err @@ -238,15 +245,23 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath, err = r.Close() if err != nil { + unlock() _ = dst.Close() return err } err = dst.Close() + unlock() } else if storage.linkMethod == LinkMethodSymLink { - err = localSourcePool.Symlink(sourcePath, filepath.Join(poolPath, baseName)) + destPath := filepath.Join(poolPath, baseName) + unlock := utils.LockFile(destPath) + err = localSourcePool.Symlink(sourcePath, destPath) + unlock() } else { - err = localSourcePool.Link(sourcePath, filepath.Join(poolPath, baseName)) + destPath := filepath.Join(poolPath, baseName) + unlock := utils.LockFile(destPath) + err = localSourcePool.Link(sourcePath, destPath) + unlock() } return err @@ -278,17 +293,32 @@ func (storage *PublishedStorage) Filelist(prefix string) ([]string, error) { // RenameFile renames (moves) file func (storage *PublishedStorage) RenameFile(oldName, newName string) error { - return os.Rename(filepath.Join(storage.rootPath, oldName), filepath.Join(storage.rootPath, newName)) + oldPath := filepath.Join(storage.rootPath, oldName) + newPath := filepath.Join(storage.rootPath, newName) + + // Lock both paths in consistent order to avoid deadlock + unlock := utils.LockFiles([]string{oldPath, newPath}) + defer unlock() + + return os.Rename(oldPath, newPath) } // SymLink creates a symbolic link, which can be read with ReadLink func (storage *PublishedStorage) SymLink(src string, dst string) error { - return os.Symlink(filepath.Join(storage.rootPath, src), filepath.Join(storage.rootPath, dst)) + dstPath := filepath.Join(storage.rootPath, dst) + unlock := utils.LockFile(dstPath) + defer unlock() + + return os.Symlink(filepath.Join(storage.rootPath, src), dstPath) } // HardLink creates a hardlink of a file func (storage *PublishedStorage) HardLink(src string, dst string) error { - return os.Link(filepath.Join(storage.rootPath, src), filepath.Join(storage.rootPath, dst)) + dstPath := filepath.Join(storage.rootPath, dst) + unlock := utils.LockFile(dstPath) + defer unlock() + + return os.Link(filepath.Join(storage.rootPath, src), dstPath) } // FileExists returns true if path exists @@ -309,3 +339,8 @@ func (storage *PublishedStorage) ReadLink(path string) (string, error) { } return filepath.Rel(storage.rootPath, absPath) } + +// Flush is a no-op for filesystem storage +func (storage *PublishedStorage) Flush() error { + return nil +} diff --git a/go.mod b/go.mod index 53c5e78c..e91ef7e6 100644 --- a/go.mod +++ b/go.mod @@ -3,130 +3,128 @@ module github.com/aptly-dev/aptly go 1.24 require ( - github.com/AlekSi/pointer v1.1.0 - github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55 - github.com/awalterschulze/gographviz v2.0.1+incompatible + github.com/AlekSi/pointer v1.2.0 + github.com/DisposaBoy/JsonConfigReader v0.0.0-20201129172854-99cf318d67e7 + github.com/awalterschulze/gographviz v2.0.3+incompatible github.com/cavaliergopher/grab/v3 v3.0.1 - github.com/cheggaaa/pb v1.0.25 - github.com/gin-gonic/gin v1.9.1 - github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/cheggaaa/pb v1.0.29 + github.com/gin-gonic/gin v1.10.1 + github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/h2non/filetype v1.1.3 github.com/jlaffaye/ftp v0.2.0 // indirect - github.com/kjk/lzma v0.0.0-20120628231508-2a7c55cad4a2 - github.com/klauspost/compress v1.17.9 - github.com/klauspost/pgzip v1.2.5 - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d + github.com/klauspost/compress v1.18.0 + github.com/klauspost/pgzip v1.2.6 + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-shellwords v1.0.12 github.com/mkrautz/goar v0.0.0-20150919110319-282caa8bd9da github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f github.com/ncw/swift v1.0.53 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.20.0 - github.com/rs/zerolog v1.29.1 - github.com/saracen/walker v0.1.2 + github.com/prometheus/client_golang v1.22.0 + github.com/rs/zerolog v1.34.0 + github.com/saracen/walker v0.1.4 github.com/smira/commander v0.0.0-20140515201010-f408b00e68d5 github.com/smira/flag v0.0.0-20170926215700-695ea5e84e76 github.com/smira/go-ftp-protocol v0.0.0-20140829150050-066b75c2b70d github.com/smira/go-xz v0.1.0 github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d - github.com/ugorji/go/codec v1.2.11 + github.com/ugorji/go/codec v1.3.0 github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/sys v0.31.0 - golang.org/x/term v0.30.0 - golang.org/x/time v0.5.0 - google.golang.org/protobuf v1.34.2 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/sys v0.34.0 + golang.org/x/term v0.33.0 + golang.org/x/time v0.12.0 + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) require ( - github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bytedance/sonic v1.9.1 // indirect + github.com/bytedance/sonic v1.13.3 // indirect + github.com/bytedance/sonic/loader v0.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect - github.com/cloudflare/circl v1.4.0 // indirect - github.com/coreos/go-semver v0.3.0 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect + github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/fatih/color v1.17.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/jsonreference v0.19.6 // indirect - github.com/go-openapi/spec v0.20.4 // indirect - github.com/go-openapi/swag v0.19.15 // indirect + github.com/gabriel-vasile/mimetype v1.4.9 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-openapi/jsonpointer v0.21.1 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect + github.com/go-openapi/swag v0.23.1 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.5 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/golang/snappy v0.0.4 // indirect + github.com/golang/snappy v1.0.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/klauspost/cpuid/v2 v2.2.11 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect - github.com/leodido/go-urn v1.2.4 // indirect - github.com/mailru/easyjson v0.7.6 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/pelletier/go-toml/v2 v2.1.0 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.59.1 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/prometheus/common v0.65.0 // indirect + github.com/prometheus/procfs v0.17.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - go.etcd.io/etcd/api/v3 v3.5.15 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.5.15 // indirect - go.uber.org/multierr v1.10.0 // indirect - go.uber.org/zap v1.26.0 // indirect - golang.org/x/arch v0.3.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/text v0.23.0 // indirect - golang.org/x/tools v0.30.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect - google.golang.org/grpc v1.64.1 // indirect - gopkg.in/cheggaaa/pb.v1 v1.0.28 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect + go.etcd.io/etcd/api/v3 v3.6.1 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.1 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.0 // indirect + golang.org/x/arch v0.19.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/text v0.27.0 // indirect + golang.org/x/tools v0.34.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect + google.golang.org/grpc v1.73.0 // indirect ) require ( - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 - github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 - github.com/ProtonMail/go-crypto v1.0.0 - github.com/aws/aws-sdk-go-v2 v1.32.5 - github.com/aws/aws-sdk-go-v2/config v1.28.5 - github.com/aws/aws-sdk-go-v2/credentials v1.17.46 - github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1 - github.com/aws/smithy-go v1.22.1 + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1 + github.com/ProtonMail/go-crypto v1.3.0 + github.com/aws/aws-sdk-go-v2 v1.36.5 + github.com/aws/aws-sdk-go-v2/config v1.29.17 + github.com/aws/aws-sdk-go-v2/credentials v1.17.70 + github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0 + github.com/aws/smithy-go v1.22.4 github.com/google/uuid v1.6.0 + github.com/prometheus/client_model v0.6.2 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 - github.com/swaggo/swag v1.16.3 - go.etcd.io/etcd/client/v3 v3.5.15 + github.com/swaggo/swag v1.16.4 + go.etcd.io/etcd/client/v3 v3.6.1 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index 502f4b21..ae2d5459 100644 --- a/go.sum +++ b/go.sum @@ -1,134 +1,133 @@ -github.com/AlekSi/pointer v1.1.0 h1:SSDMPcXD9jSl8FPy9cRzoRaMJtm9g9ggGTxecRUbQoI= -github.com/AlekSi/pointer v1.1.0/go.mod h1:y7BvfRI3wXPWKXEBhU71nbnIEEZX0QTSB2Bj48UJIZE= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c= -github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1 h1:cf+OIKbkmMHBaC3u78AXomweqM0oxQSgBXRZf3WH4yM= -github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.1/go.mod h1:ap1dmS6vQKJxSMNiGJcq4QuUQkOynyD93gLw6MDF7ek= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= -github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= -github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55 h1:jbGlDKdzAZ92NzK65hUP98ri0/r50vVVvmZsFP/nIqo= -github.com/DisposaBoy/JsonConfigReader v0.0.0-20171218180944-5ea4d0ddac55/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI= +github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= +github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 h1:Gt0j3wceWMwPmiazCa8MzMA0MfhmPIz0Qp0FJ6qcM0U= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0/go.mod h1:Ot/6aikWnKWi4l9QB7qVSwa8iMphQNqkWALMoNT3rzM= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0 h1:OVoM452qUFBrX+URdH3VpR299ma4kfom0yB0URYky9g= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.9.0/go.mod h1:kUjrAo8bgEwLeZ/CmHqNl3Z/kPm7y6FKfxxK0izYUg4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 h1:FPKJS1T+clwv+OLGt13a8UjqeRuh0O4SJ3lUriThc+4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1/go.mod h1:j2chePtV91HrC22tGoRX3sGY42uF13WzmmV80/OdVAA= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0 h1:LR0kAX9ykz8G4YgLCaRDVJ3+n43R8MneB5dTy2konZo= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.0/go.mod h1:DWAciXemNf++PQJLeXUB4HHH5OpsAh12HZnu2wXE1jA= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1 h1:lhZdRq7TIx0GJQvSyX2Si406vrYsov2FXGp/RnSEtcs= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.1/go.mod h1:8cl44BDmi+effbARHMQjgOKA2AYvcohNm7KEt42mSV8= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 h1:oygO0locgZJe7PpYPXT5A29ZkwJaPqcva7BVeemZOZs= +github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/DisposaBoy/JsonConfigReader v0.0.0-20201129172854-99cf318d67e7 h1:AJKJCKcb/psppPl/9CUiQQnTG+Bce0/cIweD5w5Q7aQ= +github.com/DisposaBoy/JsonConfigReader v0.0.0-20201129172854-99cf318d67e7/go.mod h1:GCzqZQHydohgVLSIqRKZeTt8IGb1Y4NaFfim3H40uUI= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78= -github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/awalterschulze/gographviz v2.0.1+incompatible h1:XIECBRq9VPEQqkQL5pw2OtjCAdrtIgFKoJU8eT98AS8= -github.com/awalterschulze/gographviz v2.0.1+incompatible/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= -github.com/aws/aws-sdk-go-v2 v1.32.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo= -github.com/aws/aws-sdk-go-v2 v1.32.5/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7 h1:lL7IfaFzngfx0ZwUGOZdsFFnQ5uLvR0hWqqhyE7Q9M8= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.7/go.mod h1:QraP0UcVlQJsmHfioCrveWOC1nbiWUl3ej08h4mXWoc= -github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0= -github.com/aws/aws-sdk-go-v2/config v1.28.5/go.mod h1:4VsPbHP8JdcdUDmbTVgNL/8w9SqOkM5jyY8ljIxLO3o= -github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgbz1E7Mm95ieIRDNUg= -github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 h1:4usbeaes3yJnCFC7kfeyhkdkPtoRYPa/hTmCqMpKpLI= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24/go.mod h1:5CI1JemjVwde8m2WG3cz23qHKPOxbpkq0HaoreEgLIY= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 h1:N1zsICrQglfzaBnrfM0Ys00860C+QFwu6u/5+LomP+o= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24/go.mod h1:dCn9HbJ8+K31i8IQ8EWmWj0EiIk0+vKiHNMxTTYveAg= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24 h1:JX70yGKLj25+lMC5Yyh8wBtvB01GDilyRuJvXJ4piD0= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.24/go.mod h1:+Ln60j9SUTD0LEwnhEB0Xhg61DHqplBrbZpLgyjoEHg= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5 h1:gvZOjQKPxFXy1ft3QnEyXmT+IqneM9QAUWlM3r0mfqw= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.4.5/go.mod h1:DLWnfvIcm9IET/mmjdxeXbBKmTCm0ZB8p1za9BVteM8= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 h1:wtpJ4zcwrSbwhECWQoI/g6WM9zqCcSpHDJIWSbMLOu4= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5/go.mod h1:qu/W9HXQbbQ4+1+JcZp0ZNPV31ym537ZJN+fiS7Ti8E= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5 h1:P1doBzv5VEg1ONxnJss1Kh5ZG/ewoIE4MQtKKc6Crgg= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.5/go.mod h1:NOP+euMW7W3Ukt28tAxPuoWao4rhhqJD3QEBk7oCg7w= -github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1 h1:LXLnDfjT/P6SPIaCE86xCOjJROPn4FNB2EdN68vMK5c= -github.com/aws/aws-sdk-go-v2/service/s3 v1.67.1/go.mod h1:ralv4XawHjEMaHOWnTFushl0WRqim/gQWesAMF6hTow= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM= -github.com/aws/aws-sdk-go-v2/service/sso v1.24.6/go.mod h1:WJSZH2ZvepM6t6jwu4w/Z45Eoi75lPN7DcydSRtJg6Y= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3yZp/S9OaoLU33S7vPS8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5/go.mod h1:ORITg+fyuMoeiQFiVGoqB3OydVTLkClw/ljbblMq6Cc= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 h1:6SZUVRQNvExYlMLbHdlKB48x0fLbc2iVROyaNEwBHbU= -github.com/aws/aws-sdk-go-v2/service/sts v1.33.1/go.mod h1:GqWyYCwLXnlUB1lOAXQyNSPqPLQJvmo8J0DWBzp9mtg= -github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro= -github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= +github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= +github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/awalterschulze/gographviz v2.0.3+incompatible h1:9sVEXJBJLwGX7EQVhLm2elIKCm7P2YHFC8v6096G09E= +github.com/awalterschulze/gographviz v2.0.3+incompatible/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= +github.com/aws/aws-sdk-go-v2 v1.36.5 h1:0OF9RiEMEdDdZEMqF9MRjevyxAQcf6gY+E7vwBILFj0= +github.com/aws/aws-sdk-go-v2 v1.36.5/go.mod h1:EYrzvCCN9CMUTa5+6lf6MM4tq3Zjp8UhSGR/cBsjai0= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11 h1:12SpdwU8Djs+YGklkinSSlcrPyj3H4VifVsKf78KbwA= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.11/go.mod h1:dd+Lkp6YmMryke+qxW/VnKyhMBDTYP41Q2Bb+6gNZgY= +github.com/aws/aws-sdk-go-v2/config v1.29.17 h1:jSuiQ5jEe4SAMH6lLRMY9OVC+TqJLP5655pBGjmnjr0= +github.com/aws/aws-sdk-go-v2/config v1.29.17/go.mod h1:9P4wwACpbeXs9Pm9w1QTh6BwWwJjwYvJ1iCt5QbCXh8= +github.com/aws/aws-sdk-go-v2/credentials v1.17.70 h1:ONnH5CM16RTXRkS8Z1qg7/s2eDOhHhaXVd72mmyv4/0= +github.com/aws/aws-sdk-go-v2/credentials v1.17.70/go.mod h1:M+lWhhmomVGgtuPOhO85u4pEa3SmssPTdcYpP/5J/xc= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32 h1:KAXP9JSHO1vKGCr5f4O6WmlVKLFFXgWYAGoJosorxzU= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.32/go.mod h1:h4Sg6FQdexC1yYG9RDnOvLbW1a/P986++/Y/a+GyEM8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36 h1:SsytQyTMHMDPspp+spo7XwXTP44aJZZAC7fBV2C5+5s= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.36/go.mod h1:Q1lnJArKRXkenyog6+Y+zr7WDpk4e6XlR6gs20bbeNo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36 h1:i2vNHQiXUvKhs3quBR6aqlgJaiaexz/aNvdCktW/kAM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.36/go.mod h1:UdyGa7Q91id/sdyHPwth+043HhmP6yP9MBHgbZM0xo8= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36 h1:GMYy2EOWfzdP3wfVAGXBNKY5vK4K8vMET4sYOYltmqs= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.36/go.mod h1:gDhdAV6wL3PmPqBhiPbnlS447GoWs8HTTOYef9/9Inw= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4 h1:CXV68E2dNqhuynZJPB80bhPQwAKqBWVer887figW6Jc= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.4/go.mod h1:/xFi9KtvBXP97ppCz1TAEvU1Uf66qvid89rbem3wCzQ= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4 h1:nAP2GYbfh8dd2zGZqFRSMlq+/F6cMPBUuCsGAMkN074= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.4/go.mod h1:LT10DsiGjLWh4GbjInf9LQejkYEhBgBCjLG5+lvk4EE= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17 h1:t0E6FzREdtCsiLIoLCWsYliNsRBgyGD/MCK571qk4MI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.17/go.mod h1:ygpklyoaypuyDvOM5ujWGrYWpAK3h7ugnmKCU/76Ys4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17 h1:qcLWgdhq45sDM9na4cvXax9dyLitn8EYBRl8Ak4XtG4= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.17/go.mod h1:M+jkjBFZ2J6DJrjMv2+vkBbuht6kxJYtJiwoVgX4p4U= +github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0 h1:5Y75q0RPQoAbieyOuGLhjV9P3txvYgXv2lg0UwJOfmE= +github.com/aws/aws-sdk-go-v2/service/s3 v1.83.0/go.mod h1:kUklwasNoCn5YpyAqC/97r6dzTA1SRKJfKq16SXeoDU= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.5 h1:AIRJ3lfb2w/1/8wOOSqYb9fUKGwQbtysJ2H1MofRUPg= +github.com/aws/aws-sdk-go-v2/service/sso v1.25.5/go.mod h1:b7SiVprpU+iGazDUqvRSLf5XmCdn+JtT1on7uNL6Ipc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3 h1:BpOxT3yhLwSJ77qIY3DoHAQjZsc4HEGfMCE4NGy3uFg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.3/go.mod h1:vq/GQR1gOFLquZMSrxUK/cpvKCNVYibNyJ1m7JrU88E= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.0 h1:NFOJ/NXEGV4Rq//71Hs1jC/NvPs1ezajK+yQmkwnPV0= +github.com/aws/aws-sdk-go-v2/service/sts v1.34.0/go.mod h1:7ph2tGpfQvwzgistp2+zga9f+bCjlQJPkPUmMgDSD7w= +github.com/aws/smithy-go v1.22.4 h1:uqXzVZNuNexwc/xrh6Tb56u89WDlJY6HS+KC0S4QSjw= +github.com/aws/smithy-go v1.22.4/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= -github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= -github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0= +github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= +github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4= github.com/cavaliergopher/grab/v3 v3.0.1/go.mod h1:1U/KNnD+Ft6JJiYoYBAimKH2XrYptb8Kl3DFGmsjpq4= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cheggaaa/pb v1.0.25 h1:tFpebHTkI7QZx1q1rWGOKhbunhZ3fMaxTvHDWn1bH/4= -github.com/cheggaaa/pb v1.0.25/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= -github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= -github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/cheggaaa/pb v1.0.29 h1:FckUN5ngEk2LpvuG0fw1GEFx6LtyY2pWI/Z2QgCnEYo= +github.com/cheggaaa/pb v1.0.29/go.mod h1:W40334L7FMC5JKWldsTWbdGjLo0RxUKK73K+TuPxX30= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= -github.com/cloudflare/circl v1.4.0 h1:BV7h5MgrktNzytKmWjpOtdYrf0lkkbF8YMlBGPhJQrY= -github.com/cloudflare/circl v1.4.0/go.mod h1:PDRU+oXvdD7KCtgKxW95M5Z8BpSCJXQORiZFnBQS5QU= -github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= -github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= +github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= -github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= -github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= -github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= -github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= +github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= +github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= +github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= +github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= -github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4= +github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= -github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= -github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= +github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -140,18 +139,21 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg= github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -169,16 +171,16 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kjk/lzma v0.0.0-20120628231508-2a7c55cad4a2 h1:TVZQgMi+I83S3rCuE65HnmDO6+wFPRi3n2LOzr+tr68= -github.com/kjk/lzma v0.0.0-20120628231508-2a7c55cad4a2/go.mod h1:phT/jsRPBAEqjAibu1BurrabCBNTYiVI+zbmyCZJY6Q= -github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= -github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d h1:RnWZeH8N8KXfbwMTex/KKMYMj0FJRCF6tQubUuQ02GM= +github.com/kjk/lzma v0.0.0-20161016003348-3fd93898850d/go.mod h1:phT/jsRPBAEqjAibu1BurrabCBNTYiVI+zbmyCZJY6Q= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= -github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU= +github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= +github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -188,21 +190,23 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= +github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mkrautz/goar v0.0.0-20150919110319-282caa8bd9da h1:Iu5QFXIMK/YrHJ0NgUnK0rqYTTyb0ldt/rqNenAj39U= @@ -218,7 +222,6 @@ github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/ncw/swift v1.0.53 h1:luHjjTNtekIEvHg5KdAFIBaH7bWfNkefwFnpDffSIks= github.com/ncw/swift v1.0.53/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -233,8 +236,8 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= -github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= -github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -242,25 +245,25 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.20.0 h1:jBzTZ7B099Rg24tny+qngoynol8LtVYlA2bqx3vEloI= -github.com/prometheus/client_golang v1.20.0/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.59.1 h1:LXb1quJHWm1P6wq/U824uxYi4Sg0oGvNeUm1z5dJoX0= -github.com/prometheus/common v0.59.1/go.mod h1:GpWM7dewqmVYcd7SmRaiWVe9SSqjf0UrwnYnpEZNuT0= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= +github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= +github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= -github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= -github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= -github.com/saracen/walker v0.1.2 h1:/o1TxP82n8thLvmL4GpJXduYaRmJ7qXp8u9dSlV0zmo= -github.com/saracen/walker v0.1.2/go.mod h1:0oKYMsKVhSJ+ful4p/XbjvXbMgLEkLITZaxozsl4CGE= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= +github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= +github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/saracen/walker v0.1.4 h1:/WCOt98GRkQ0KgL6hXJFBpoH21XY6iCD2N6LQWBFiaU= +github.com/saracen/walker v0.1.4/go.mod h1:2F+hfOidTHfXP2AmlKOqpO+yewf8fIvNUDBNJogpJbk= github.com/smira/commander v0.0.0-20140515201010-f408b00e68d5 h1:jLFwP6SDEUHmb6QSu5n2FHseWzMio1ou1FV9p7W6p7I= github.com/smira/commander v0.0.0-20140515201010-f408b00e68d5/go.mod h1:XTQy55hw5s3pxmC42m7X0/b+9naXQ1rGN9Of6BGIZmU= github.com/smira/flag v0.0.0-20170926215700-695ea5e84e76 h1:OM075OkN4x9IB1mbzkzaKaJjFxx8Mfss8Z3E1LHwawQ= @@ -274,62 +277,67 @@ github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSS github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= -github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= -github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= +github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= +github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs= github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= -github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= +github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0 h1:3UeQBvD0TFrlVjOeLOBz+CPAI8dnbqNSVwUwRrkp7vQ= github.com/wsxiaoys/terminal v0.0.0-20160513160801-0940f3fc43a0/go.mod h1:IXCdmsXIht47RaVFLEdVnh1t+pgYtTAhQGj73kz+2DM= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -go.etcd.io/etcd/api/v3 v3.5.15 h1:3KpLJir1ZEBrYuV2v+Twaa/e2MdDCEZ/70H+lzEiwsk= -go.etcd.io/etcd/api/v3 v3.5.15/go.mod h1:N9EhGzXq58WuMllgH9ZvnEr7SI9pS0k0+DHZezGp7jM= -go.etcd.io/etcd/client/pkg/v3 v3.5.15 h1:fo0HpWz/KlHGMCC+YejpiCmyWDEuIpnTDzpJLB5fWlA= -go.etcd.io/etcd/client/pkg/v3 v3.5.15/go.mod h1:mXDI4NAOwEiszrHCb0aqfAYNCrZP4e9hRca3d1YK8EU= -go.etcd.io/etcd/client/v3 v3.5.15 h1:23M0eY4Fd/inNv1ZfU3AxrbbOdW79r9V9Rl62Nm6ip4= -go.etcd.io/etcd/client/v3 v3.5.15/go.mod h1:CLSJxrYjvLtHsrPKsy7LmZEE+DK2ktfd2bN4RhBMwlU= -go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= -go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo= -go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ= -go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= -go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= -golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +go.etcd.io/etcd/api/v3 v3.6.1 h1:yJ9WlDih9HT457QPuHt/TH/XtsdN2tubyxyQHSHPsEo= +go.etcd.io/etcd/api/v3 v3.6.1/go.mod h1:lnfuqoGsXMlZdTJlact3IB56o3bWp1DIlXPIGKRArto= +go.etcd.io/etcd/client/pkg/v3 v3.6.1 h1:CxDVv8ggphmamrXM4Of8aCC8QHzDM4tGcVr9p2BSoGk= +go.etcd.io/etcd/client/pkg/v3 v3.6.1/go.mod h1:aTkCp+6ixcVTZmrJGa7/Mc5nMNs59PEgBbq+HCmWyMc= +go.etcd.io/etcd/client/v3 v3.6.1 h1:KelkcizJGsskUXlsxjVrSmINvMMga0VWwFF0tSPGEP0= +go.etcd.io/etcd/client/v3 v3.6.1/go.mod h1:fCbPUdjWNLfx1A6ATo9syUmFVxqHH9bCnPLBZmnLmMY= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= +go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= +go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= +go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= +go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= +go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= +go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= +go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= +go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/arch v0.19.0 h1:LmbDQUodHThXE+htjrnmVD73M//D9GTH6wFZjyDkjyU= +golang.org/x/arch v0.19.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= -golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -337,92 +345,78 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg= +golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= +golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= +golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= -golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4= -google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= -google.golang.org/grpc v1.64.1 h1:LKtvyfbX3UGVPFcGqJ9ItpVWW6oN/2XqTxfAnwRRXiA= -google.golang.org/grpc v1.64.1/go.mod h1:hiQF4LFZelK2WKaP6W0L92zGHtiQdZxk8CrSdvyjeP0= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= +google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -431,15 +425,11 @@ google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzi google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= -gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -449,7 +439,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/http/download.go b/http/download.go index 3dc9c378..887f9b3b 100644 --- a/http/download.go +++ b/http/download.go @@ -240,7 +240,7 @@ func (downloader *downloaderImpl) download(req *http.Request, url, destination s } if resp.Body != nil { defer func() { - _ = resp.Body.Close() + _ = resp.Body.Close() }() } diff --git a/http/download_go16.go b/http/download_go16.go index f4df5156..bde6560c 100644 --- a/http/download_go16.go +++ b/http/download_go16.go @@ -1,3 +1,4 @@ +//go:build !go1.7 // +build !go1.7 package http diff --git a/http/grab.go b/http/grab.go index fd6420a1..8478afce 100644 --- a/http/grab.go +++ b/http/grab.go @@ -49,9 +49,9 @@ func (d *GrabDownloader) Download(ctx context.Context, url string, destination s func (d *GrabDownloader) DownloadWithChecksum(ctx context.Context, url string, destination string, expected *utils.ChecksumInfo, ignoreMismatch bool) error { maxTries := d.maxTries - // FIXME: const delayMax = time.Duration(5 * time.Minute) + // FIXME: const delayMax = time.Duration(5 * time.Minute) delay := time.Duration(1 * time.Second) - // FIXME: const delayMultiplier = 2 + // FIXME: const delayMultiplier = 2 err := fmt.Errorf("no tries available") for maxTries > 0 { err = d.download(ctx, url, destination, expected, ignoreMismatch) @@ -133,17 +133,17 @@ func (d *GrabDownloader) download(_ context.Context, url string, destination str resp := d.client.Do(req) - <-resp.Done + <-resp.Done // download is complete -// Loop: -// for { -// select { -// case <-resp.Done: -// // download is complete -// break Loop -// } -// } + // Loop: + // for { + // select { + // case <-resp.Done: + // // download is complete + // break Loop + // } + // } err = resp.Err() if err != nil && err == grab.ErrBadChecksum && ignoreMismatch { fmt.Printf("Ignoring checksum mismatch for %s\n", url) diff --git a/man/aptly.1 b/man/aptly.1 index bd6ad223..124c81db 100644 --- a/man/aptly.1 +++ b/man/aptly.1 @@ -303,7 +303,19 @@ The legacy json configuration is still supported (and also supports comments): // // Debug (optional) // // Enables detailed request/response dump for each S3 operation - // "debug": false + // "debug": false, + // + // // ConcurrentUploads (optional) + // // Number of concurrent upload workers for S3 operations + // // * 0 (default) \- uploads are performed sequentially + // // * >0 \- enables concurrent uploads with specified number of workers + // "concurrentUploads": 0, + // + // // UploadQueueSize (optional) + // // Multiplier for upload queue size (queue_size = concurrentUploads * uploadQueueSize) + // // * 2 (default) \- queue holds 2x the number of workers + // // * higher values allow more uploads to be queued before blocking + // "uploadQueueSize": 2 // } }, diff --git a/pgp/gnupg_finder_test.go b/pgp/gnupg_finder_test.go index a6a956bc..098cb781 100644 --- a/pgp/gnupg_finder_test.go +++ b/pgp/gnupg_finder_test.go @@ -23,7 +23,7 @@ func (s *GPGFinderSuite) TestGPG1Finder(c *C) { // Test GPG1 finder configuration finder := GPG1Finder() c.Check(finder, NotNil) - + pathFinder, ok := finder.(*pathGPGFinder) c.Check(ok, Equals, true) c.Check(pathFinder.gpgNames, DeepEquals, []string{"gpg", "gpg1"}) @@ -36,7 +36,7 @@ func (s *GPGFinderSuite) TestGPG2Finder(c *C) { // Test GPG2 finder configuration finder := GPG2Finder() c.Check(finder, NotNil) - + pathFinder, ok := finder.(*pathGPGFinder) c.Check(ok, Equals, true) c.Check(pathFinder.gpgNames, DeepEquals, []string{"gpg", "gpg2"}) @@ -49,7 +49,7 @@ func (s *GPGFinderSuite) TestGPGDefaultFinder(c *C) { // Test default finder configuration finder := GPGDefaultFinder() c.Check(finder, NotNil) - + iterFinder, ok := finder.(*iteratingGPGFinder) c.Check(ok, Equals, true) c.Check(len(iterFinder.finders), Equals, 2) @@ -64,7 +64,7 @@ func (s *GPGFinderSuite) TestPathGPGFinderFindGPGNotFound(c *C) { expectedVersionSubstring: `\(GnuPG.*\) (1).(\d)`, errorMessage: "test error", } - + gpg, version, err := finder.FindGPG() c.Check(gpg, Equals, "") c.Check(version, Equals, GPGVersion(0)) @@ -80,7 +80,7 @@ func (s *GPGFinderSuite) TestPathGPGFinderFindGPGVNotFound(c *C) { expectedVersionSubstring: `\(GnuPG.*\) (1).(\d)`, errorMessage: "test error", } - + gpgv, version, err := finder.FindGPGV() c.Check(gpgv, Equals, "") c.Check(version, Equals, GPGVersion(0)) @@ -96,18 +96,18 @@ func (s *GPGFinderSuite) TestIteratingGPGFinderAllFail(c *C) { expectedVersionSubstring: `\(GnuPG.*\) (1).(\d)`, errorMessage: "individual finder error", } - + finder := &iteratingGPGFinder{ finders: []GPGFinder{failingFinder, failingFinder}, errorMessage: "all finders failed", } - + gpg, version, err := finder.FindGPG() c.Check(gpg, Equals, "") c.Check(version, Equals, GPGVersion(0)) c.Check(err, NotNil) c.Check(err.Error(), Equals, "all finders failed") - + gpgv, version, err := finder.FindGPGV() c.Check(gpgv, Equals, "") c.Check(version, Equals, GPGVersion(0)) @@ -134,7 +134,7 @@ func (s *GPGFinderSuite) TestCliVersionCheckGPG1Pattern(c *C) { // Test version pattern recognition for GPG 1.x // Since we can't easily mock exec.Command, we test the pattern matching logic pattern := `\(GnuPG.*\) (1).(\d)` - + // Test that the pattern would match GPG 1.x format c.Check(strings.Contains(pattern, "(1)"), Equals, true) } @@ -142,7 +142,7 @@ func (s *GPGFinderSuite) TestCliVersionCheckGPG1Pattern(c *C) { func (s *GPGFinderSuite) TestCliVersionCheckGPG2Pattern(c *C) { // Test version pattern recognition for GPG 2.x pattern := `\(GnuPG.*\) (2).(\d)` - + // Test that the pattern would match GPG 2.x format c.Check(strings.Contains(pattern, "(2)"), Equals, true) } @@ -150,20 +150,20 @@ func (s *GPGFinderSuite) TestCliVersionCheckGPG2Pattern(c *C) { func (s *GPGFinderSuite) TestGPGFinderInterface(c *C) { // Test that all finders implement the GPGFinder interface var finder GPGFinder - + finder = GPG1Finder() c.Check(finder, NotNil) - + finder = GPG2Finder() c.Check(finder, NotNil) - + finder = GPGDefaultFinder() c.Check(finder, NotNil) - + // Test interface methods exist and return (may succeed or fail depending on system) gpg, gpgv, err1 := finder.FindGPG() _, _, err2 := finder.FindGPGV() - + // Methods should exist and return something if err1 == nil { // If GPG is found, paths should be non-empty @@ -182,12 +182,12 @@ func (s *GPGFinderSuite) TestPathGPGFinderMultipleNames(c *C) { expectedVersionSubstring: `\(GnuPG.*\) (1).(\d)`, errorMessage: "none found", } - + // Should try all names and still fail gpg, version, err := finder.FindGPG() c.Check(gpg, Equals, "") c.Check(err, NotNil) - + gpgv, version, err := finder.FindGPGV() c.Check(gpgv, Equals, "") c.Check(version, Equals, GPGVersion(0)) @@ -201,23 +201,23 @@ func (s *GPGFinderSuite) TestIteratingGPGFinderFirstSuccess(c *C) { gpgvResult: "test-gpgv", version: GPG1x, } - + failingFinder := &pathGPGFinder{ gpgNames: []string{"nonexistent"}, gpgvNames: []string{"nonexistent"}, errorMessage: "should not reach this", } - + finder := &iteratingGPGFinder{ finders: []GPGFinder{successFinder, failingFinder}, errorMessage: "should not see this error", } - + gpg, version, err := finder.FindGPG() c.Check(err, IsNil) c.Check(gpg, Equals, "test-gpg") c.Check(version, Equals, GPG1x) - + gpgv, version, err := finder.FindGPGV() c.Check(err, IsNil) c.Check(gpgv, Equals, "test-gpgv") @@ -229,11 +229,11 @@ func (s *GPGFinderSuite) TestGPGFinderErrorMessages(c *C) { gpg1Finder := GPG1Finder().(*pathGPGFinder) c.Check(strings.Contains(gpg1Finder.errorMessage, "gnupg1"), Equals, true) c.Check(strings.Contains(gpg1Finder.errorMessage, "gpg(v)1"), Equals, true) - + gpg2Finder := GPG2Finder().(*pathGPGFinder) c.Check(strings.Contains(gpg2Finder.errorMessage, "gnupg2"), Equals, true) c.Check(strings.Contains(gpg2Finder.errorMessage, "gpg(v)2"), Equals, true) - + defaultFinder := GPGDefaultFinder().(*iteratingGPGFinder) c.Check(strings.Contains(defaultFinder.errorMessage, "gnupg"), Equals, true) c.Check(strings.Contains(defaultFinder.errorMessage, "suitable"), Equals, true) @@ -242,16 +242,16 @@ func (s *GPGFinderSuite) TestGPGFinderErrorMessages(c *C) { func (s *GPGFinderSuite) TestRealGPGCommandExistence(c *C) { // Test if any real GPG commands exist in the system // This test documents the real-world behavior without failing if GPG is not installed - + commands := []string{"gpg", "gpg1", "gpg2", "gpgv", "gpgv1", "gpgv2"} foundCommands := []string{} - + for _, cmd := range commands { if _, err := exec.LookPath(cmd); err == nil { foundCommands = append(foundCommands, cmd) } } - + // This test just documents what's available, doesn't require any specific GPG c.Check(len(foundCommands) >= 0, Equals, true) // Always true, just documenting } @@ -275,15 +275,15 @@ func (s *GPGFinderSuite) TestMockGPGFinder(c *C) { // Test the mock finder implementation mock := &mockSuccessfulGPGFinder{ gpgResult: "mock-gpg", - gpgvResult: "mock-gpgv", + gpgvResult: "mock-gpgv", version: GPG21x, } - + gpg, version, err := mock.FindGPG() c.Check(err, IsNil) c.Check(gpg, Equals, "mock-gpg") c.Check(version, Equals, GPG21x) - + gpgv, version, err := mock.FindGPGV() c.Check(err, IsNil) c.Check(gpgv, Equals, "mock-gpgv") @@ -293,15 +293,15 @@ func (s *GPGFinderSuite) TestMockGPGFinder(c *C) { func (s *GPGFinderSuite) TestCliVersionCheckComplexVersions(c *C) { // Test version parsing with different GPG version outputs // Note: This test focuses on the regex parsing logic - + // Test patterns that would match different GPG versions pattern1x := `\(GnuPG.*\) (1).(\d)` pattern2x := `\(GnuPG.*\) (2).(\d)` - + // Verify patterns are correctly formed c.Check(strings.Contains(pattern1x, "(1)"), Equals, true) c.Check(strings.Contains(pattern2x, "(2)"), Equals, true) - + // Test with non-existent command to verify error handling result, version := cliVersionCheck("definitely-nonexistent-command-12345", pattern1x) c.Check(result, Equals, false) @@ -314,7 +314,7 @@ func (s *GPGFinderSuite) TestGPGVersionEnumValues(c *C) { c.Check(int(GPG20x), Equals, 2) c.Check(int(GPG21x), Equals, 3) c.Check(int(GPG22xPlus), Equals, 4) - + // Test version comparisons c.Check(GPG1x < GPG20x, Equals, true) c.Check(GPG20x < GPG21x, Equals, true) @@ -329,24 +329,24 @@ func (s *GPGFinderSuite) TestIteratingGPGFinderPartialSuccess(c *C) { expectedVersionSubstring: `\(GnuPG.*\) (1).(\d)`, errorMessage: "first finder failed", } - + successFinder := &mockSuccessfulGPGFinder{ gpgResult: "second-gpg", gpgvResult: "second-gpgv", version: GPG20x, } - + finder := &iteratingGPGFinder{ finders: []GPGFinder{failingFinder, successFinder}, errorMessage: "all failed", } - + // Should succeed with second finder gpg, version, err := finder.FindGPG() c.Check(err, IsNil) c.Check(gpg, Equals, "second-gpg") c.Check(version, Equals, GPG20x) - + gpgv, version, err := finder.FindGPGV() c.Check(err, IsNil) c.Check(gpgv, Equals, "second-gpgv") @@ -361,13 +361,13 @@ func (s *GPGFinderSuite) TestPathGPGFinderEmptyArrays(c *C) { expectedVersionSubstring: `\(GnuPG.*\) (1).(\d)`, errorMessage: "no names to try", } - + gpg, version, err := finder.FindGPG() c.Check(gpg, Equals, "") c.Check(version, Equals, GPGVersion(0)) c.Check(err, NotNil) c.Check(err.Error(), Equals, "no names to try") - + gpgv, version, err := finder.FindGPGV() c.Check(gpgv, Equals, "") c.Check(version, Equals, GPGVersion(0)) @@ -380,15 +380,15 @@ func (s *GPGFinderSuite) TestIteratingGPGFinderEmptyFinders(c *C) { finders: []GPGFinder{}, errorMessage: "no finders available", } - + gpg, version, err := finder.FindGPG() c.Check(gpg, Equals, "") c.Check(version, Equals, GPGVersion(0)) c.Check(err, NotNil) c.Check(err.Error(), Equals, "no finders available") - + gpgv, version, err := finder.FindGPGV() c.Check(gpgv, Equals, "") c.Check(version, Equals, GPGVersion(0)) c.Check(err, NotNil) -} \ No newline at end of file +} diff --git a/pgp/openpgp_test.go b/pgp/openpgp_test.go index 2d6a8c1c..2681beab 100644 --- a/pgp/openpgp_test.go +++ b/pgp/openpgp_test.go @@ -25,12 +25,12 @@ var _ = Suite(&OpenPGPSuite{}) func (s *OpenPGPSuite) TestHashForSignatureBinary(c *C) { // Test hash creation for binary signature hashFunc := crypto.SHA256 - + h1, h2, err := hashForSignature(hashFunc, packet.SigTypeBinary) c.Check(err, IsNil) c.Check(h1, NotNil) c.Check(h2, NotNil) - + // For binary signatures, both hashes should be the same instance c.Check(h1, Equals, h2) } @@ -38,12 +38,12 @@ func (s *OpenPGPSuite) TestHashForSignatureBinary(c *C) { func (s *OpenPGPSuite) TestHashForSignatureText(c *C) { // Test hash creation for text signature hashFunc := crypto.SHA256 - + h1, h2, err := hashForSignature(hashFunc, packet.SigTypeText) c.Check(err, IsNil) c.Check(h1, NotNil) c.Check(h2, NotNil) - + // For text signatures, h2 should be a canonical text hash wrapper c.Check(h1, Not(Equals), h2) } @@ -51,7 +51,7 @@ func (s *OpenPGPSuite) TestHashForSignatureText(c *C) { func (s *OpenPGPSuite) TestHashForSignatureUnsupportedHash(c *C) { // Test with unsupported hash algorithm hashFunc := crypto.Hash(999) // Invalid hash - + h1, h2, err := hashForSignature(hashFunc, packet.SigTypeBinary) c.Check(err, NotNil) c.Check(h1, IsNil) @@ -62,7 +62,7 @@ func (s *OpenPGPSuite) TestHashForSignatureUnsupportedHash(c *C) { func (s *OpenPGPSuite) TestHashForSignatureUnsupportedSigType(c *C) { // Test with unsupported signature type hashFunc := crypto.SHA256 - + h1, h2, err := hashForSignature(hashFunc, packet.SignatureType(255)) c.Check(err, NotNil) c.Check(h1, IsNil) @@ -74,14 +74,14 @@ func (s *OpenPGPSuite) TestSignatureResultStruct(c *C) { // Test signatureResult struct creation and field access now := time.Now() keyID := uint64(0x1234567890ABCDEF) - + result := signatureResult{ CreationTime: now, IssuerKeyID: keyID, PubKeyAlgo: packet.PubKeyAlgoRSA, Entity: nil, // Can be nil for missing keys } - + c.Check(result.CreationTime, Equals, now) c.Check(result.IssuerKeyID, Equals, keyID) c.Check(result.PubKeyAlgo, Equals, packet.PubKeyAlgoRSA) @@ -93,7 +93,7 @@ func (s *OpenPGPSuite) TestCheckDetachedSignatureNoSignature(c *C) { keyring := openpgp.EntityList{} signed := strings.NewReader("test data") signature := strings.NewReader("") - + signers, missingKeys, err := checkDetachedSignature(keyring, signed, signature) c.Check(err, Equals, errors.ErrUnknownIssuer) c.Check(len(signers), Equals, 0) @@ -105,7 +105,7 @@ func (s *OpenPGPSuite) TestCheckDetachedSignatureInvalidPacket(c *C) { keyring := openpgp.EntityList{} signed := strings.NewReader("test data") signature := strings.NewReader("invalid packet data") - + signers, missingKeys, err := checkDetachedSignature(keyring, signed, signature) c.Check(err, NotNil) c.Check(len(signers), Equals, 0) @@ -118,7 +118,7 @@ func (s *OpenPGPSuite) TestReadArmoredValidBlock(c *C) { iQEcBAABAgAGBQJeRllaAAoJEDvKaJaAL9sRiUUH/test -----END PGP SIGNATURE-----` - + reader := strings.NewReader(armoredData) body, err := readArmored(reader, "PGP SIGNATURE") c.Check(err, IsNil) @@ -131,7 +131,7 @@ func (s *OpenPGPSuite) TestReadArmoredWrongType(c *C) { test -----END PGP MESSAGE-----` - + reader := strings.NewReader(armoredData) body, err := readArmored(reader, "PGP SIGNATURE") c.Check(err, NotNil) @@ -152,7 +152,7 @@ func (s *OpenPGPSuite) TestCheckArmoredDetachedSignatureInvalidArmor(c *C) { keyring := openpgp.EntityList{} signed := strings.NewReader("test data") signature := strings.NewReader("not armored") - + signers, missingKeys, err := checkArmoredDetachedSignature(keyring, signed, signature) c.Check(err, NotNil) c.Check(len(signers), Equals, 0) @@ -184,7 +184,7 @@ func (s *OpenPGPSuite) TestKeyBitsRSA(c *C) { // Test RSA key bit calculation rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) c.Check(err, IsNil) - + bits := keyBits(&rsaKey.PublicKey) c.Check(bits, Equals, "2048") } @@ -196,7 +196,7 @@ func (s *OpenPGPSuite) TestKeyBitsDSA(c *C) { P: big.NewInt(0).SetBit(big.NewInt(0), 1024, 1), // 2^1024 }, } - + bits := keyBits(dsaKey) c.Check(bits, Equals, "1025") // SetBit creates a number with bit 1024 set } @@ -205,7 +205,7 @@ func (s *OpenPGPSuite) TestKeyBitsECDSA(c *C) { // Test ECDSA key bit calculation ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) c.Check(err, IsNil) - + bits := keyBits(&ecdsaKey.PublicKey) c.Check(bits, Equals, "256") // P256 curve } @@ -221,7 +221,7 @@ func (s *OpenPGPSuite) TestValidEntityNoIdentities(c *C) { entity := &openpgp.Entity{ Identities: make(map[string]*openpgp.Identity), } - + valid := validEntity(entity) c.Check(valid, Equals, false) } @@ -240,7 +240,7 @@ func (s *OpenPGPSuite) TestValidEntityWithRevocations(c *C) { {}, // Has revocation }, } - + valid := validEntity(entity) c.Check(valid, Equals, false) } @@ -256,7 +256,7 @@ func (s *OpenPGPSuite) TestValidEntityWithRevocationReason(c *C) { }, }, } - + valid := validEntity(entity) c.Check(valid, Equals, false) } @@ -272,7 +272,7 @@ func (s *OpenPGPSuite) TestValidEntityInvalidFlags(c *C) { }, }, } - + valid := validEntity(entity) c.Check(valid, Equals, false) } @@ -284,14 +284,14 @@ func (s *OpenPGPSuite) TestValidEntityExpired(c *C) { Identities: map[string]*openpgp.Identity{ "test": { SelfSignature: &packet.Signature{ - FlagsValid: true, - CreationTime: time.Now().Add(-time.Hour), // Created 1 hour ago - KeyLifetimeSecs: &keyLifetime, + FlagsValid: true, + CreationTime: time.Now().Add(-time.Hour), // Created 1 hour ago + KeyLifetimeSecs: &keyLifetime, }, }, }, } - + valid := validEntity(entity) c.Check(valid, Equals, false) } @@ -300,7 +300,7 @@ func (s *OpenPGPSuite) TestValidEntityMultipleIdentitiesPrimary(c *C) { // Test entity with multiple identities, one marked as primary isPrimary := true isNotPrimary := false - + entity := &openpgp.Entity{ Identities: map[string]*openpgp.Identity{ "secondary": { @@ -319,7 +319,7 @@ func (s *OpenPGPSuite) TestValidEntityMultipleIdentitiesPrimary(c *C) { }, }, } - + valid := validEntity(entity) c.Check(valid, Equals, true) } @@ -336,7 +336,7 @@ func (s *OpenPGPSuite) TestValidEntityValidCase(c *C) { }, }, } - + valid := validEntity(entity) c.Check(valid, Equals, true) } @@ -346,7 +346,7 @@ func (s *OpenPGPSuite) TestCheckDetachedSignatureEmptyReader(c *C) { keyring := openpgp.EntityList{} signed := strings.NewReader("") signature := strings.NewReader("") - + signers, missingKeys, err := checkDetachedSignature(keyring, signed, signature) c.Check(err, Equals, errors.ErrUnknownIssuer) c.Check(len(signers), Equals, 0) @@ -358,7 +358,7 @@ func (s *OpenPGPSuite) TestCheckDetachedSignatureErrorInCopy(c *C) { keyring := openpgp.EntityList{} signed := &errorReader{} // Custom reader that returns error signature := strings.NewReader("") - + signers, missingKeys, err := checkDetachedSignature(keyring, signed, signature) c.Check(err, NotNil) c.Check(len(signers), Equals, 0) @@ -382,7 +382,7 @@ func (s *OpenPGPSuite) TestHashForSignatureAllSupportedHashes(c *C) { crypto.SHA384, crypto.SHA512, } - + for _, hashFunc := range supportedHashes { if hashFunc.Available() { h1, h2, err := hashForSignature(hashFunc, packet.SigTypeBinary) @@ -396,7 +396,7 @@ func (s *OpenPGPSuite) TestHashForSignatureAllSupportedHashes(c *C) { func (s *OpenPGPSuite) TestSignatureResultZeroValues(c *C) { // Test signatureResult with zero values result := signatureResult{} - + c.Check(result.CreationTime.IsZero(), Equals, true) c.Check(result.IssuerKeyID, Equals, uint64(0)) c.Check(result.PubKeyAlgo, Equals, packet.PublicKeyAlgorithm(0)) @@ -413,9 +413,9 @@ func (e *errorReader) Read(p []byte) (n int, err error) { func (s *OpenPGPSuite) TestArmorDecodeCornerCases(c *C) { // Test various armor decode edge cases testCases := []struct { - name string - input string - expected string + name string + input string + expected string shouldErr bool }{ { @@ -423,7 +423,7 @@ func (s *OpenPGPSuite) TestArmorDecodeCornerCases(c *C) { input: `-----BEGIN PGP SIGNATURE----- -----END PGP SIGNATURE-----`, - expected: "PGP SIGNATURE", + expected: "PGP SIGNATURE", shouldErr: false, }, { @@ -433,7 +433,7 @@ Version: GnuPG v1 test -----END PGP SIGNATURE-----`, - expected: "PGP SIGNATURE", + expected: "PGP SIGNATURE", shouldErr: false, }, { @@ -441,7 +441,7 @@ test input: `----BEGIN PGP SIGNATURE----- test -----END PGP SIGNATURE-----`, - expected: "", + expected: "", shouldErr: true, }, { @@ -449,15 +449,15 @@ test input: `-----BEGIN PGP SIGNATURE----- test ----END PGP SIGNATURE-----`, - expected: "", + expected: "", shouldErr: true, }, } - + for _, tc := range testCases { reader := strings.NewReader(tc.input) body, err := readArmored(reader, tc.expected) - + if tc.shouldErr { c.Check(err, NotNil, Commentf("Test case: %s", tc.name)) c.Check(body, IsNil, Commentf("Test case: %s", tc.name)) @@ -496,7 +496,7 @@ func (s *OpenPGPSuite) TestKeyBitsEdgeCases(c *C) { expected: "?", }, } - + for _, tc := range testCases { result := keyBits(tc.key) c.Check(result, Equals, tc.expected, Commentf("Test case: %s", tc.name)) @@ -505,7 +505,7 @@ func (s *OpenPGPSuite) TestKeyBitsEdgeCases(c *C) { func (s *OpenPGPSuite) TestValidEntityEdgeCases(c *C) { // Test validEntity with various edge cases - + // Entity with nil self-signature entity1 := &openpgp.Entity{ Identities: map[string]*openpgp.Identity{ @@ -515,7 +515,7 @@ func (s *OpenPGPSuite) TestValidEntityEdgeCases(c *C) { }, } c.Check(validEntity(entity1), Equals, false) - + // Entity with key that never expires (nil KeyLifetimeSecs) entity2 := &openpgp.Entity{ Identities: map[string]*openpgp.Identity{ @@ -529,7 +529,7 @@ func (s *OpenPGPSuite) TestValidEntityEdgeCases(c *C) { }, } c.Check(validEntity(entity2), Equals, true) - + // Entity with key that expires in the future futureLifetime := uint32(3600) // 1 hour from creation entity3 := &openpgp.Entity{ @@ -544,4 +544,4 @@ func (s *OpenPGPSuite) TestValidEntityEdgeCases(c *C) { }, } c.Check(validEntity(entity3), Equals, true) -} \ No newline at end of file +} diff --git a/s3/public.go b/s3/public.go index fe7a1831..985ed5ca 100644 --- a/s3/public.go +++ b/s3/public.go @@ -1,6 +1,7 @@ package s3 import ( + "bytes" "context" "fmt" "io" @@ -56,6 +57,21 @@ type PublishedStorage struct { // True if the bucket encrypts objects by default. encryptByDefault bool + + // Concurrent upload configuration + concurrentUploads int + uploadQueue chan *uploadTask + uploadErrors chan error + uploadWg sync.WaitGroup +} + +// uploadTask represents a file upload job +type uploadTask struct { + path string + sourceFilename string + sourceReader io.ReadSeeker + sourceMD5 string + isFile bool // true for PutFile, false for putFile with reader } // Check interface @@ -67,7 +83,7 @@ var ( func NewPublishedStorageRaw( bucket, defaultACL, prefix, storageClass, encryptionMethod string, plusWorkaround, disabledMultiDel, forceVirtualHostedStyle bool, - config *aws.Config, endpoint string, + config *aws.Config, endpoint string, concurrentUploads int, uploadQueueSize int, ) (*PublishedStorage, error) { var acl types.ObjectCannedACL if defaultACL == "" || defaultACL == "private" { @@ -93,14 +109,32 @@ func NewPublishedStorageRaw( o.HTTPSignerV4 = signer.NewSigner() o.BaseEndpoint = baseEndpoint }), - bucket: bucket, - config: config, - acl: acl, - prefix: prefix, - storageClass: types.StorageClass(storageClass), - encryptionMethod: types.ServerSideEncryption(encryptionMethod), - plusWorkaround: plusWorkaround, - disableMultiDel: disabledMultiDel, + bucket: bucket, + config: config, + acl: acl, + prefix: prefix, + storageClass: types.StorageClass(storageClass), + encryptionMethod: types.ServerSideEncryption(encryptionMethod), + plusWorkaround: plusWorkaround, + disableMultiDel: disabledMultiDel, + concurrentUploads: concurrentUploads, + } + + // Initialize concurrent upload infrastructure if enabled + if concurrentUploads > 0 { + // Default queue size is 2x the number of workers if not specified + if uploadQueueSize <= 0 { + uploadQueueSize = 2 + } + queueSize := concurrentUploads * uploadQueueSize + + result.uploadQueue = make(chan *uploadTask, queueSize) + result.uploadErrors = make(chan error, 1) + + // Start upload workers + for i := 0; i < concurrentUploads; i++ { + go result.uploadWorker() + } } result.setKMSFlag() @@ -123,11 +157,48 @@ func (storage *PublishedStorage) setKMSFlag() { } } +// uploadWorker processes upload tasks from the queue +func (storage *PublishedStorage) uploadWorker() { + for task := range storage.uploadQueue { + var err error + + if task.isFile { + // Handle file upload + source, openErr := os.Open(task.sourceFilename) + if openErr != nil { + err = errors.Wrap(openErr, fmt.Sprintf("error opening %s", task.sourceFilename)) + } else { + err = storage.putFile(task.path, source, task.sourceMD5) + _ = source.Close() + if err != nil { + err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s", task.sourceFilename, storage)) + } + } + } else { + // Handle reader upload (for LinkFromPool) + err = storage.putFile(task.path, task.sourceReader, task.sourceMD5) + if err != nil { + err = errors.Wrap(err, fmt.Sprintf("error uploading to %s", storage)) + } + } + + if err != nil { + // Send error to error channel (non-blocking) + select { + case storage.uploadErrors <- err: + default: + } + } + + storage.uploadWg.Done() + } +} + // NewPublishedStorage creates new instance of PublishedStorage with specified S3 access // keys, region and bucket name func NewPublishedStorage( accessKey, secretKey, sessionToken, region, endpoint, bucket, defaultACL, prefix, storageClass, encryptionMethod string, - plusWorkaround, disableMultiDel, _, forceVirtualHostedStyle, debug bool) (*PublishedStorage, error) { + plusWorkaround, disableMultiDel, _, forceVirtualHostedStyle, debug bool, concurrentUploads int, uploadQueueSize int) (*PublishedStorage, error) { opts := []func(*config.LoadOptions) error{config.WithRegion(region)} if accessKey != "" { @@ -144,7 +215,7 @@ func NewPublishedStorage( } result, err := NewPublishedStorageRaw(bucket, defaultACL, prefix, storageClass, - encryptionMethod, plusWorkaround, disableMultiDel, forceVirtualHostedStyle, &config, endpoint) + encryptionMethod, plusWorkaround, disableMultiDel, forceVirtualHostedStyle, &config, endpoint, concurrentUploads, uploadQueueSize) return result, err } @@ -162,23 +233,53 @@ func (storage *PublishedStorage) MkDir(_ string) error { // PutFile puts file into published storage at specified path func (storage *PublishedStorage) PutFile(path string, sourceFilename string) error { - var ( - source *os.File - err error - ) - source, err = os.Open(sourceFilename) - if err != nil { + // If concurrent uploads are disabled, use the original implementation + if storage.concurrentUploads == 0 { + var ( + source *os.File + err error + ) + source, err = os.Open(sourceFilename) + if err != nil { + return err + } + defer func() { _ = source.Close() }() + + log.Debug().Msgf("S3: PutFile '%s'", path) + err = storage.putFile(path, source, "") + if err != nil { + err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s", sourceFilename, storage)) + } + return err } - defer func() { _ = source.Close() }() - log.Debug().Msgf("S3: PutFile '%s'", path) - err = storage.putFile(path, source, "") - if err != nil { - err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s", sourceFilename, storage)) + // Concurrent upload path + log.Debug().Msgf("S3: PutFile '%s' (concurrent)", path) + + // Check for any previous errors + select { + case err := <-storage.uploadErrors: + return err + default: } - return err + // Queue the upload task + task := &uploadTask{ + path: path, + sourceFilename: sourceFilename, + isFile: true, + } + + storage.uploadWg.Add(1) + select { + case storage.uploadQueue <- task: + // Task queued successfully + return nil + case err := <-storage.uploadErrors: + storage.uploadWg.Done() + return err + } } // getMD5 retrieves MD5 stored in the metadata, if any @@ -393,9 +494,9 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath, return err } // Thread-safe cache write - storage.pathCacheMutex.Lock() - storage.pathCache[relPath] = destinationMD5 - storage.pathCacheMutex.Unlock() + storage.pathCacheMutex.Lock() + storage.pathCache[relPath] = destinationMD5 + storage.pathCacheMutex.Unlock() } if destinationMD5 == sourceMD5 { @@ -407,24 +508,75 @@ func (storage *PublishedStorage) LinkFromPool(publishedPrefix, publishedRelPath, } } + // If concurrent uploads are disabled, use the original implementation + if storage.concurrentUploads == 0 { + source, err := sourcePool.Open(sourcePath) + if err != nil { + return err + } + defer func() { _ = source.Close() }() + + log.Debug().Msgf("S3: LinkFromPool '%s'", relPath) + err = storage.putFile(relPath, source, sourceMD5) + if err == nil { + // Thread-safe cache write + storage.pathCacheMutex.Lock() + storage.pathCache[relPath] = sourceMD5 + storage.pathCacheMutex.Unlock() + } else { + err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s: %s", sourcePath, storage, poolPath)) + } + + return err + } + + // Concurrent upload path + log.Debug().Msgf("S3: LinkFromPool '%s' (concurrent)", relPath) + + // Check for any previous errors + select { + case err := <-storage.uploadErrors: + return err + default: + } + + // Open the source file to create a copy for the worker source, err := sourcePool.Open(sourcePath) if err != nil { return err } - defer func() { _ = source.Close() }() - log.Debug().Msgf("S3: LinkFromPool '%s'", relPath) - err = storage.putFile(relPath, source, sourceMD5) - if err == nil { - // Thread-safe cache write + // Read the entire content into memory to avoid concurrent access issues + content, err := io.ReadAll(source) + _ = source.Close() + if err != nil { + return err + } + + // Create a new reader from the content + reader := bytes.NewReader(content) + + // Queue the upload task + task := &uploadTask{ + path: relPath, + sourceReader: reader, + sourceMD5: sourceMD5, + isFile: false, + } + + storage.uploadWg.Add(1) + select { + case storage.uploadQueue <- task: + // Task queued successfully + // Update cache optimistically storage.pathCacheMutex.Lock() storage.pathCache[relPath] = sourceMD5 storage.pathCacheMutex.Unlock() - } else { - err = errors.Wrap(err, fmt.Sprintf("error uploading %s to %s: %s", sourcePath, storage, poolPath)) + return nil + case err := <-storage.uploadErrors: + storage.uploadWg.Done() + return err } - - return err } // Filelist returns list of files under prefix @@ -542,6 +694,25 @@ func (storage *PublishedStorage) HardLink(src string, dst string) error { return storage.SymLink(src, dst) } +// Flush waits for all concurrent uploads to complete and returns any errors +func (storage *PublishedStorage) Flush() error { + if storage.concurrentUploads == 0 { + // Nothing to flush if concurrent uploads are disabled + return nil + } + + // Wait for all uploads to complete + storage.uploadWg.Wait() + + // Check for any errors + select { + case err := <-storage.uploadErrors: + return err + default: + return nil + } +} + // FileExists returns true if path exists func (storage *PublishedStorage) FileExists(path string) (bool, error) { params := &s3.HeadObjectInput{ diff --git a/s3/public_test.go b/s3/public_test.go index 375f7813..a2bf98a6 100644 --- a/s3/public_test.go +++ b/s3/public_test.go @@ -411,7 +411,7 @@ func (s *PublishedStorageSuite) TestConcurrentUploads(c *C) { // Create storage with concurrent uploads enabled (3 workers, default queue size) concurrentStorage, err := NewPublishedStorage("aa", "bb", "", "test-1", s.srv.URL(), "test", "", "concurrent", "", "", false, true, false, false, false, 3, 0) c.Assert(err, IsNil) - + // Create test files tmpDir := c.MkDir() files := []string{"file1.txt", "file2.txt", "file3.txt", "file4.txt", "file5.txt"} @@ -419,17 +419,17 @@ func (s *PublishedStorageSuite) TestConcurrentUploads(c *C) { err := os.WriteFile(filepath.Join(tmpDir, name), []byte("test content: "+name), 0644) c.Assert(err, IsNil) } - + // Upload files concurrently for _, name := range files { err := concurrentStorage.PutFile(name, filepath.Join(tmpDir, name)) c.Assert(err, IsNil) } - + // Flush to ensure all uploads complete err = concurrentStorage.Flush() c.Assert(err, IsNil) - + // Verify all files exist for _, name := range files { exists, err := concurrentStorage.FileExists(name) @@ -442,7 +442,7 @@ func (s *PublishedStorageSuite) TestConcurrentUploadsWithCustomQueueSize(c *C) { // Create storage with concurrent uploads and custom queue size (2 workers, 5x queue size) concurrentStorage, err := NewPublishedStorage("aa", "bb", "", "test-1", s.srv.URL(), "test", "", "concurrent-custom", "", "", false, true, false, false, false, 2, 5) c.Assert(err, IsNil) - + // Create test files tmpDir := c.MkDir() // Create more files than workers * queue size (2 * 5 = 10) @@ -454,21 +454,21 @@ func (s *PublishedStorageSuite) TestConcurrentUploadsWithCustomQueueSize(c *C) { err := os.WriteFile(filepath.Join(tmpDir, name), []byte(fmt.Sprintf("content %d", i)), 0644) c.Assert(err, IsNil) } - + // Upload files concurrently for _, name := range files { err := concurrentStorage.PutFile(name, filepath.Join(tmpDir, name)) c.Assert(err, IsNil) } - + // Flush to ensure all uploads complete err = concurrentStorage.Flush() c.Assert(err, IsNil) - + // Verify all files exist for _, name := range files { exists, err := concurrentStorage.FileExists(name) c.Assert(err, IsNil) c.Check(exists, Equals, true) } -} \ No newline at end of file +} diff --git a/s3/server_test.go b/s3/server_test.go index a8a30475..4a2e7111 100644 --- a/s3/server_test.go +++ b/s3/server_test.go @@ -112,9 +112,11 @@ func NewServer(config *Config) (*Server, error) { buckets: make(map[string]*bucket), config: config, } - go func() { _ = http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - srv.serveHTTP(w, req) - })) }() + go func() { + _ = http.Serve(l, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + srv.serveHTTP(w, req) + })) + }() return srv, nil } @@ -527,14 +529,13 @@ func (bucketResource) post(a *action) interface{} { // and dashes (-). You can use uppercase letters for buckets only in the // US Standard region. // -// Must start with a number or letter +// # Must start with a number or letter // -// Must be between 3 and 255 characters long +// # Must be between 3 and 255 characters long // // There's one extra rule (Must not be formatted as an IP address (e.g., 192.168.5.4) // but the real S3 server does not seem to check that rule, so we will not // check it either. -// func validBucketName(name string) bool { if len(name) < 3 || len(name) > 255 { return false diff --git a/swift/public.go b/swift/public.go index dd3c6d7b..d541ac25 100644 --- a/swift/public.go +++ b/swift/public.go @@ -317,3 +317,8 @@ func (storage *PublishedStorage) ReadLink(path string) (string, error) { return headers["SymLink"], nil } + +// Flush is a no-op for Swift storage +func (storage *PublishedStorage) Flush() error { + return nil +} diff --git a/systemd/README.md b/systemd/README.md index 1c07e682..154d8b56 100644 --- a/systemd/README.md +++ b/systemd/README.md @@ -1,4 +1,4 @@ -Partial import of https://github.com/coreos/go-systemd to avoid a build dependency on systemd-dev (which is not reasonably available on the type of Travis CI that is used - i.e. Ubuntu 14.04). +Partial import of https://github.com/coreos/go-systemd to avoid a build dependency on systemd-dev for maximum build compatibility across different environments. This import only includes activation code without tests as the tests use code from another directory making them not relocatable without introducing a delta to make them pass. diff --git a/systemd/activation/activation_test.go b/systemd/activation/activation_test.go index c7e5ecd8..967018c3 100644 --- a/systemd/activation/activation_test.go +++ b/systemd/activation/activation_test.go @@ -33,7 +33,7 @@ func (s *ActivationSuite) TearDownTest(c *C) { } else { os.Unsetenv("LISTEN_PID") } - + if s.originalFDS != "" { os.Setenv("LISTEN_FDS", s.originalFDS) } else { @@ -45,7 +45,7 @@ func (s *ActivationSuite) TestFilesNoEnvironment(c *C) { // Test Files function when no environment variables are set os.Unsetenv("LISTEN_PID") os.Unsetenv("LISTEN_FDS") - + files := Files(false) c.Check(files, IsNil) } @@ -54,10 +54,10 @@ func (s *ActivationSuite) TestFilesWrongPID(c *C) { // Test Files function when LISTEN_PID doesn't match current process currentPID := os.Getpid() wrongPID := currentPID + 1000 - + os.Setenv("LISTEN_PID", strconv.Itoa(wrongPID)) os.Setenv("LISTEN_FDS", "1") - + files := Files(false) c.Check(files, IsNil) } @@ -66,7 +66,7 @@ func (s *ActivationSuite) TestFilesInvalidPID(c *C) { // Test Files function with invalid PID os.Setenv("LISTEN_PID", "invalid") os.Setenv("LISTEN_FDS", "1") - + files := Files(false) c.Check(files, IsNil) } @@ -76,7 +76,7 @@ func (s *ActivationSuite) TestFilesInvalidFDS(c *C) { currentPID := os.Getpid() os.Setenv("LISTEN_PID", strconv.Itoa(currentPID)) os.Setenv("LISTEN_FDS", "invalid") - + files := Files(false) c.Check(files, IsNil) } @@ -86,7 +86,7 @@ func (s *ActivationSuite) TestFilesZeroFDS(c *C) { currentPID := os.Getpid() os.Setenv("LISTEN_PID", strconv.Itoa(currentPID)) os.Setenv("LISTEN_FDS", "0") - + files := Files(false) c.Check(files, IsNil) } @@ -96,7 +96,7 @@ func (s *ActivationSuite) TestFilesCorrectPID(c *C) { currentPID := os.Getpid() os.Setenv("LISTEN_PID", strconv.Itoa(currentPID)) os.Setenv("LISTEN_FDS", "2") - + files := Files(false) // Should return a slice of files even if the FDs aren't valid c.Check(files, NotNil) @@ -108,13 +108,13 @@ func (s *ActivationSuite) TestFilesUnsetEnv(c *C) { currentPID := os.Getpid() os.Setenv("LISTEN_PID", strconv.Itoa(currentPID)) os.Setenv("LISTEN_FDS", "1") - + files := Files(true) - + // Environment variables should be unset after the call c.Check(os.Getenv("LISTEN_PID"), Equals, "") c.Check(os.Getenv("LISTEN_FDS"), Equals, "") - + // Should still return files c.Check(files, NotNil) c.Check(len(files), Equals, 1) @@ -124,16 +124,16 @@ func (s *ActivationSuite) TestFilesKeepEnv(c *C) { // Test Files function with unsetEnv=false currentPID := os.Getpid() pidStr := strconv.Itoa(currentPID) - + os.Setenv("LISTEN_PID", pidStr) os.Setenv("LISTEN_FDS", "1") - + files := Files(false) - + // Environment variables should remain set c.Check(os.Getenv("LISTEN_PID"), Equals, pidStr) c.Check(os.Getenv("LISTEN_FDS"), Equals, "1") - + // Should return files c.Check(files, NotNil) c.Check(len(files), Equals, 1) @@ -143,7 +143,7 @@ func (s *ActivationSuite) TestListenersNoFiles(c *C) { // Test Listeners function when Files returns nil os.Unsetenv("LISTEN_PID") os.Unsetenv("LISTEN_FDS") - + listeners, err := Listeners(false) c.Check(err, IsNil) c.Check(listeners, NotNil) @@ -155,12 +155,12 @@ func (s *ActivationSuite) TestListenersWithFiles(c *C) { currentPID := os.Getpid() os.Setenv("LISTEN_PID", strconv.Itoa(currentPID)) os.Setenv("LISTEN_FDS", "2") - + listeners, err := Listeners(false) c.Check(err, IsNil) c.Check(listeners, NotNil) c.Check(len(listeners), Equals, 2) - + // The listeners will be nil because the FDs aren't real sockets for _, listener := range listeners { c.Check(listener, IsNil) @@ -171,7 +171,7 @@ func (s *ActivationSuite) TestPacketConnsNoFiles(c *C) { // Test PacketConns function when Files returns nil os.Unsetenv("LISTEN_PID") os.Unsetenv("LISTEN_FDS") - + conns, err := PacketConns(false) c.Check(err, IsNil) c.Check(conns, NotNil) @@ -183,12 +183,12 @@ func (s *ActivationSuite) TestPacketConnsWithFiles(c *C) { currentPID := os.Getpid() os.Setenv("LISTEN_PID", strconv.Itoa(currentPID)) os.Setenv("LISTEN_FDS", "3") - + conns, err := PacketConns(false) c.Check(err, IsNil) c.Check(conns, NotNil) c.Check(len(conns), Equals, 3) - + // The connections will be nil because the FDs aren't real packet sockets for _, conn := range conns { c.Check(conn, IsNil) @@ -199,7 +199,7 @@ func (s *ActivationSuite) TestTLSListenersNilConfig(c *C) { // Test TLSListeners with nil TLS config os.Unsetenv("LISTEN_PID") os.Unsetenv("LISTEN_FDS") - + listeners, err := TLSListeners(false, nil) c.Check(err, IsNil) c.Check(listeners, NotNil) @@ -211,16 +211,16 @@ func (s *ActivationSuite) TestTLSListenersWithConfig(c *C) { currentPID := os.Getpid() os.Setenv("LISTEN_PID", strconv.Itoa(currentPID)) os.Setenv("LISTEN_FDS", "2") - + tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, } - + listeners, err := TLSListeners(false, tlsConfig) c.Check(err, IsNil) c.Check(listeners, NotNil) c.Check(len(listeners), Equals, 2) - + // The listeners will be nil because the FDs aren't real sockets // This is expected behavior in test environment for _, listener := range listeners { @@ -237,14 +237,14 @@ func (s *ActivationSuite) TestFileDescriptorRange(c *C) { // Test file descriptor range calculation currentPID := os.Getpid() nfds := 5 - + os.Setenv("LISTEN_PID", strconv.Itoa(currentPID)) os.Setenv("LISTEN_FDS", strconv.Itoa(nfds)) - + files := Files(false) c.Check(files, NotNil) c.Check(len(files), Equals, nfds) - + // Check that file descriptors start from listenFdsStart for i, file := range files { expectedFD := listenFdsStart + i @@ -270,17 +270,17 @@ func (m mockListener) Addr() net.Addr { return m.addr } func (s *ActivationSuite) TestTLSListenerWrapping(c *C) { // Test TLS listener wrapping logic - + // Create mock listeners tcpListener := &mockListener{addr: mockAddr{network: "tcp"}} udpListener := &mockListener{addr: mockAddr{network: "udp"}} - + listeners := []net.Listener{tcpListener, udpListener, nil} - + tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, } - + // Simulate the TLS wrapping logic for i, l := range listeners { if l != nil && l.Addr().Network() == "tcp" { @@ -290,11 +290,11 @@ func (s *ActivationSuite) TestTLSListenerWrapping(c *C) { listeners[i] = l // Keep reference for test } } - + // Verify that only TCP listeners would be wrapped - c.Check(listeners[0].Addr().Network(), Equals, "tcp") // Would be wrapped - c.Check(listeners[1].Addr().Network(), Equals, "udp") // Would not be wrapped - c.Check(listeners[2], IsNil) // Nil listener + c.Check(listeners[0].Addr().Network(), Equals, "tcp") // Would be wrapped + c.Check(listeners[1].Addr().Network(), Equals, "udp") // Would not be wrapped + c.Check(listeners[2], IsNil) // Nil listener } func (s *ActivationSuite) TestEnvironmentVariableHandling(c *C) { @@ -313,13 +313,13 @@ func (s *ActivationSuite) TestEnvironmentVariableHandling(c *C) { {"negative FDS", strconv.Itoa(os.Getpid()), "-1", false}, {"small FDS", strconv.Itoa(os.Getpid()), "2", true}, } - + for _, tc := range testCases { os.Setenv("LISTEN_PID", tc.pid) os.Setenv("LISTEN_FDS", tc.fds) - + files := Files(false) - + if tc.expected { c.Check(files, NotNil, Commentf("Test case: %s", tc.name)) if tc.fds != "0" && tc.fds != "-1" { @@ -336,12 +336,12 @@ func (s *ActivationSuite) TestEnvironmentVariableHandling(c *C) { func (s *ActivationSuite) TestErrorHandling(c *C) { // Test error handling in all functions - + // Test Listeners with no error listeners, err := Listeners(false) c.Check(err, IsNil) c.Check(listeners, NotNil) - + // Test PacketConns with no error conns, err := PacketConns(false) c.Check(err, IsNil) @@ -353,7 +353,7 @@ func (s *ActivationSuite) TestTLSListenersWithNilConfig(c *C) { currentPID := os.Getpid() os.Setenv("LISTEN_PID", strconv.Itoa(currentPID)) os.Setenv("LISTEN_FDS", "1") - + listeners, err := TLSListeners(false, nil) c.Check(err, IsNil) c.Check(listeners, NotNil) @@ -365,10 +365,10 @@ func (s *ActivationSuite) TestFilesUnsetEnvAdditional(c *C) { currentPID := os.Getpid() os.Setenv("LISTEN_PID", strconv.Itoa(currentPID)) os.Setenv("LISTEN_FDS", "1") - + files := Files(true) c.Check(files, NotNil) - + // Environment variables should be unset after the call c.Check(os.Getenv("LISTEN_PID"), Equals, "") c.Check(os.Getenv("LISTEN_FDS"), Equals, "") @@ -378,11 +378,11 @@ func (s *ActivationSuite) TestTLSListenersNilListeners(c *C) { // Test TLSListeners when Listeners returns empty slice os.Unsetenv("LISTEN_PID") os.Unsetenv("LISTEN_FDS") - + tlsConfig := &tls.Config{ MinVersion: tls.VersionTLS12, } - + listeners, err := TLSListeners(false, tlsConfig) c.Check(err, IsNil) c.Check(listeners, NotNil) // Returns empty slice, not nil @@ -394,7 +394,7 @@ func (s *ActivationSuite) TestExcessiveFDSLimit(c *C) { currentPID := os.Getpid() os.Setenv("LISTEN_PID", strconv.Itoa(currentPID)) os.Setenv("LISTEN_FDS", "2000") // Over the 1000 limit - + files := Files(false) c.Check(files, IsNil) // Should return nil due to excessive FDS count } @@ -402,22 +402,22 @@ func (s *ActivationSuite) TestExcessiveFDSLimit(c *C) { func (s *ActivationSuite) TestDeferredEnvironmentCleanup(c *C) { // Test the deferred environment cleanup currentPID := os.Getpid() - + // Set environment variables os.Setenv("LISTEN_PID", strconv.Itoa(currentPID)) os.Setenv("LISTEN_FDS", "1") - + // Verify they are set c.Check(os.Getenv("LISTEN_PID"), Not(Equals), "") c.Check(os.Getenv("LISTEN_FDS"), Not(Equals), "") - + // Call Files with unsetEnv=true files := Files(true) - + // Verify environment is cleaned up c.Check(os.Getenv("LISTEN_PID"), Equals, "") c.Check(os.Getenv("LISTEN_FDS"), Equals, "") - + // Should still return files c.Check(files, NotNil) } @@ -425,16 +425,16 @@ func (s *ActivationSuite) TestDeferredEnvironmentCleanup(c *C) { func (s *ActivationSuite) TestCloseOnExecCall(c *C) { // Test that CloseOnExec is called for file descriptors // This is a structural test since we can't easily verify syscall effects - + currentPID := os.Getpid() os.Setenv("LISTEN_PID", strconv.Itoa(currentPID)) os.Setenv("LISTEN_FDS", "2") - + files := Files(false) c.Check(files, NotNil) c.Check(len(files), Equals, 2) - + // Verify files are created with expected names c.Check(files[0].Name(), Equals, "LISTEN_FD_3") c.Check(files[1].Name(), Equals, "LISTEN_FD_4") -} \ No newline at end of file +} diff --git a/task/list.go b/task/list.go index 5b09d34c..52990887 100644 --- a/task/list.go +++ b/task/list.go @@ -51,6 +51,16 @@ func (list *List) consumer() { list.Unlock() go func() { + // Ensure Done() is always called, even if panic occurs + defer func() { + list.Lock() + defer list.Unlock() + + task.wgTask.Done() + list.wg.Done() + list.usedResources.Free(task.resources) + }() + retValue, err := task.process(aptly.Progress(task.output), task.detail) list.Lock() @@ -65,17 +75,12 @@ func (list *List) consumer() { task.State = SUCCEEDED } - list.usedResources.Free(task.resources) - - task.wgTask.Done() - list.wg.Done() - for _, t := range list.tasks { if t.State == IDLE { // check resources blockingTasks := list.usedResources.UsedBy(t.resources) if len(blockingTasks) == 0 { - list.usedResources.MarkInUse(task.resources, task) + list.usedResources.MarkInUse(t.resources, t) list.queue <- t break } diff --git a/task/list_race_test.go b/task/list_race_test.go index 3ee03163..f1e33c58 100644 --- a/task/list_race_test.go +++ b/task/list_race_test.go @@ -5,7 +5,7 @@ import ( "sync" "testing" "time" - + "github.com/aptly-dev/aptly/aptly" ) @@ -13,25 +13,25 @@ import ( func TestTaskListGoroutineLeak(t *testing.T) { // Record initial goroutine count initialGoroutines := runtime.NumGoroutine() - + // Create multiple TaskLists and store them for explicit cleanup lists := make([]*TaskList, 10) for i := 0; i < 10; i++ { lists[i] = NewList() } - + // Test proper cleanup with Stop() for _, list := range lists { list.Stop() } - + // Allow time for goroutines to stop time.Sleep(50 * time.Millisecond) - + // Check if goroutines properly cleaned up finalGoroutines := runtime.NumGoroutine() leaked := finalGoroutines - initialGoroutines - + if leaked > 0 { t.Errorf("Goroutine leak detected even with Stop(): %d goroutines leaked", leaked) t.Logf("Initial: %d, Final: %d", initialGoroutines, finalGoroutines) @@ -41,11 +41,11 @@ func TestTaskListGoroutineLeak(t *testing.T) { // Test 2: Double Close Panic func TestTaskListDoubleClosePanic(t *testing.T) { list := NewList() - + // Call Stop() multiple times concurrently var wg sync.WaitGroup panicked := make(chan bool, 10) - + for i := 0; i < 10; i++ { wg.Add(1) go func() { @@ -58,10 +58,10 @@ func TestTaskListDoubleClosePanic(t *testing.T) { list.Stop() }() } - + wg.Wait() close(panicked) - + // Check if any goroutine panicked for panic := range panicked { if panic { @@ -75,16 +75,16 @@ func TestTaskListDoubleClosePanic(t *testing.T) { func TestTaskListConcurrentOperations(t *testing.T) { list := NewList() defer list.Stop() - + var wg sync.WaitGroup errors := make(chan error, 100) - + // Simulate concurrent task creation and deletion for i := 0; i < 50; i++ { wg.Add(1) go func(id int) { defer wg.Done() - + // Create task task, err := list.RunTaskInBackground( "test-task", @@ -94,7 +94,7 @@ func TestTaskListConcurrentOperations(t *testing.T) { return &ProcessReturnValue{Code: 200}, nil }, ) - + if err != nil { select { case errors <- err: @@ -102,7 +102,7 @@ func TestTaskListConcurrentOperations(t *testing.T) { } return } - + // Try to get task details _, getErr := list.GetTaskByID(task.ID) if getErr != nil { @@ -113,10 +113,10 @@ func TestTaskListConcurrentOperations(t *testing.T) { } }(i) } - + wg.Wait() close(errors) - + // Check for errors for err := range errors { t.Errorf("Concurrent operation error: %v", err) @@ -127,16 +127,16 @@ func TestTaskListConcurrentOperations(t *testing.T) { func TestTaskListResourceRace(t *testing.T) { list := NewList() defer list.Stop() - + var wg sync.WaitGroup completedTasks := make(chan int, 100) - + // Create tasks that use the same resource for i := 0; i < 20; i++ { wg.Add(1) go func(id int) { defer wg.Done() - + task, err := list.RunTaskInBackground( "resource-test", []string{"shared-resource"}, @@ -145,40 +145,40 @@ func TestTaskListResourceRace(t *testing.T) { return &ProcessReturnValue{Code: 200}, nil }, ) - + if err != nil { t.Errorf("Failed to create task: %v", err) return } - + // Wait for task completion _, waitErr := list.WaitForTaskByID(task.ID) if waitErr != nil { t.Errorf("Failed to wait for task: %v", waitErr) return } - + completedTasks <- task.ID }(i) } - + // Wait for all tasks to complete go func() { wg.Wait() close(completedTasks) }() - + // Collect completed task IDs var completed []int for taskID := range completedTasks { completed = append(completed, taskID) } - + // Check that all tasks completed if len(completed) != 20 { t.Errorf("Expected 20 completed tasks, got %d", len(completed)) } - + // Check for resource leaks by examining remaining tasks remainingTasks := list.GetTasks() runningCount := 0 @@ -187,8 +187,8 @@ func TestTaskListResourceRace(t *testing.T) { runningCount++ } } - + if runningCount > 0 { t.Errorf("Resource leak: %d tasks still running", runningCount) } -} \ No newline at end of file +} diff --git a/task/list_test.go b/task/list_test.go index 8bce5377..eb6ebddd 100644 --- a/task/list_test.go +++ b/task/list_test.go @@ -50,5 +50,5 @@ func (s *ListSuite) TestList(c *check.C) { c.Check(detail, check.Equals, "Details") _, deleteErr := list.DeleteTaskByID(task.ID) c.Check(deleteErr, check.IsNil) - list.Stop() + list.Stop() } diff --git a/task/output_test.go b/task/output_test.go index 6cbcbb32..ab001b9c 100644 --- a/task/output_test.go +++ b/task/output_test.go @@ -50,7 +50,7 @@ func (s *OutputSuite) TestOutputString(c *C) { func (s *OutputSuite) TestOutputStringConcurrent(c *C) { // Test String method with concurrent access var wg sync.WaitGroup - + // Start multiple goroutines writing to output for i := 0; i < 10; i++ { wg.Add(1) @@ -59,7 +59,7 @@ func (s *OutputSuite) TestOutputStringConcurrent(c *C) { s.output.WriteString("test") }(i) } - + // Start multiple goroutines reading from output for i := 0; i < 5; i++ { wg.Add(1) @@ -68,9 +68,9 @@ func (s *OutputSuite) TestOutputStringConcurrent(c *C) { _ = s.output.String() }() } - + wg.Wait() - + // Should contain all the writes result := s.output.String() c.Check(len(result), Equals, 40) // 10 * "test" = 40 chars @@ -82,7 +82,7 @@ func (s *OutputSuite) TestOutputWrite(c *C) { n, err := s.output.Write(data) c.Check(err, IsNil) c.Check(n, Equals, len(data)) - + // Write method doesn't actually write to buffer, just returns length c.Check(s.output.String(), Equals, "") } @@ -90,7 +90,7 @@ func (s *OutputSuite) TestOutputWrite(c *C) { func (s *OutputSuite) TestOutputWriteError(c *C) { // Test Write method - can't override the method, so just test normal behavior data := []byte("test data") - + n, err := s.output.Write(data) c.Check(err, IsNil) c.Check(n, Equals, len(data)) @@ -108,20 +108,20 @@ func (s *OutputSuite) TestOutputWriteString(c *C) { func (s *OutputSuite) TestOutputWriteStringMultiple(c *C) { // Test multiple WriteString calls texts := []string{"hello", " ", "world", "!"} - + for _, text := range texts { n, err := s.output.WriteString(text) c.Check(err, IsNil) c.Check(n, Equals, len(text)) } - + c.Check(s.output.String(), Equals, "hello world!") } func (s *OutputSuite) TestOutputWriteStringConcurrent(c *C) { // Test WriteString with concurrent access var wg sync.WaitGroup - + // Start multiple goroutines writing different strings texts := []string{"a", "b", "c", "d", "e"} for _, text := range texts { @@ -131,12 +131,12 @@ func (s *OutputSuite) TestOutputWriteStringConcurrent(c *C) { s.output.WriteString(t) }(text) } - + wg.Wait() - + result := s.output.String() c.Check(len(result), Equals, 5) - + // All characters should be present (order may vary due to concurrency) for _, text := range texts { c.Check(result, Matches, ".*"+text+".*") @@ -279,7 +279,7 @@ func (s *OutputSuite) TestOutputMixedMethods(c *C) { s.output.Printf(" %s", "middle") s.output.ColoredPrintf(" %s", "end") s.output.PrintfStdErr("error") - + expected := "Start middle end\nerror" c.Check(s.output.String(), Equals, expected) } @@ -290,7 +290,7 @@ func (s *OutputSuite) TestPublishOutputInitBar(c *C) { // Test InitBar for publish output count := int64(100) s.publishOutput.InitBar(count, false, aptly.BarPublishGeneratePackageFiles) - + c.Check(s.publishOutput.barType, NotNil) c.Check(*s.publishOutput.barType, Equals, aptly.BarPublishGeneratePackageFiles) c.Check(s.publishOutput.TotalNumberOfPackages, Equals, count) @@ -301,7 +301,7 @@ func (s *OutputSuite) TestPublishOutputInitBarOtherType(c *C) { // Test InitBar for publish output with different bar type count := int64(50) s.publishOutput.InitBar(count, false, aptly.BarGeneralBuildPackageList) - + c.Check(s.publishOutput.barType, NotNil) c.Check(*s.publishOutput.barType, Equals, aptly.BarGeneralBuildPackageList) // Should not set package counts for other bar types @@ -313,7 +313,7 @@ func (s *OutputSuite) TestPublishOutputShutdownBar(c *C) { // Test ShutdownBar for publish output s.publishOutput.barType = &aptly_BarPublishGeneratePackageFiles_ptr s.publishOutput.ShutdownBar() - + c.Check(s.publishOutput.barType, IsNil) } @@ -321,11 +321,11 @@ func (s *OutputSuite) TestPublishOutputAddBar(c *C) { // Test AddBar for publish output with correct bar type s.publishOutput.barType = &aptly_BarPublishGeneratePackageFiles_ptr s.publishOutput.RemainingNumberOfPackages = 10 - + s.publishOutput.AddBar(1) c.Check(s.publishOutput.RemainingNumberOfPackages, Equals, int64(9)) - - s.publishOutput.AddBar(3) // Still decrements by 1, not 3 + + s.publishOutput.AddBar(3) // Still decrements by 1, not 3 c.Check(s.publishOutput.RemainingNumberOfPackages, Equals, int64(8)) } @@ -334,7 +334,7 @@ func (s *OutputSuite) TestPublishOutputAddBarWrongType(c *C) { otherBarType := aptly.BarGeneralBuildPackageList s.publishOutput.barType = &otherBarType s.publishOutput.RemainingNumberOfPackages = 10 - + s.publishOutput.AddBar(1) // Should not decrement for wrong bar type c.Check(s.publishOutput.RemainingNumberOfPackages, Equals, int64(10)) @@ -344,7 +344,7 @@ func (s *OutputSuite) TestPublishOutputAddBarNilBarType(c *C) { // Test AddBar for publish output with nil bar type s.publishOutput.barType = nil s.publishOutput.RemainingNumberOfPackages = 10 - + s.publishOutput.AddBar(1) // Should not decrement when bar type is nil c.Check(s.publishOutput.RemainingNumberOfPackages, Equals, int64(10)) @@ -353,18 +353,18 @@ func (s *OutputSuite) TestPublishOutputAddBarNilBarType(c *C) { func (s *OutputSuite) TestPublishOutputComplete(c *C) { // Test complete workflow of publish output count := int64(5) - + // Initialize s.publishOutput.InitBar(count, false, aptly.BarPublishGeneratePackageFiles) c.Check(s.publishOutput.TotalNumberOfPackages, Equals, count) c.Check(s.publishOutput.RemainingNumberOfPackages, Equals, count) - + // Simulate processing packages for i := int64(0); i < count; i++ { s.publishOutput.AddBar(1) c.Check(s.publishOutput.RemainingNumberOfPackages, Equals, count-i-1) } - + // Shutdown s.publishOutput.ShutdownBar() c.Check(s.publishOutput.barType, IsNil) @@ -374,7 +374,7 @@ func (s *OutputSuite) TestOutputThreadSafety(c *C) { // Test thread safety of all methods var wg sync.WaitGroup numGoroutines := 20 - + // Test concurrent access to all methods for i := 0; i < numGoroutines; i++ { wg.Add(1) @@ -388,17 +388,17 @@ func (s *OutputSuite) TestOutputThreadSafety(c *C) { _ = s.output.String() }(i) } - + wg.Wait() - + // Should contain all messages without corruption result := s.output.String() c.Check(len(result) > 0, Equals, true) - + // Check that a reasonable amount of output was generated // Each goroutine writes 5 messages, so we should have significant output c.Check(len(result) > numGoroutines*10, Equals, true) - + // Check that some expected message patterns exist (not all, to avoid flakiness) // This verifies basic functionality without being too strict about concurrent ordering foundMsg := false @@ -415,7 +415,7 @@ func (s *OutputSuite) TestOutputThreadSafety(c *C) { foundStderr = true } } - + c.Check(foundMsg, Equals, true) c.Check(foundPrintf, Equals, true) c.Check(foundStderr, Equals, true) @@ -425,7 +425,7 @@ func (s *OutputSuite) TestProgressInterfaceCompliance(c *C) { // Test that Output implements aptly.Progress interface var progress aptly.Progress = s.output c.Check(progress, NotNil) - + // Test calling interface methods progress.Start() progress.Shutdown() @@ -442,7 +442,7 @@ func (s *OutputSuite) TestPublishOutputProgressInterfaceCompliance(c *C) { // Test that PublishOutput implements aptly.Progress interface var progress aptly.Progress = s.publishOutput c.Check(progress, NotNil) - + // Test calling interface methods progress.InitBar(100, false, aptly.BarPublishGeneratePackageFiles) progress.AddBar(1) @@ -454,7 +454,7 @@ func (s *OutputSuite) TestPublishOutputProgressInterfaceCompliance(c *C) { func (s *OutputSuite) TestOutputLargeData(c *C) { // Test with large amounts of data largeString := strings.Repeat("x", 10000) - + n, err := s.output.WriteString(largeString) c.Check(err, IsNil) c.Check(n, Equals, len(largeString)) @@ -464,7 +464,7 @@ func (s *OutputSuite) TestOutputLargeData(c *C) { func (s *OutputSuite) TestOutputSpecialCharacters(c *C) { // Test with special characters and unicode specialString := "Hello L! \n\t\r Special: @#$%^&*()" - + s.output.WriteString(specialString) c.Check(s.output.String(), Equals, specialString) } @@ -473,7 +473,7 @@ func (s *OutputSuite) TestPublishOutputNegativeAddBar(c *C) { // Test AddBar with negative values (edge case) s.publishOutput.barType = &aptly_BarPublishGeneratePackageFiles_ptr s.publishOutput.RemainingNumberOfPackages = 5 - + // This should still decrement by 1 regardless of the parameter s.publishOutput.AddBar(-10) c.Check(s.publishOutput.RemainingNumberOfPackages, Equals, int64(4)) @@ -483,7 +483,7 @@ func (s *OutputSuite) TestPublishOutputZeroAddBar(c *C) { // Test AddBar with zero value s.publishOutput.barType = &aptly_BarPublishGeneratePackageFiles_ptr s.publishOutput.RemainingNumberOfPackages = 5 - + s.publishOutput.AddBar(0) c.Check(s.publishOutput.RemainingNumberOfPackages, Equals, int64(4)) -} \ No newline at end of file +} diff --git a/task/resources_test.go b/task/resources_test.go index 0de119ea..86dfbf96 100644 --- a/task/resources_test.go +++ b/task/resources_test.go @@ -14,12 +14,12 @@ func (s *ResourcesSuite) TestResourceConflictError(c *check.C) { // Test ResourceConflictError task1 := Task{ID: 1, Name: "Test Task 1"} task2 := Task{ID: 2, Name: "Test Task 2"} - + err := &ResourceConflictError{ Tasks: []Task{task1, task2}, Message: "Resource conflict detected", } - + c.Check(err.Error(), check.Equals, "Resource conflict detected") c.Check(len(err.Tasks), check.Equals, 2) c.Check(err.Tasks[0].ID, check.Equals, 1) @@ -37,11 +37,11 @@ func (s *ResourcesSuite) TestNewResourcesSet(c *check.C) { func (s *ResourcesSuite) TestMarkInUse(c *check.C) { rs := NewResourcesSet() task := &Task{ID: 1, Name: "Test Task"} - + // Mark resources as in use resources := []string{"resource1", "resource2"} rs.MarkInUse(resources, task) - + c.Check(len(rs.set), check.Equals, 2) c.Check(rs.set["resource1"], check.Equals, task) c.Check(rs.set["resource2"], check.Equals, task) @@ -49,7 +49,7 @@ func (s *ResourcesSuite) TestMarkInUse(c *check.C) { func (s *ResourcesSuite) TestUsedByEmpty(c *check.C) { rs := NewResourcesSet() - + // Test with empty resource set tasks := rs.UsedBy([]string{"resource1"}) c.Check(len(tasks), check.Equals, 0) @@ -59,20 +59,20 @@ func (s *ResourcesSuite) TestUsedByBasic(c *check.C) { rs := NewResourcesSet() task1 := &Task{ID: 1, Name: "Task 1"} task2 := &Task{ID: 2, Name: "Task 2"} - + // Mark different resources rs.MarkInUse([]string{"resource1"}, task1) rs.MarkInUse([]string{"resource2"}, task2) - + // Test finding tasks by resource tasks := rs.UsedBy([]string{"resource1"}) c.Check(len(tasks), check.Equals, 1) c.Check(tasks[0].ID, check.Equals, 1) - + tasks = rs.UsedBy([]string{"resource2"}) c.Check(len(tasks), check.Equals, 1) c.Check(tasks[0].ID, check.Equals, 2) - + // Test non-existent resource tasks = rs.UsedBy([]string{"nonexistent"}) c.Check(len(tasks), check.Equals, 0) @@ -83,16 +83,16 @@ func (s *ResourcesSuite) TestUsedByAllLocalRepos(c *check.C) { task1 := &Task{ID: 1, Name: "Local Task 1"} task2 := &Task{ID: 2, Name: "Local Task 2"} task3 := &Task{ID: 3, Name: "Remote Task"} - + // Mark resources with local repo prefix "L" rs.MarkInUse([]string{"Lrepo1"}, task1) rs.MarkInUse([]string{"Lrepo2"}, task2) rs.MarkInUse([]string{"Rremote1"}, task3) - + // Test AllLocalReposResourcesKey tasks := rs.UsedBy([]string{AllLocalReposResourcesKey}) c.Check(len(tasks), check.Equals, 2) - + // Verify both local repo tasks are found var foundTask1, foundTask2 bool for _, task := range tasks { @@ -112,16 +112,16 @@ func (s *ResourcesSuite) TestUsedByAllResources(c *check.C) { task1 := &Task{ID: 1, Name: "Task 1"} task2 := &Task{ID: 2, Name: "Task 2"} task3 := &Task{ID: 3, Name: "Task 3"} - + // Mark different resources rs.MarkInUse([]string{"resource1"}, task1) rs.MarkInUse([]string{"resource2"}, task2) rs.MarkInUse([]string{"resource3"}, task3) - + // Test AllResourcesKey tasks := rs.UsedBy([]string{AllResourcesKey}) c.Check(len(tasks), check.Equals, 3) - + // Verify all tasks are found taskIDs := make(map[int]bool) for _, task := range tasks { @@ -136,15 +136,15 @@ func (s *ResourcesSuite) TestUsedBySpecialResourceMarked(c *check.C) { rs := NewResourcesSet() allLocalTask := &Task{ID: 1, Name: "All Local Task"} allTask := &Task{ID: 2, Name: "All Task"} - + // Mark special resources directly rs.MarkInUse([]string{AllLocalReposResourcesKey}, allLocalTask) rs.MarkInUse([]string{AllResourcesKey}, allTask) - + // Test finding by any resource should include special tasks tasks := rs.UsedBy([]string{"any-resource"}) c.Check(len(tasks), check.Equals, 2) - + taskIDs := make(map[int]bool) for _, task := range tasks { taskIDs[task.ID] = true @@ -157,22 +157,22 @@ func (s *ResourcesSuite) TestAppendTaskDuplicates(c *check.C) { // Test appendTask function with duplicates task1 := &Task{ID: 1, Name: "Task 1"} task2 := &Task{ID: 2, Name: "Task 2"} - + var tasks []Task - + // Add first task tasks = appendTask(tasks, task1) c.Check(len(tasks), check.Equals, 1) c.Check(tasks[0].ID, check.Equals, 1) - + // Add second task tasks = appendTask(tasks, task2) c.Check(len(tasks), check.Equals, 2) - + // Try to add first task again (should not duplicate) tasks = appendTask(tasks, task1) c.Check(len(tasks), check.Equals, 2) - + // Verify no duplicate taskIDs := make(map[int]int) for _, task := range tasks { @@ -186,25 +186,25 @@ func (s *ResourcesSuite) TestFree(c *check.C) { rs := NewResourcesSet() task1 := &Task{ID: 1, Name: "Task 1"} task2 := &Task{ID: 2, Name: "Task 2"} - + // Mark resources rs.MarkInUse([]string{"resource1", "resource2"}, task1) rs.MarkInUse([]string{"resource3"}, task2) - + c.Check(len(rs.set), check.Equals, 3) - + // Free some resources rs.Free([]string{"resource1", "resource3"}) c.Check(len(rs.set), check.Equals, 1) c.Check(rs.set["resource2"], check.Equals, task1) - + // Verify freed resources are no longer in use tasks := rs.UsedBy([]string{"resource1"}) c.Check(len(tasks), check.Equals, 0) - + tasks = rs.UsedBy([]string{"resource3"}) c.Check(len(tasks), check.Equals, 0) - + // But resource2 should still be in use tasks = rs.UsedBy([]string{"resource2"}) c.Check(len(tasks), check.Equals, 1) @@ -214,14 +214,14 @@ func (s *ResourcesSuite) TestFree(c *check.C) { func (s *ResourcesSuite) TestFreeNonExistentResources(c *check.C) { rs := NewResourcesSet() task := &Task{ID: 1, Name: "Task 1"} - + rs.MarkInUse([]string{"resource1"}, task) c.Check(len(rs.set), check.Equals, 1) - + // Free non-existent resources (should not panic) rs.Free([]string{"nonexistent1", "nonexistent2"}) c.Check(len(rs.set), check.Equals, 1) - + // Free mix of existing and non-existent rs.Free([]string{"resource1", "nonexistent"}) c.Check(len(rs.set), check.Equals, 0) @@ -233,23 +233,23 @@ func (s *ResourcesSuite) TestComplexScenario(c *check.C) { localTask2 := &Task{ID: 2, Name: "Local Task 2"} remoteTask := &Task{ID: 3, Name: "Remote Task"} globalTask := &Task{ID: 4, Name: "Global Task"} - + // Set up complex scenario rs.MarkInUse([]string{"Llocal-repo-1"}, localTask1) rs.MarkInUse([]string{"Llocal-repo-2"}, localTask2) rs.MarkInUse([]string{"Rremote-repo"}, remoteTask) rs.MarkInUse([]string{AllResourcesKey}, globalTask) - + // Test various queries tasks := rs.UsedBy([]string{"Llocal-repo-1"}) c.Check(len(tasks), check.Equals, 2) // localTask1 + globalTask - + tasks = rs.UsedBy([]string{AllLocalReposResourcesKey}) c.Check(len(tasks), check.Equals, 3) // localTask1 + localTask2 + globalTask - + tasks = rs.UsedBy([]string{"Rremote-repo"}) c.Check(len(tasks), check.Equals, 2) // remoteTask + globalTask - + tasks = rs.UsedBy([]string{AllResourcesKey}) c.Check(len(tasks), check.Equals, 4) // All tasks -} \ No newline at end of file +} diff --git a/task/simple_test.go b/task/simple_test.go index c95e4d46..85f4eb4b 100644 --- a/task/simple_test.go +++ b/task/simple_test.go @@ -12,19 +12,19 @@ var _ = check.Suite(&SimpleSuite{}) func (s *SimpleSuite) TestSimpleTask(c *check.C) { list := NewList() defer list.Stop() - + c.Check(len(list.GetTasks()), check.Equals, 0) - + task, err := list.RunTaskInBackground("Simple task", nil, func(out aptly.Progress, detail *Detail) (*ProcessReturnValue, error) { return nil, nil }) c.Assert(err, check.IsNil) - + _, _ = list.WaitForTaskByID(task.ID) - + tasks := list.GetTasks() c.Check(len(tasks), check.Equals, 1) - + taskResult, _ := list.GetTaskByID(task.ID) c.Check(taskResult.State, check.Equals, SUCCEEDED) } @@ -33,7 +33,7 @@ func (s *SimpleSuite) TestTaskWait(c *check.C) { // Test Wait function with no running tasks list := NewList() defer list.Stop() - + // This should return immediately since no tasks are running list.Wait() c.Check(true, check.Equals, true) // Test passes if Wait returns @@ -42,18 +42,18 @@ func (s *SimpleSuite) TestTaskWait(c *check.C) { func (s *SimpleSuite) TestOutputProgress(c *check.C) { // Test Output progress methods with zero counts output := NewOutput() - + // Test progress methods that should be no-ops output.Start() output.Shutdown() output.Flush() - + // Test bar methods with zero counts output.InitBar(0, false, 0) output.ShutdownBar() output.AddBar(0) output.SetBar(0) - + c.Check(true, check.Equals, true) // All methods should complete without error } @@ -61,7 +61,7 @@ func (s *SimpleSuite) TestTaskCleanup(c *check.C) { // Test task cleanup functionality list := NewList() defer list.Stop() - + // Test that cleanup method exists and can be called list.cleanup() c.Check(true, check.Equals, true) // Cleanup should complete without error @@ -70,11 +70,11 @@ func (s *SimpleSuite) TestTaskCleanup(c *check.C) { func (s *SimpleSuite) TestTaskListStop(c *check.C) { // Test Stop method behavior with partial coverage list := NewList() - + // Stop the list multiple times to test idempotency list.Stop() list.Stop() // Second call should be safe - + c.Check(true, check.Equals, true) } @@ -82,7 +82,7 @@ func (s *SimpleSuite) TestDeleteTaskByIDEdgeCases(c *check.C) { // Test DeleteTaskByID edge cases list := NewList() defer list.Stop() - + // Test deleting non-existent task _, err := list.DeleteTaskByID(999) c.Check(err, check.NotNil) // Should return error for non-existent task @@ -92,25 +92,25 @@ func (s *SimpleSuite) TestTaskWithProgress(c *check.C) { // Test task with progress operations list := NewList() defer list.Stop() - + task, err := list.RunTaskInBackground("Progress task", nil, func(out aptly.Progress, detail *Detail) (*ProcessReturnValue, error) { // Test progress bar operations out.InitBar(100, false, 1) out.AddBar(50) out.SetBar(75) out.ShutdownBar() - + // Test printing operations out.Printf("Test message: %s", "hello") out.ColoredPrintf("Colored message: %s", "world") out.PrintfStdErr("Error message: %s", "test") - + return &ProcessReturnValue{}, nil }) c.Assert(err, check.IsNil) - + _, _ = list.WaitForTaskByID(task.ID) - + taskResult, _ := list.GetTaskByID(task.ID) c.Check(taskResult.State, check.Equals, SUCCEEDED) -} \ No newline at end of file +} diff --git a/utils/checksum_extra_test.go b/utils/checksum_extra_test.go index 5fd108bd..52938723 100644 --- a/utils/checksum_extra_test.go +++ b/utils/checksum_extra_test.go @@ -2,7 +2,7 @@ package utils import ( "io/ioutil" - + . "gopkg.in/check.v1" ) @@ -14,23 +14,23 @@ func (s *ChecksumExtraSuite) TestComplete(c *C) { // Test incomplete checksum info info := ChecksumInfo{} c.Assert(info.Complete(), Equals, false) - + // Test with only MD5 info.MD5 = "d41d8cd98f00b204e9800998ecf8427e" c.Assert(info.Complete(), Equals, false) - + // Test with MD5 and SHA1 info.SHA1 = "da39a3ee5e6b4b0d3255bfef95601890afd80709" c.Assert(info.Complete(), Equals, false) - + // Test with MD5, SHA1, and SHA256 info.SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" c.Assert(info.Complete(), Equals, false) - + // Test with all checksums present info.SHA512 = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e" c.Assert(info.Complete(), Equals, true) - + // Test with empty strings counts as incomplete info2 := ChecksumInfo{ MD5: "", @@ -44,22 +44,22 @@ func (s *ChecksumExtraSuite) TestComplete(c *C) { func (s *ChecksumExtraSuite) TestSaveConfigRaw(c *C) { tempDir := c.MkDir() configFile := tempDir + "/test.conf" - + testData := []byte(`{ "rootDir": "/tmp/aptly", "architectures": ["amd64", "i386"], "dependencyFollowSuggests": false }`) - + // Test normal save err := SaveConfigRaw(configFile, testData) c.Assert(err, IsNil) - + // Verify file was created with correct content data, err := ioutil.ReadFile(configFile) c.Assert(err, IsNil) c.Assert(data, DeepEquals, testData) - + // Test save to invalid path err = SaveConfigRaw("/nonexistent/path/config.json", testData) c.Assert(err, NotNil) @@ -74,7 +74,7 @@ func (s *ChecksumExtraSuite) TestPackagePoolStorageUnmarshalJSON(c *C) { "container": "aptly", "prefix": "pool" }` - + var pool PackagePoolStorage err := pool.UnmarshalJSON([]byte(azureJSON)) c.Assert(err, IsNil) @@ -82,33 +82,33 @@ func (s *ChecksumExtraSuite) TestPackagePoolStorageUnmarshalJSON(c *C) { c.Assert(pool.Local, IsNil) c.Assert(pool.Azure.AccountName, Equals, "myaccount") c.Assert(pool.Azure.Container, Equals, "aptly") - + // Test unmarshaling Local type localJSON := `{ "type": "local", "path": "/var/aptly/pool" }` - + var pool2 PackagePoolStorage err = pool2.UnmarshalJSON([]byte(localJSON)) c.Assert(err, IsNil) c.Assert(pool2.Local, NotNil) c.Assert(pool2.Azure, IsNil) c.Assert(pool2.Local.Path, Equals, "/var/aptly/pool") - + // Test unmarshaling unknown type unknownJSON := `{ "type": "unknown", "some": "data" }` - + var pool3 PackagePoolStorage err = pool3.UnmarshalJSON([]byte(unknownJSON)) c.Assert(err, NotNil) c.Assert(err.Error(), Equals, "unknown pool storage type: unknown") - + // Test invalid JSON var pool4 PackagePoolStorage err = pool4.UnmarshalJSON([]byte("invalid json")) c.Assert(err, NotNil) -} \ No newline at end of file +} diff --git a/utils/config.go b/utils/config.go index 4cfac039..ad26ef29 100644 --- a/utils/config.go +++ b/utils/config.go @@ -67,9 +67,11 @@ type ConfigStructure struct { // nolint: maligned // DBConfig structure type DBConfig struct { - Type string `json:"type" yaml:"type"` - DBPath string `json:"dbPath" yaml:"db_path"` - URL string `json:"url" yaml:"url"` + Type string `json:"type" yaml:"type"` + DBPath string `json:"dbPath" yaml:"db_path"` + URL string `json:"url" yaml:"url"` + Timeout string `json:"timeout" yaml:"timeout"` + WriteRetries int `json:"writeRetries" yaml:"write_retries"` } type LocalPoolStorage struct { @@ -185,6 +187,8 @@ type S3PublishRoot struct { ForceSigV2 bool `json:"forceSigV2" yaml:"force_sigv2"` ForceVirtualHostedStyle bool `json:"forceVirtualHostedStyle" yaml:"force_virtualhosted_style"` Debug bool `json:"debug" yaml:"debug"` + ConcurrentUploads int `json:"concurrentUploads" yaml:"concurrent_uploads"` + UploadQueueSize int `json:"uploadQueueSize" yaml:"upload_queue_size"` } // SwiftPublishRoot describes single OpenStack Swift publishing entry point @@ -324,3 +328,39 @@ func SaveConfigYAML(filename string, config *ConfigStructure) error { func (conf *ConfigStructure) GetRootDir() string { return strings.Replace(conf.RootDir, "~", os.Getenv("HOME"), 1) } + +// GetFileSystemPublishRoots returns a copy of FileSystemPublishRoots map to avoid concurrent access +func (conf *ConfigStructure) GetFileSystemPublishRoots() map[string]FileSystemPublishRoot { + result := make(map[string]FileSystemPublishRoot, len(conf.FileSystemPublishRoots)) + for k, v := range conf.FileSystemPublishRoots { + result[k] = v + } + return result +} + +// GetS3PublishRoots returns a copy of S3PublishRoots map to avoid concurrent access +func (conf *ConfigStructure) GetS3PublishRoots() map[string]S3PublishRoot { + result := make(map[string]S3PublishRoot, len(conf.S3PublishRoots)) + for k, v := range conf.S3PublishRoots { + result[k] = v + } + return result +} + +// GetSwiftPublishRoots returns a copy of SwiftPublishRoots map to avoid concurrent access +func (conf *ConfigStructure) GetSwiftPublishRoots() map[string]SwiftPublishRoot { + result := make(map[string]SwiftPublishRoot, len(conf.SwiftPublishRoots)) + for k, v := range conf.SwiftPublishRoots { + result[k] = v + } + return result +} + +// GetAzurePublishRoots returns a copy of AzurePublishRoots map to avoid concurrent access +func (conf *ConfigStructure) GetAzurePublishRoots() map[string]AzureEndpoint { + result := make(map[string]AzureEndpoint, len(conf.AzurePublishRoots)) + for k, v := range conf.AzurePublishRoots { + result[k] = v + } + return result +} diff --git a/utils/config_accessor_test.go b/utils/config_accessor_test.go index b670f0a7..006750b2 100644 --- a/utils/config_accessor_test.go +++ b/utils/config_accessor_test.go @@ -22,7 +22,7 @@ func (s *ConfigAccessorSuite) TestGetFileSystemPublishRoots(c *C) { "test1": {RootDir: "/tmp/test1", LinkMethod: "hardlink"}, "test2": {RootDir: "/tmp/test2", LinkMethod: "copy", VerifyMethod: "md5"}, } - + roots = config.GetFileSystemPublishRoots() c.Assert(len(roots), Equals, 2) c.Assert(roots["test1"].RootDir, Equals, "/tmp/test1") @@ -30,7 +30,7 @@ func (s *ConfigAccessorSuite) TestGetFileSystemPublishRoots(c *C) { c.Assert(roots["test2"].RootDir, Equals, "/tmp/test2") c.Assert(roots["test2"].LinkMethod, Equals, "copy") c.Assert(roots["test2"].VerifyMethod, Equals, "md5") - + // Verify it's a copy by modifying the returned map roots["test3"] = FileSystemPublishRoot{RootDir: "/tmp/test3"} c.Assert(len(config.FileSystemPublishRoots), Equals, 2) // Original unchanged @@ -54,19 +54,19 @@ func (s *ConfigAccessorSuite) TestGetS3PublishRoots(c *C) { ACL: "public-read", }, "bucket2": { - Region: "eu-west-1", - Bucket: "my-bucket-2", - Prefix: "aptly", - ACL: "private", + Region: "eu-west-1", + Bucket: "my-bucket-2", + Prefix: "aptly", + ACL: "private", }, } - + roots = config.GetS3PublishRoots() c.Assert(len(roots), Equals, 2) c.Assert(roots["bucket1"].Region, Equals, "us-east-1") c.Assert(roots["bucket1"].Bucket, Equals, "my-bucket-1") c.Assert(roots["bucket2"].Prefix, Equals, "aptly") - + // Verify it's a copy roots["bucket3"] = S3PublishRoot{Bucket: "new-bucket"} c.Assert(len(config.S3PublishRoots), Equals, 2) // Original unchanged @@ -90,12 +90,12 @@ func (s *ConfigAccessorSuite) TestGetSwiftPublishRoots(c *C) { AuthURL: "https://auth.example.com", }, } - + roots = config.GetSwiftPublishRoots() c.Assert(len(roots), Equals, 1) c.Assert(roots["container1"].Container, Equals, "aptly-container") c.Assert(roots["container1"].Prefix, Equals, "debian") - + // Verify it's a copy delete(roots, "container1") c.Assert(len(config.SwiftPublishRoots), Equals, 1) // Original unchanged @@ -124,16 +124,16 @@ func (s *ConfigAccessorSuite) TestGetAzurePublishRoots(c *C) { Container: "debian", }, } - + roots = config.GetAzurePublishRoots() c.Assert(len(roots), Equals, 2) c.Assert(roots["storage1"].AccountName, Equals, "myaccount") c.Assert(roots["storage1"].Container, Equals, "aptly") c.Assert(roots["storage2"].Container, Equals, "debian") - + // Verify it's a copy modified := roots["storage1"] modified.Container = "modified" roots["storage1"] = modified c.Assert(config.AzurePublishRoots["storage1"].Container, Equals, "aptly") // Original unchanged -} \ No newline at end of file +} diff --git a/utils/config_test.go b/utils/config_test.go index da6f927e..da3cd7c6 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -19,8 +19,8 @@ func (s *ConfigSuite) TestLoadConfig(c *C) { _, _ = f.WriteString(configFile) _ = f.Close() - // start with empty config - s.config = ConfigStructure{} + // start with empty config + s.config = ConfigStructure{} err := LoadConfig(configname, &s.config) c.Assert(err, IsNil) @@ -32,8 +32,8 @@ func (s *ConfigSuite) TestLoadConfig(c *C) { func (s *ConfigSuite) TestSaveConfig(c *C) { configname := filepath.Join(c.MkDir(), "aptly.json2") - // start with empty config - s.config = ConfigStructure{} + // start with empty config + s.config = ConfigStructure{} s.config.RootDir = "/tmp/aptly" s.config.DownloadConcurrency = 5 @@ -71,93 +71,93 @@ func (s *ConfigSuite) TestSaveConfig(c *C) { _, _ = f.Read(buf) c.Check(string(buf), Equals, ""+ - "{\n" + - " \"rootDir\": \"/tmp/aptly\",\n" + - " \"logLevel\": \"info\",\n" + - " \"logFormat\": \"json\",\n" + - " \"databaseOpenAttempts\": 5,\n" + - " \"architectures\": null,\n" + - " \"skipLegacyPool\": false,\n" + - " \"dependencyFollowSuggests\": false,\n" + - " \"dependencyFollowRecommends\": false,\n" + - " \"dependencyFollowAllVariants\": false,\n" + - " \"dependencyFollowSource\": false,\n" + - " \"dependencyVerboseResolve\": false,\n" + - " \"ppaDistributorID\": \"\",\n" + - " \"ppaCodename\": \"\",\n" + - " \"serveInAPIMode\": false,\n" + - " \"enableMetricsEndpoint\": false,\n" + - " \"enableSwaggerEndpoint\": false,\n" + - " \"AsyncAPI\": false,\n" + - " \"databaseBackend\": {\n" + - " \"type\": \"\",\n" + - " \"dbPath\": \"\",\n" + - " \"url\": \"\"\n" + - " },\n" + - " \"downloader\": \"\",\n" + - " \"downloadConcurrency\": 5,\n" + - " \"downloadSpeedLimit\": 0,\n" + - " \"downloadRetries\": 0,\n" + - " \"downloadSourcePackages\": false,\n" + - " \"gpgProvider\": \"gpg\",\n" + - " \"gpgDisableSign\": false,\n" + - " \"gpgDisableVerify\": false,\n" + - " \"skipContentsPublishing\": false,\n" + - " \"skipBz2Publishing\": false,\n" + - " \"FileSystemPublishEndpoints\": {\n" + - " \"test\": {\n" + - " \"rootDir\": \"/opt/aptly-publish\",\n" + - " \"linkMethod\": \"\",\n" + - " \"verifyMethod\": \"\"\n" + - " }\n" + - " },\n" + - " \"S3PublishEndpoints\": {\n" + - " \"test\": {\n" + - " \"region\": \"us-east-1\",\n" + - " \"bucket\": \"repo\",\n" + - " \"prefix\": \"\",\n" + - " \"acl\": \"\",\n" + - " \"awsAccessKeyID\": \"\",\n" + - " \"awsSecretAccessKey\": \"\",\n" + - " \"awsSessionToken\": \"\",\n" + - " \"endpoint\": \"\",\n" + - " \"storageClass\": \"\",\n" + - " \"encryptionMethod\": \"\",\n" + - " \"plusWorkaround\": false,\n" + - " \"disableMultiDel\": false,\n" + - " \"forceSigV2\": false,\n" + - " \"forceVirtualHostedStyle\": false,\n" + - " \"debug\": false\n" + - " }\n" + - " },\n" + - " \"SwiftPublishEndpoints\": {\n" + - " \"test\": {\n" + - " \"container\": \"repo\",\n" + - " \"prefix\": \"\",\n" + - " \"osname\": \"\",\n" + - " \"password\": \"\",\n" + - " \"tenant\": \"\",\n" + - " \"tenantid\": \"\",\n" + - " \"domain\": \"\",\n" + - " \"domainid\": \"\",\n" + - " \"tenantdomain\": \"\",\n" + - " \"tenantdomainid\": \"\",\n" + - " \"authurl\": \"\"\n" + - " }\n" + - " },\n" + - " \"AzurePublishEndpoints\": {\n" + - " \"test\": {\n" + - " \"container\": \"repo\",\n" + - " \"prefix\": \"\",\n" + - " \"accountName\": \"\",\n" + - " \"accountKey\": \"\",\n" + - " \"endpoint\": \"\"\n" + - " }\n" + - " },\n" + - " \"packagePoolStorage\": {\n" + - " \"type\": \"local\",\n" + - " \"path\": \"/tmp/aptly-pool\"\n" + - " }\n" + + "{\n"+ + " \"rootDir\": \"/tmp/aptly\",\n"+ + " \"logLevel\": \"info\",\n"+ + " \"logFormat\": \"json\",\n"+ + " \"databaseOpenAttempts\": 5,\n"+ + " \"architectures\": null,\n"+ + " \"skipLegacyPool\": false,\n"+ + " \"dependencyFollowSuggests\": false,\n"+ + " \"dependencyFollowRecommends\": false,\n"+ + " \"dependencyFollowAllVariants\": false,\n"+ + " \"dependencyFollowSource\": false,\n"+ + " \"dependencyVerboseResolve\": false,\n"+ + " \"ppaDistributorID\": \"\",\n"+ + " \"ppaCodename\": \"\",\n"+ + " \"serveInAPIMode\": false,\n"+ + " \"enableMetricsEndpoint\": false,\n"+ + " \"enableSwaggerEndpoint\": false,\n"+ + " \"AsyncAPI\": false,\n"+ + " \"databaseBackend\": {\n"+ + " \"type\": \"\",\n"+ + " \"dbPath\": \"\",\n"+ + " \"url\": \"\"\n"+ + " },\n"+ + " \"downloader\": \"\",\n"+ + " \"downloadConcurrency\": 5,\n"+ + " \"downloadSpeedLimit\": 0,\n"+ + " \"downloadRetries\": 0,\n"+ + " \"downloadSourcePackages\": false,\n"+ + " \"gpgProvider\": \"gpg\",\n"+ + " \"gpgDisableSign\": false,\n"+ + " \"gpgDisableVerify\": false,\n"+ + " \"skipContentsPublishing\": false,\n"+ + " \"skipBz2Publishing\": false,\n"+ + " \"FileSystemPublishEndpoints\": {\n"+ + " \"test\": {\n"+ + " \"rootDir\": \"/opt/aptly-publish\",\n"+ + " \"linkMethod\": \"\",\n"+ + " \"verifyMethod\": \"\"\n"+ + " }\n"+ + " },\n"+ + " \"S3PublishEndpoints\": {\n"+ + " \"test\": {\n"+ + " \"region\": \"us-east-1\",\n"+ + " \"bucket\": \"repo\",\n"+ + " \"prefix\": \"\",\n"+ + " \"acl\": \"\",\n"+ + " \"awsAccessKeyID\": \"\",\n"+ + " \"awsSecretAccessKey\": \"\",\n"+ + " \"awsSessionToken\": \"\",\n"+ + " \"endpoint\": \"\",\n"+ + " \"storageClass\": \"\",\n"+ + " \"encryptionMethod\": \"\",\n"+ + " \"plusWorkaround\": false,\n"+ + " \"disableMultiDel\": false,\n"+ + " \"forceSigV2\": false,\n"+ + " \"forceVirtualHostedStyle\": false,\n"+ + " \"debug\": false\n"+ + " }\n"+ + " },\n"+ + " \"SwiftPublishEndpoints\": {\n"+ + " \"test\": {\n"+ + " \"container\": \"repo\",\n"+ + " \"prefix\": \"\",\n"+ + " \"osname\": \"\",\n"+ + " \"password\": \"\",\n"+ + " \"tenant\": \"\",\n"+ + " \"tenantid\": \"\",\n"+ + " \"domain\": \"\",\n"+ + " \"domainid\": \"\",\n"+ + " \"tenantdomain\": \"\",\n"+ + " \"tenantdomainid\": \"\",\n"+ + " \"authurl\": \"\"\n"+ + " }\n"+ + " },\n"+ + " \"AzurePublishEndpoints\": {\n"+ + " \"test\": {\n"+ + " \"container\": \"repo\",\n"+ + " \"prefix\": \"\",\n"+ + " \"accountName\": \"\",\n"+ + " \"accountKey\": \"\",\n"+ + " \"endpoint\": \"\"\n"+ + " }\n"+ + " },\n"+ + " \"packagePoolStorage\": {\n"+ + " \"type\": \"local\",\n"+ + " \"path\": \"/tmp/aptly-pool\"\n"+ + " }\n"+ "}") } @@ -167,8 +167,8 @@ func (s *ConfigSuite) TestLoadYAMLConfig(c *C) { _, _ = f.WriteString(configFileYAML) _ = f.Close() - // start with empty config - s.config = ConfigStructure{} + // start with empty config + s.config = ConfigStructure{} err := LoadConfig(configname, &s.config) c.Assert(err, IsNil) @@ -183,8 +183,8 @@ func (s *ConfigSuite) TestLoadYAMLErrorConfig(c *C) { _, _ = f.WriteString(configFileYAMLError) _ = f.Close() - // start with empty config - s.config = ConfigStructure{} + // start with empty config + s.config = ConfigStructure{} err := LoadConfig(configname, &s.config) c.Assert(err.Error(), Equals, "invalid yaml (unknown pool storage type: invalid) or json (invalid character 'p' looking for beginning of value)") @@ -196,13 +196,13 @@ func (s *ConfigSuite) TestSaveYAMLConfig(c *C) { _, _ = f.WriteString(configFileYAML) _ = f.Close() - // start with empty config - s.config = ConfigStructure{} + // start with empty config + s.config = ConfigStructure{} err := LoadConfig(configname, &s.config) c.Assert(err, IsNil) - err = SaveConfigYAML(configname, &s.config) + err = SaveConfigYAML(configname, &s.config) c.Assert(err, IsNil) f, _ = os.Open(configname) @@ -218,17 +218,17 @@ func (s *ConfigSuite) TestSaveYAMLConfig(c *C) { } func (s *ConfigSuite) TestSaveYAML2Config(c *C) { - // start with empty config - s.config = ConfigStructure{} + // start with empty config + s.config = ConfigStructure{} s.config.PackagePoolStorage.Local = &LocalPoolStorage{"/tmp/aptly-pool"} - s.config.PackagePoolStorage.Azure = nil + s.config.PackagePoolStorage.Azure = nil configname := filepath.Join(c.MkDir(), "aptly.yaml4") - err := SaveConfigYAML(configname, &s.config) + err := SaveConfigYAML(configname, &s.config) c.Assert(err, IsNil) - f, _ := os.Open(configname) + f, _ := os.Open(configname) defer func() { _ = f.Close() }() @@ -237,44 +237,44 @@ func (s *ConfigSuite) TestSaveYAML2Config(c *C) { buf := make([]byte, st.Size()) _, _ = f.Read(buf) - c.Check(string(buf), Equals, "" + - "root_dir: \"\"\n" + - "log_level: \"\"\n" + - "log_format: \"\"\n" + - "database_open_attempts: 0\n" + - "architectures: []\n" + - "skip_legacy_pool: false\n" + - "dep_follow_suggests: false\n" + - "dep_follow_recommends: false\n" + - "dep_follow_all_variants: false\n" + - "dep_follow_source: false\n" + - "dep_verboseresolve: false\n" + - "ppa_distributor_id: \"\"\n" + - "ppa_codename: \"\"\n" + - "serve_in_api_mode: false\n" + - "enable_metrics_endpoint: false\n" + - "enable_swagger_endpoint: false\n" + - "async_api: false\n" + - "database_backend:\n" + - " type: \"\"\n" + - " db_path: \"\"\n" + - " url: \"\"\n" + - "downloader: \"\"\n" + - "download_concurrency: 0\n" + - "download_limit: 0\n" + - "download_retries: 0\n" + - "download_sourcepackages: false\n" + - "gpg_provider: \"\"\n" + - "gpg_disable_sign: false\n" + - "gpg_disable_verify: false\n" + - "skip_contents_publishing: false\n" + - "skip_bz2_publishing: false\n" + - "filesystem_publish_endpoints: {}\n" + - "s3_publish_endpoints: {}\n" + - "swift_publish_endpoints: {}\n" + - "azure_publish_endpoints: {}\n" + - "packagepool_storage:\n" + - " type: local\n" + + c.Check(string(buf), Equals, ""+ + "root_dir: \"\"\n"+ + "log_level: \"\"\n"+ + "log_format: \"\"\n"+ + "database_open_attempts: 0\n"+ + "architectures: []\n"+ + "skip_legacy_pool: false\n"+ + "dep_follow_suggests: false\n"+ + "dep_follow_recommends: false\n"+ + "dep_follow_all_variants: false\n"+ + "dep_follow_source: false\n"+ + "dep_verboseresolve: false\n"+ + "ppa_distributor_id: \"\"\n"+ + "ppa_codename: \"\"\n"+ + "serve_in_api_mode: false\n"+ + "enable_metrics_endpoint: false\n"+ + "enable_swagger_endpoint: false\n"+ + "async_api: false\n"+ + "database_backend:\n"+ + " type: \"\"\n"+ + " db_path: \"\"\n"+ + " url: \"\"\n"+ + "downloader: \"\"\n"+ + "download_concurrency: 0\n"+ + "download_limit: 0\n"+ + "download_retries: 0\n"+ + "download_sourcepackages: false\n"+ + "gpg_provider: \"\"\n"+ + "gpg_disable_sign: false\n"+ + "gpg_disable_verify: false\n"+ + "skip_contents_publishing: false\n"+ + "skip_bz2_publishing: false\n"+ + "filesystem_publish_endpoints: {}\n"+ + "s3_publish_endpoints: {}\n"+ + "swift_publish_endpoints: {}\n"+ + "azure_publish_endpoints: {}\n"+ + "packagepool_storage:\n"+ + " type: local\n"+ " path: /tmp/aptly-pool\n") } @@ -283,8 +283,8 @@ func (s *ConfigSuite) TestLoadEmptyConfig(c *C) { f, _ := os.Create(configname) _ = f.Close() - // start with empty config - s.config = ConfigStructure{} + // start with empty config + s.config = ConfigStructure{} err := LoadConfig(configname, &s.config) c.Assert(err.Error(), Equals, "invalid yaml (EOF) or json (EOF)") diff --git a/utils/logging.go b/utils/logging.go index 6eceae14..fef39ccb 100644 --- a/utils/logging.go +++ b/utils/logging.go @@ -46,6 +46,10 @@ func SetupDefaultLogger(levelStr string) { } func GetLogLevelOrDebug(levelStr string) zerolog.Level { + if levelStr == "" { + return zerolog.DebugLevel + } + levelStr = strings.ToLower(levelStr) if levelStr == "warning" { levelStr = "warn" diff --git a/utils/logging_test.go b/utils/logging_test.go index 570becc6..faa2f7be 100644 --- a/utils/logging_test.go +++ b/utils/logging_test.go @@ -37,16 +37,16 @@ func (s *LoggingSuite) TestLogWriter(c *C) { var buf bytes.Buffer logger := zerolog.New(&buf) logWriter := LogWriter{Logger: logger} - + // Test Write method testData := []byte("test log message") n, err := logWriter.Write(testData) c.Check(err, IsNil) c.Check(n, Equals, len(testData)) - + // Check that something was written to the buffer c.Check(buf.Len() > 0, Equals, true) - + // Check that the output contains the message output := buf.String() c.Check(strings.Contains(output, "test log message"), Equals, true) @@ -57,7 +57,7 @@ func (s *LoggingSuite) TestLogWriterEmpty(c *C) { var buf bytes.Buffer logger := zerolog.New(&buf) logWriter := LogWriter{Logger: logger} - + n, err := logWriter.Write([]byte{}) c.Check(err, IsNil) c.Check(n, Equals, 0) @@ -66,21 +66,21 @@ func (s *LoggingSuite) TestLogWriterEmpty(c *C) { func (s *LoggingSuite) TestSetupJSONLogger(c *C) { // Test SetupJSONLogger function var buf bytes.Buffer - + // Test with different log levels testLevels := []string{"debug", "info", "warn", "error"} - + for _, level := range testLevels { buf.Reset() SetupJSONLogger(level, &buf) - + // Check that message and level field names are set correctly c.Check(zerolog.MessageFieldName, Equals, "message") c.Check(zerolog.LevelFieldName, Equals, "level") - + // Test logging something log.Info().Msg("test message") - + // Check that JSON was written output := buf.String() if len(output) > 0 { @@ -93,14 +93,14 @@ func (s *LoggingSuite) TestSetupJSONLogger(c *C) { func (s *LoggingSuite) TestSetupDefaultLogger(c *C) { // Test SetupDefaultLogger function testLevels := []string{"debug", "info", "warn", "error"} - + for _, level := range testLevels { SetupDefaultLogger(level) - + // Check that message and level field names are set correctly c.Check(zerolog.MessageFieldName, Equals, "message") c.Check(zerolog.LevelFieldName, Equals, "level") - + // Check that logger is configured (hard to test output since it goes to stderr) c.Check(log.Logger, NotNil) } @@ -126,7 +126,7 @@ func (s *LoggingSuite) TestGetLogLevelOrDebugValid(c *C) { "trace": zerolog.TraceLevel, "TRACE": zerolog.TraceLevel, } - + for levelStr, expectedLevel := range testCases { result := GetLogLevelOrDebug(levelStr) c.Check(result, Equals, expectedLevel, Commentf("Failed for level: %s", levelStr)) @@ -142,20 +142,20 @@ func (s *LoggingSuite) TestGetLogLevelOrDebugInvalid(c *C) { "verbose", "critical", } - + // Capture log output to verify warning is logged var buf bytes.Buffer originalLogger := log.Logger log.Logger = zerolog.New(&buf).Level(zerolog.TraceLevel) defer func() { log.Logger = originalLogger }() - + for _, levelStr := range invalidLevels { buf.Reset() result := GetLogLevelOrDebug(levelStr) - + // Should default to debug level c.Check(result, Equals, zerolog.DebugLevel, Commentf("Failed for invalid level: %s", levelStr)) - + // Should log a warning (if levelStr is not empty) if levelStr != "" { output := buf.String() @@ -168,18 +168,18 @@ func (s *LoggingSuite) TestGetLogLevelOrDebugInvalid(c *C) { func (s *LoggingSuite) TestTimestampHook(c *C) { // Test timestampHook struct and Run method hook := ×tampHook{} - + var buf bytes.Buffer logger := zerolog.New(&buf).Hook(hook) - + // Log a message logger.Info().Msg("test message with timestamp") - + // Check that output contains timestamp output := buf.String() c.Check(strings.Contains(output, "time"), Equals, true) c.Check(strings.Contains(output, "test message with timestamp"), Equals, true) - + // Check that timestamp is in RFC3339 format (contains T and Z or +/- timezone) c.Check(strings.Contains(output, "T") || strings.Contains(output, ":"), Equals, true) } @@ -187,10 +187,10 @@ func (s *LoggingSuite) TestTimestampHook(c *C) { func (s *LoggingSuite) TestTimestampHookMultipleLevels(c *C) { // Test timestampHook with different log levels hook := ×tampHook{} - + var buf bytes.Buffer logger := zerolog.New(&buf).Hook(hook) - + // Test different log levels testCases := []struct { level zerolog.Level @@ -201,11 +201,11 @@ func (s *LoggingSuite) TestTimestampHookMultipleLevels(c *C) { {zerolog.WarnLevel, "warn message"}, {zerolog.ErrorLevel, "error message"}, } - + for _, tc := range testCases { buf.Reset() logger.WithLevel(tc.level).Msg(tc.message) - + output := buf.String() if len(output) > 0 { c.Check(strings.Contains(output, "time"), Equals, true, Commentf("No timestamp for level: %v", tc.level)) @@ -218,16 +218,16 @@ func (s *LoggingSuite) TestLogLevelCaseInsensitive(c *C) { // Test that log level parsing is case insensitive mixedCaseLevels := []string{ "Debug", "deBuG", "DeBuG", - "Info", "inFo", "InFo", + "Info", "inFo", "InFo", "Warn", "waRn", "WaRn", "Error", "erRor", "ErRoR", } - + for _, levelStr := range mixedCaseLevels { result := GetLogLevelOrDebug(levelStr) // Should not default to debug (unless it's actually debug) if strings.ToLower(levelStr) != "debug" { - c.Check(result != zerolog.DebugLevel || strings.ToLower(levelStr) == "debug", Equals, true, + c.Check(result != zerolog.DebugLevel || strings.ToLower(levelStr) == "debug", Equals, true, Commentf("Case insensitive parsing failed for: %s", levelStr)) } } @@ -236,19 +236,19 @@ func (s *LoggingSuite) TestLogLevelCaseInsensitive(c *C) { func (s *LoggingSuite) TestSetupLoggersIntegration(c *C) { // Test integration between setup functions and actual logging var buf bytes.Buffer - + // Test JSON logger SetupJSONLogger("info", &buf) log.Info().Str("key", "value").Msg("json test message") - + jsonOutput := buf.String() if len(jsonOutput) > 0 { c.Check(strings.Contains(jsonOutput, "json test message"), Equals, true) c.Check(strings.Contains(jsonOutput, "key"), Equals, true) c.Check(strings.Contains(jsonOutput, "value"), Equals, true) } - + // Test default logger (output goes to stderr, so we can't easily capture it) SetupDefaultLogger("warn") c.Check(log.Logger, NotNil) -} \ No newline at end of file +} diff --git a/utils/sanitize_test.go b/utils/sanitize_test.go index 9b9b8532..cb7c40d5 100644 --- a/utils/sanitize_test.go +++ b/utils/sanitize_test.go @@ -2,7 +2,7 @@ package utils import ( "strings" - + . "gopkg.in/check.v1" ) @@ -122,4 +122,4 @@ func (s *SanitizeSuite) TestSanitizePathSecurity(c *C) { c.Check(result[0] != '/', Equals, true, Commentf("Absolute path for: %s, got: %s", tc.desc, result)) } } -} \ No newline at end of file +} diff --git a/utils/utils_test.go b/utils/utils_test.go index 17c46f13..4c304e53 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -34,7 +34,7 @@ func (s *UtilsSuite) TestDirIsAccessibleNotExist(c *C) { func (s *UtilsSuite) TestDirIsAccessibleNotAccessible(c *C) { accessible := DirIsAccessible(s.tempfile.Name()) if accessible == nil { - c.Fatalf("Test dir should not be accessible: %s", s.tempfile.Name()) - } + c.Fatalf("Test dir should not be accessible: %s", s.tempfile.Name()) + } c.Check(accessible.Error(), Equals, fmt.Errorf("'%s' is inaccessible, check access rights", s.tempfile.Name()).Error()) }