name: PMG E2E Tests on: pull_request: branches: - main paths-ignore: - "**/*.md" - "docs/**" push: branches: - main paths-ignore: - "**/*.md" - "docs/**" workflow_dispatch: # PRs share a concurrency group (cancel/serialize); Pushes use unique groups to avoid cancellation/queuing concurrency: group: ${{ github.workflow }}-${{ (github.event_name == 'pull_request' && github.ref) || github.run_id }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} permissions: contents: read jobs: e2e-test: name: PMG E2E Tests - All Package Managers runs-on: ${{ matrix.os }} timeout-minutes: 20 strategy: fail-fast: false matrix: os: [ubuntu-latest] defaults: run: shell: bash steps: - name: Checkout Source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Setup Go uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5 with: go-version-file: go.mod - name: Setup Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 20 check-latest: true - name: Setup PNPM uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4 with: version: 10 - name: Setup Bun uses: oven-sh/setup-bun@735343b667d3e6f658f44d0eca948eb6282f2b76 # v2 with: bun-version: latest - name: Setup Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: "3.11" - name: Setup uv uses: astral-sh/setup-uv@caf0cab7a618c569241d31dcd442f54681755d39 - name: Install Poetry uses: snok/install-poetry@76e04a911780d5b312d89783f7b1cd627778900a - name: Build PMG run: make - name: Add pmg to PATH run: echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH - name: Setup PMG run: pmg setup install - name: Test PMG - Config File is Created run: | test -f $HOME/.config/safedep/pmg/config.yml - name: Test pmg.rc File is Created run: | test -f $HOME/.pmg.rc - name: Test NPM - Single Package & Manifest run: | echo "Testing NPM single package installation..." mkdir npm-test && cd npm-test pmg --proxy-mode=false npm init -y pmg --proxy-mode=false npm install express@5.2.1 pmg --proxy-mode=false npm install lodash@4.17.21 # Verification: npm added packages present and manifest updated test -d node_modules/express test -d node_modules/lodash grep -q '"express"' package.json grep -q '"lodash"' package.json echo "Testing NPM manifest installation..." rm -rf node_modules package-lock.json pmg --proxy-mode=false npm install # Verification: npm lockfile and installed modules exist after manifest install test -f package-lock.json test -d node_modules/express test -d node_modules/lodash cd .. && rm -rf npm-test - name: Test NPM - Proxy Mode run: | echo "Testing NPM with proxy-based interception..." mkdir npm-proxy-test && cd npm-proxy-test pmg npm init -y echo "Testing proxy mode single package installation..." pmg npm install express@5.2.1 pmg npm install lodash@4.17.21 # Verification: packages installed via proxy mode test -d node_modules/express test -d node_modules/lodash grep -q '"express"' package.json grep -q '"lodash"' package.json echo "Testing proxy mode manifest installation..." rm -rf node_modules package-lock.json pmg npm install # Verification: manifest install via proxy mode works test -f package-lock.json test -d node_modules/express test -d node_modules/lodash echo "Testing proxy mode with scoped package..." pmg npm install @types/node@18.0.0 # Verification: scoped package installed via proxy test -d node_modules/@types test -d node_modules/@types/node grep -q '"@types/node"' package.json echo "Testing proxy mode dry-run (should not create files)..." rm -rf node_modules package-lock.json pmg --dry-run npm install # Verification: dry-run doesn't create files even in proxy mode test ! -d node_modules test ! -f package-lock.json cd .. && rm -rf npm-proxy-test - name: Test PyPI - Proxy Mode run: | echo "Testing PyPI package managers with proxy-based interception..." mkdir pypi-proxy-test && cd pypi-proxy-test echo "Setting up Python virtual environment for pip and pip3 tests..." python -m venv venv && source venv/bin/activate python --version pip --version echo "Testing pip single package installation via proxy mode..." pmg pip install requests==2.32.4 pmg pip install numpy==2.3.5 # Verification: packages installed and importable python -c "import requests, numpy; print('pip ok:', requests.__version__, numpy.__version__)" echo "Testing pip manifest installation via proxy mode..." pmg pip freeze > requirements.txt pmg pip uninstall -y requests numpy pmg pip install -r <(grep -v '^PMG:' requirements.txt) python -c "import requests, numpy; print('pip manifest ok:', requests.__version__, numpy.__version__)" deactivate echo "Setting up Python virtual environment for pip3 tests..." python -m venv venv3 && source venv3/bin/activate python --version pip3 --version echo "Testing pip3 single package installation via proxy mode..." pmg pip3 install requests==2.32.4 pmg pip3 install numpy==2.3.5 # Verification: packages installed and importable python -c "import requests, numpy; print('pip3 ok:', requests.__version__, numpy.__version__)" echo "Testing pip3 manifest installation via proxy mode..." pmg pip3 freeze > requirements3.txt pmg pip3 uninstall -y requests numpy pmg pip3 install -r <(grep -v '^PMG:' requirements3.txt) python -c "import requests, numpy; print('pip3 manifest ok:', requests.__version__, numpy.__version__)" deactivate echo "Testing uv add and uv pip install via proxy mode..." mkdir uv-proxy && cd uv-proxy pmg uv init --no-readme pmg uv add requests==2.32.4 pmg uv add numpy==2.3.5 # Verification: pyproject.toml lists expected dependencies test -f pyproject.toml grep -q 'requests' pyproject.toml grep -q 'numpy' pyproject.toml echo "Sync environment and verify installations..." pmg uv sync pmg uv pip show requests >/dev/null pmg uv pip show numpy >/dev/null echo "Testing uv pip install from requirements via proxy mode..." pmg uv pip freeze > requirements.txt pmg uv pip install -r <(grep -v '^PMG:' requirements.txt) pmg uv pip show requests >/dev/null pmg uv pip show numpy >/dev/null cd .. cd .. && rm -rf pypi-proxy-test - name: Test PNPM - Single Package & Manifest run: | echo "Testing PNPM single package installation..." mkdir pnpm-test && cd pnpm-test pmg --proxy-mode=false pnpm init pmg --proxy-mode=false pnpm add express@5.2.1 pmg --proxy-mode=false pnpm add lodash@4.17.21 # Verification: pnpm packages installed and lockfile created test -d node_modules/express test -d node_modules/lodash test -f pnpm-lock.yaml echo "Testing PNPM manifest installation..." rm -rf node_modules pnpm-lock.yaml pmg --proxy-mode=false pnpm install # Verification: pnpm lockfile and modules exist after manifest install test -f pnpm-lock.yaml test -d node_modules/express test -d node_modules/lodash cd .. && rm -rf pnpm-test - name: Test Bun - Single Package & Manifest run: | echo "Testing Bun single package installation..." mkdir bun-test && cd bun-test pmg --proxy-mode=false bun init -y pmg --proxy-mode=false bun add express@5.2.1 pmg --proxy-mode=false bun add lodash@4.17.21 # Verification: bun packages installed and lockfile created test -d node_modules/express test -d node_modules/lodash test -f bun.lock echo "Testing Bun manifest installation..." rm -rf node_modules bun.lock pmg --proxy-mode=false bun install # Verification: bun lockfile and modules exist after manifest install test -f bun.lock test -d node_modules/express test -d node_modules/lodash cd .. && rm -rf bun-test - name: Test Yarn - Single Package & Manifest run: | echo "Testing Yarn single package installation..." export YARN_ENABLE_HARDENED_MODE=0 npm install -g yarn@1.22.22 yarn --version mkdir yarn-test && cd yarn-test pmg --proxy-mode=false yarn init -y pmg --proxy-mode=false yarn add express@5.2.1 pmg --proxy-mode=false yarn add lodash@4.17.21 # Verification: yarn packages installed and lockfile created test -d node_modules/express test -d node_modules/lodash test -f yarn.lock echo "Testing Yarn manifest installation..." rm -rf node_modules yarn.lock pmg --proxy-mode=false yarn install # Verification: yarn lockfile and modules exist after manifest install test -f yarn.lock test -d node_modules/express test -d node_modules/lodash cd .. && rm -rf yarn-test - name: Test NPX - Package Execution run: | echo "Testing NPX package execution..." mkdir npx-test && cd npx-test echo "Testing npx with a simple package..." pmg --proxy-mode=false npx cowsay@1.6.0 "Hello from pmg npx" | tee npx-output.txt # Verification: cowsay output contains our message grep -q "Hello from pmg npx" npx-output.txt echo "Testing npx with --package flag..." pmg --proxy-mode=false npx --package cowsay@1.6.0 -- cowsay "Hello with package flag" | tee npx-pkg-output.txt # Verification: package flag execution produces expected output grep -q "Hello with package flag" npx-pkg-output.txt echo "Testing npx dry-run mode..." pmg --proxy-mode=false --dry-run npx cowsay@1.6.0 "This should not execute" | tee npx-dry-output.txt # Verification: dry-run should NOT produce cowsay ASCII art (cow face ^__^ should not appear) ! grep -q '\^__\^' npx-dry-output.txt cd .. && rm -rf npx-test - name: Test PNPX - Package Execution run: | echo "Testing PNPX package execution..." mkdir pnpx-test && cd pnpx-test echo "Testing pnpx with a simple package..." pmg --proxy-mode=false pnpx cowsay@1.6.0 "Hello from pmg pnpx" | tee pnpx-output.txt # Verification: cowsay output contains our message grep -q "Hello from pmg pnpx" pnpx-output.txt echo "Testing pnpx with --package flag..." pmg --proxy-mode=false pnpx --package cowsay@1.6.0 -- cowsay "Hello with package flag" | tee pnpx-pkg-output.txt # Verification: package flag execution produces expected output grep -q "Hello with package flag" pnpx-pkg-output.txt echo "Testing pnpx dry-run mode..." pmg --proxy-mode=false --dry-run pnpx cowsay@1.6.0 "This should not execute" | tee pnpx-dry-output.txt # Verification: dry-run should NOT produce cowsay ASCII art (cow face ^__^ should not appear) ! grep -q '\^__\^' pnpx-dry-output.txt cd .. && rm -rf pnpx-test - name: Test Pip - Single Package & Manifest run: | echo "Testing Pip single package installation..." mkdir pip-test && cd pip-test python -m venv venv && source venv/bin/activate pmg --proxy-mode=false pip install requests==2.32.4 pmg --proxy-mode=false pip install numpy==2.3.5 pmg --proxy-mode=false pip freeze > requirements.txt # Verification: requirements.txt contains expected packages test -s requirements.txt grep -E '^requests==' requirements.txt grep -E '^numpy==' requirements.txt echo "Testing Pip manifest installation..." pmg --proxy-mode=false pip uninstall -y requests numpy pmg --proxy-mode=false pip install -r requirements.txt # Verification: imported packages are available in the environment python -c "import requests, numpy; print(requests.__version__); print(numpy.__version__)" deactivate cd .. && rm -rf pip-test - name: Test Pip3 - Single Package & Manifest run: | echo "Testing Pip3 single package installation..." mkdir pip3-test && cd pip3-test python -m venv venv && source venv/bin/activate pmg --proxy-mode=false pip3 install requests==2.32.4 pmg --proxy-mode=false pip3 install numpy==2.3.5 pmg --proxy-mode=false pip3 freeze > requirements.txt # Verification: requirements.txt contains expected packages test -s requirements.txt grep -E '^requests==' requirements.txt grep -E '^numpy==' requirements.txt echo "Testing Pip3 manifest installation..." pmg --proxy-mode=false pip3 uninstall -y requests numpy pmg --proxy-mode=false pip3 install -r requirements.txt # Verification: imported packages are available in the environment python -c "import requests, numpy; print(requests.__version__); print(numpy.__version__)" deactivate cd .. && rm -rf pip3-test - name: Test UV - Single Package & Manifest run: | echo "Testing UV single package installation..." mkdir uv-test && cd uv-test pmg --proxy-mode=false uv init --no-readme pmg --proxy-mode=false uv add requests==2.32.4 pmg --proxy-mode=false uv add numpy==2.3.5 # Verification: pyproject.toml lists expected dependencies test -f pyproject.toml grep -q 'requests' pyproject.toml grep -q 'numpy' pyproject.toml echo "Testing UV manifest installation..." rm -rf .venv uv.lock pmg --proxy-mode=false uv sync # Verification: uv lockfile and virtualenv created; packages present test -d .venv test -f uv.lock pmg --proxy-mode=false uv pip show requests >/dev/null pmg --proxy-mode=false uv pip show numpy >/dev/null echo "Testing UV pip commands..." pmg --proxy-mode=false uv pip freeze > requirements.txt pmg --proxy-mode=false uv pip install -r requirements.txt pmg --proxy-mode=false uv pip sync requirements.txt # Verification: uv pip can show installed packages after requirements sync pmg --proxy-mode=false uv pip show requests >/dev/null pmg --proxy-mode=false uv pip show numpy >/dev/null cd .. && rm -rf uv-test - name: Test Poetry - Single Package & Manifest run: | echo "Testing Poetry single package installation..." mkdir poetry-test && cd poetry-test pmg --proxy-mode=false poetry init --name poetry-test --no-interaction --quiet pmg --proxy-mode=false poetry add requests==2.32.4 pmg --proxy-mode=false poetry add numpy==2.3.5 # Verification: pyproject.toml dependencies updated test -f pyproject.toml grep -q 'requests' pyproject.toml grep -q 'numpy' pyproject.toml echo "Testing Poetry manifest installation..." rm -rf .venv poetry.lock pmg --proxy-mode=false poetry install --no-root cd .. && rm -rf poetry-test - name: Test Malicious Package Detection run: | echo "Testing malicious package detection..." mkdir malicious-test && cd malicious-test pmg --proxy-mode=false npm init -y ! pmg --proxy-mode=false npm install nyc-config@10.0.0 || echo "Malicious package correctly blocked" cd .. && rm -rf malicious-test - name: Test safedep-test-pkg is Blocked using Proxy mode run: | echo "Testing that safedep-test-pkg is blocked..." mkdir safedep-test-pkg-test && cd safedep-test-pkg-test pmg npm init -y # Attempt to install safedep-test-pkg - should fail if pmg npm --no-cache --prefer-online i safedep-test-pkg@0.1.3; then echo "ERROR: safedep-test-pkg was not blocked!" exit 1 else echo "SUCCESS: safedep-test-pkg correctly blocked" fi # Verify package is not installed locally if [ -d "node_modules/safedep-test-pkg" ]; then echo "ERROR: safedep-test-pkg found in node_modules!" exit 1 else echo "SUCCESS: safedep-test-pkg not present in node_modules" fi cd .. && rm -rf safedep-test-pkg-test - name: Test PMG Modes run: | echo "Testing different PMG modes..." mkdir pmg-modes-test && cd pmg-modes-test pmg npm init -y # Mode: --dry-run should not create node_modules or lockfiles pmg --proxy-mode=false --dry-run npm install express # Verification: no files created during dry-run test ! -d node_modules test ! -f package-lock.json # Mode: --silent should install without noisy output pmg --proxy-mode=false --silent npm install express # Verification: package installed test -d node_modules/express # Clean and test --verbose installation rm -rf node_modules package-lock.json pmg --proxy-mode=false --verbose npm install express # Verification: package installed test -d node_modules/express # Clean and test --debug with log output rm -rf node_modules package-lock.json pmg --proxy-mode=false --debug --log debug.json npm install express # Verification: debug log written test -f debug.json # Mode: --paranoid may require cloud credentials; run non-blocking with dry-run pmg --proxy-mode=false --paranoid --dry-run npm install express || true cd .. && rm -rf pmg-modes-test sandbox-e2e-macos: name: Sandbox E2E - macOS runs-on: macos-latest timeout-minutes: 10 steps: - name: Checkout Source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Setup Go uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5 with: go-version-file: go.mod - name: Setup Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 20 check-latest: true - name: Setup PNPM uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4 with: version: 10 - name: Build PMG run: make - name: Add pmg to PATH run: echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH - name: Setup PMG run: pmg setup install - name: Create Test Directories for Sandbox Permissions Tests run: mkdir -p ~/.aws ~/.gcloud ~/.kube ~/.ssh - name: Create Test Files for Sandbox Permissions Tests run: | touch ~/.aws/credentials touch ~/.gcloud/credentials.json touch ~/.kube/config touch ~/.ssh/id_rsa - name: Run Sandbox E2E Test run: pmg --sandbox --sandbox-enforce npm exec -- node test/sandbox-e2e.js - name: Run Package Manager E2E Test run: pmg --sandbox --sandbox-enforce npm exec -- node test/pm-e2e.js sandbox-e2e-linux: name: Sandbox E2E - Linux (Bubblewrap) runs-on: ubuntu-latest timeout-minutes: 10 defaults: run: shell: bash steps: - name: Checkout Source uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4 - name: Setup Go uses: actions/setup-go@0aaccfd150d50ccaeb58ebd88d36e91967a5f35b # v5 with: go-version-file: go.mod - name: Setup Node.js uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 20 check-latest: true - name: Setup PNPM uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4 with: version: 10 - name: Install Bubblewrap run: sudo apt-get update && sudo apt-get install -y bubblewrap - name: Verify Bubblewrap Installation run: bwrap --version - name: Build PMG run: make - name: Add pmg to PATH run: echo "$GITHUB_WORKSPACE/bin" >> $GITHUB_PATH - name: Setup PMG run: pmg setup install - name: Create Test Directories for Sandbox Permissions Tests run: mkdir -p ~/.aws ~/.gcloud ~/.kube ~/.ssh ~/.gnupg ~/.docker - name: Create Test Files for Sandbox Permissions Tests run: | touch ~/.aws/credentials touch ~/.gcloud/credentials.json touch ~/.kube/config touch ~/.ssh/id_rsa touch ~/.gnupg/pubring.kbx touch ~/.docker/config.json - name: Disable AppArmor for Bubblewrap run: | sudo systemctl stop apparmor sudo systemctl disable apparmor sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - name: Run Sandbox E2E Test run: pmg --sandbox --sandbox-enforce --sandbox-profile npm-restrictive npm exec -- node test/sandbox-e2e.js - name: Run Package Manager E2E Test run: pmg --sandbox --sandbox-enforce --sandbox-profile npm-restrictive npm exec -- node test/pm-e2e.js