Compare commits

..

1 Commits

Author SHA1 Message Date
github-actions[bot] dc0ab79732 Update configuration files 2026-07-12 04:15:04 +00:00
75 changed files with 104232 additions and 114538 deletions
+6
View File
@@ -0,0 +1,6 @@
# Ignore list
/*
# Do not ignore these folders:
!__tests__/
!src/
+51
View File
@@ -0,0 +1,51 @@
// This is a reusable configuration file copied from https://github.com/actions/reusable-workflows/tree/main/reusable-configurations. Please don't make changes to this file as it's the subject of an automatic update.
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:eslint-plugin-jest/recommended',
'eslint-config-prettier'
],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint', 'eslint-plugin-node', 'eslint-plugin-jest'],
rules: {
'@typescript-eslint/no-require-imports': 'error',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-ignore': 'allow-with-description'
}
],
'no-console': 'error',
'yoda': 'error',
'prefer-const': [
'error',
{
destructuring: 'all'
}
],
'no-control-regex': 'off',
'no-constant-condition': ['error', {checkLoops: false}],
'node/no-extraneous-import': 'error'
},
overrides: [
{
files: ['**/*{test,spec}.ts'],
rules: {
'@typescript-eslint/no-unused-vars': 'off',
'jest/no-standalone-expect': 'off',
'jest/no-conditional-expect': 'off',
'no-console': 'off',
}
}
],
env: {
node: true,
es6: true,
'jest/globals': true
}
};
+88 -53
View File
@@ -23,18 +23,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04,
ubuntu-latest, ubuntu-latest,
ubuntu-22.04,
ubuntu-24.04-arm, ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest, windows-latest,
windows-11-arm, macos-latest,
macos-15-intel, macos-15-intel
macos-latest
] ]
python-version: [3.13.14t, 3.14.6t] python-version: [3.13.0t, 3.13.1t, 3.13.2t]
exclude:
- os: windows-11-arm
python-version: 3.13.14t
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Setup Python - name: Setup Python
@@ -53,18 +50,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04,
ubuntu-latest, ubuntu-latest,
ubuntu-24.04-arm, ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest, windows-latest,
windows-11-arm, macos-latest,
macos-15-intel, macos-15-intel
macos-latest
] ]
python-version: [3.13.14t, 3.14.6t] python-version: [3.13.1t, 3.13.2t, 3.13.5t]
exclude:
- os: windows-11-arm
python-version: 3.13.14t
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Setup Python - name: Setup Python
@@ -96,15 +90,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04,
ubuntu-latest, ubuntu-latest,
ubuntu-22.04,
ubuntu-24.04-arm, ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest, windows-latest,
windows-11-arm, macos-latest,
macos-15-intel, macos-15-intel
macos-latest
] ]
python-version: [3.13.14, 3.14.6] python-version: [3.13.0, 3.13.1, 3.13.2]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Install poetry - name: Install poetry
@@ -128,18 +122,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04,
ubuntu-latest, ubuntu-latest,
ubuntu-22.04,
ubuntu-24.04-arm, ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest, windows-latest,
windows-11-arm, macos-latest,
macos-15-intel, macos-15-intel
macos-latest
] ]
python-version: [3.13.14t, 3.14.6t] python-version: [3.13.0t, 3.13.1t, 3.13.2t]
exclude:
- os: windows-11-arm
python-version: 3.13.14t
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Setup Python - name: Setup Python
@@ -159,18 +150,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04,
ubuntu-latest, ubuntu-latest,
ubuntu-22.04,
ubuntu-24.04-arm, ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest, windows-latest,
windows-11-arm, macos-latest,
macos-15-intel, macos-15-intel
macos-latest
] ]
python-version: [3.13.14t, 3.14.6t] python-version: [3.13.1t, 3.13.2t, 3.13.5t]
exclude:
- os: windows-11-arm
python-version: 3.13.14t
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Setup Python - name: Setup Python
@@ -203,18 +191,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04,
ubuntu-latest, ubuntu-latest,
ubuntu-22.04,
ubuntu-24.04-arm, ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest, windows-latest,
windows-11-arm, macos-latest,
macos-15-intel, macos-15-intel
macos-latest
] ]
python-version: [3.13.14t, 3.14.6t] python-version: [3.13.0t, 3.13.1t, 3.13.2t]
exclude:
- os: windows-11-arm
python-version: 3.13.14t
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Setup Python - name: Setup Python
@@ -234,18 +219,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04,
ubuntu-latest, ubuntu-latest,
ubuntu-22.04,
ubuntu-24.04-arm, ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest, windows-latest,
windows-11-arm, macos-latest,
macos-15-intel, macos-15-intel
macos-latest
] ]
python-version: [3.13.14t, 3.14.6t] python-version: [3.13.0t, 3.13.1t, 3.13.2t]
exclude:
- os: windows-11-arm
python-version: 3.13.14t
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Setup Python - name: Setup Python
@@ -257,3 +239,56 @@ jobs:
pip-version: '25.0.1' pip-version: '25.0.1'
- name: Install dependencies - name: Install dependencies
run: pip install numpy pandas requests run: pip install numpy pandas requests
python-pip-dependencies-caching-with-pip-install:
name: Test pip (Python ${{ matrix.python-version}}, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
[
ubuntu-latest,
ubuntu-22.04,
ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
]
python-version: [3.13.0t, 3.13.1t, 3.13.2t]
steps:
- uses: actions/checkout@v6
- name: Setup Python
uses: ./
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
pip-install: numpy pandas requests
python-pip-dependencies-caching-path-with-pip-install:
name: Test pip (Python ${{ matrix.python-version}}, ${{ matrix.os }}, caching path)
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
[
ubuntu-latest,
ubuntu-22.04,
ubuntu-24.04-arm,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
]
python-version: [3.13.0t, 3.13.1t, 3.13.2t]
steps:
- uses: actions/checkout@v6
- name: Setup Python
uses: ./
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: __tests__/data/requirements.txt
pip-install: numpy pandas requests
+129 -60
View File
@@ -23,23 +23,30 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04,
ubuntu-latest, ubuntu-latest,
ubuntu-24.04-arm, ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest, windows-latest,
windows-11-arm, macos-latest,
macos-15-intel, macos-15-intel
macos-latest
] ]
python-version: python-version:
['3.10', '3.11', 'pypy-3.11-v7.x', '3.12', '3.13', '3.14'] [
'3.9',
'pypy-3.9-v7.x',
'3.10',
'pypy-3.10-v7.x',
'3.11',
'pypy-3.11-v7.x',
'3.12',
'3.13'
]
exclude: exclude:
- os: windows-latest - os: windows-latest
python-version: pypy-3.11-v7.x python-version: pypy-3.11-v7.x
- os: windows-11-arm - os: macos-latest
python-version: pypy-3.11-v7.x python-version: pypy-3.9-v7.x
- os: windows-11-arm
python-version: '3.10'
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
@@ -59,22 +66,27 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04,
ubuntu-latest, ubuntu-latest,
ubuntu-24.04-arm, ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest, windows-latest,
windows-11-arm, macos-latest,
macos-15-intel, macos-15-intel
macos-latest
] ]
python-version: ['3.11', 'pypy-3.11-v7.x', '3.12', '3.13', '3.14'] python-version:
['3.10', 'pypy-3.10-v7.x', '3.11', 'pypy-3.11-v7.x', '3.12', '3.13']
exclude: exclude:
- os: windows-latest - os: windows-latest
python-version: pypy-3.11-v7.x python-version: pypy-3.11-v7.x
- os: ubuntu-22.04 - os: ubuntu-22.04
python-version: pypy-3.11-v7.x python-version: pypy-3.11-v7.x
- os: windows-11-arm - os: ubuntu-22.04-arm
python-version: pypy-3.10-v7.x
- os: ubuntu-22.04-arm
python-version: pypy-3.11-v7.x python-version: pypy-3.11-v7.x
- os: ubuntu-22.04-arm
python-version: pypy-3.10-v7.x
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Setup Python - name: Setup Python
@@ -111,25 +123,25 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04,
ubuntu-latest, ubuntu-latest,
ubuntu-24.04-arm, ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest, windows-latest,
windows-11-arm, macos-latest,
macos-15-intel, macos-15-intel
macos-latest
] ]
python-version: python-version:
['3.10', '3.11', 'pypy-3.11-v7.x', '3.12', '3.13', '3.14'] [
exclude: '3.9',
- os: windows-latest 'pypy-3.9-v7.x',
python-version: pypy-3.11-v7.x '3.10',
- os: ubuntu-22.04 'pypy-3.10-v7.x',
python-version: pypy-3.11-v7.x '3.11',
- os: windows-11-arm 'pypy-3.11-v7.x',
python-version: pypy-3.11-v7.x '3.12',
- os: windows-11-arm '3.13'
python-version: '3.10' ]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Install poetry - name: Install poetry
@@ -157,23 +169,30 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04,
ubuntu-latest, ubuntu-latest,
ubuntu-24.04-arm, ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest, windows-latest,
windows-11-arm, macos-latest,
macos-15-intel, macos-15-intel
macos-latest
] ]
python-version: python-version:
['3.10', '3.11', 'pypy-3.11-v7.x', '3.12', '3.13', '3.14'] [
'3.9',
'pypy-3.9-v7.x',
'3.10',
'pypy-3.10-v7.x',
'3.11',
'pypy-3.11-v7.x',
'3.12',
'3.13'
]
exclude: exclude:
- os: windows-latest - os: windows-latest
python-version: pypy-3.11-v7.x python-version: pypy-3.11-v7.x
- os: windows-11-arm - os: macos-latest
python-version: pypy-3.11-v7.x python-version: pypy-3.9-v7.x
- os: windows-11-arm
python-version: '3.10'
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Setup Python - name: Setup Python
@@ -193,21 +212,24 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04,
ubuntu-latest, ubuntu-latest,
ubuntu-24.04-arm, ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest, windows-latest,
windows-11-arm, macos-latest,
macos-15-intel, macos-15-intel
macos-latest
] ]
python-version: ['3.11', 'pypy-3.11-v7.x', '3.12', '3.13', '3.14'] python-version:
['3.10', 'pypy-3.10-v7.x', '3.11', 'pypy-3.11-v7.x', '3.12', '3.13']
exclude: exclude:
- os: windows-latest - os: windows-latest
python-version: pypy-3.11-v7.x python-version: pypy-3.11-v7.x
- os: ubuntu-22.04 - os: ubuntu-22.04
python-version: pypy-3.11-v7.x python-version: pypy-3.11-v7.x
- os: windows-11-arm - os: ubuntu-22.04-arm
python-version: pypy-3.10-v7.x
- os: ubuntu-22.04-arm
python-version: pypy-3.11-v7.x python-version: pypy-3.11-v7.x
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
@@ -246,18 +268,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04,
ubuntu-latest, ubuntu-latest,
ubuntu-24.04-arm, ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest, windows-latest,
windows-11-arm, macos-latest,
macos-15-intel, macos-15-intel
macos-latest
] ]
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
exclude:
- os: windows-11-arm
python-version: '3.10'
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Setup Python - name: Setup Python
@@ -277,18 +296,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04,
ubuntu-latest, ubuntu-latest,
ubuntu-24.04-arm, ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest, windows-latest,
windows-11-arm, macos-latest,
macos-15-intel, macos-15-intel
macos-latest
] ]
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
exclude:
- os: windows-11-arm
python-version: '3.10'
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Setup Python - name: Setup Python
@@ -300,3 +316,56 @@ jobs:
pip-version: '25.0.1' pip-version: '25.0.1'
- name: Install dependencies - name: Install dependencies
run: pip install numpy pandas requests run: pip install numpy pandas requests
python-pip-dependencies-caching-with-pip-install:
name: Test pip (Python ${{ matrix.python-version}}, ${{ matrix.os }})
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
[
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v6
- name: Setup Python
uses: ./
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
pip-install: numpy pandas requests
python-pip-dependencies-caching-path-with-pip-install:
name: Test pip (Python ${{ matrix.python-version}}, ${{ matrix.os }}, caching path)
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os:
[
ubuntu-latest,
ubuntu-24.04-arm,
ubuntu-22.04,
ubuntu-22.04-arm,
windows-latest,
macos-latest,
macos-15-intel
]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
steps:
- uses: actions/checkout@v6
- name: Setup Python
uses: ./
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: __tests__/data/requirements.txt
pip-install: numpy pandas requests
+36 -38
View File
@@ -19,25 +19,30 @@ jobs:
matrix: matrix:
operating-system: operating-system:
[ [
windows-latest,
ubuntu-22.04, ubuntu-22.04,
ubuntu-22.04-arm,
ubuntu-latest, ubuntu-latest,
ubuntu-24.04-arm, ubuntu-24.04-arm,
windows-latest, macos-latest,
windows-11-arm, macos-15-intel
macos-15-intel,
macos-latest
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Run with setup-python 3.9.13
uses: ./
with:
python-version: 3.9.13
- name: Verify 3.9.13
run: python __tests__/verify-python.py 3.9.13
- name: Run with setup-python 3.10.11 - name: Run with setup-python 3.10.11
if: matrix.operating-system != 'windows-11-arm'
uses: ./ uses: ./
with: with:
python-version: 3.10.11 python-version: 3.10.11
- name: Verify 3.10.11 - name: Verify 3.10.11
if: matrix.operating-system != 'windows-11-arm'
run: python __tests__/verify-python.py 3.10.11 run: python __tests__/verify-python.py 3.10.11
- name: Run with setup-python 3.11.9 - name: Run with setup-python 3.11.9
@@ -47,59 +52,52 @@ jobs:
- name: Verify 3.11.9 - name: Verify 3.11.9
run: python __tests__/verify-python.py 3.11.9 run: python __tests__/verify-python.py 3.11.9
- name: Run with setup-python 3.12.10 - name: Run with setup-python 3.12.7
uses: ./ uses: ./
with: with:
python-version: 3.12.10 python-version: 3.12.7
- name: Verify 3.12.10 - name: Verify 3.12.7
run: python __tests__/verify-python.py 3.12.10 run: python __tests__/verify-python.py 3.12.7
- name: Run with setup-python 3.13.14 - name: Run with setup-python 3.13.0
uses: ./ uses: ./
with: with:
python-version: 3.13.14 python-version: 3.13.0
- name: Verify 3.13.14 - name: Verify 3.13.0
run: python __tests__/verify-python.py 3.13.14 run: python __tests__/verify-python.py 3.13.0
- name: Run with setup-python 3.14.6 - name: Run with setup-python 3.13
id: cp313
uses: ./ uses: ./
with: with:
python-version: 3.14.6 python-version: '3.13'
- name: Verify 3.14.6 - name: Verify 3.13
run: python __tests__/verify-python.py 3.14.6 run: python __tests__/verify-python.py 3.13
- name: Run with setup-python 3.14
id: cp314
uses: ./
with:
python-version: '3.14'
- name: Verify 3.14
run: python __tests__/verify-python.py 3.14
- name: Install pipx (Windows) - name: Install pipx (Windows)
if: runner. os == 'Windows' if: runner. os == 'Windows'
run: python -m pip install pipx run: python -m pip install pipx
- name: Run python-path sample 3.14 - name: Run python-path sample 3.13
run: pipx run --python '${{ steps.cp314.outputs.python-path }}' nox --version run: pipx run --python '${{ steps.cp313.outputs.python-path }}' nox --version
env: env:
PIPX_HOME: '${{ runner.temp }}/pipx' PIPX_HOME: '${{ runner.temp }}/pipx'
PIPX_BIN_DIR: '${{ runner.temp }}/pipx/bin' PIPX_BIN_DIR: '${{ runner.temp }}/pipx/bin'
- name: Run with setup-python ==3.14 - name: Run with setup-python ==3.13
uses: ./ uses: ./
with: with:
python-version: '==3.14' python-version: '==3.13'
- name: Verify ==3.14 - name: Verify ==3.13
run: python __tests__/verify-python.py 3.14
- name: Run with setup-python <3.14
uses: ./
with:
python-version: '<3.14'
- name: Verify <3.14
run: python __tests__/verify-python.py 3.13 run: python __tests__/verify-python.py 3.13
- name: Run with setup-python <3.13
uses: ./
with:
python-version: '<3.13'
- name: Verify <3.13
run: python __tests__/verify-python.py 3.12
- name: Test Raw Endpoint Access - name: Test Raw Endpoint Access
run: | run: |
curl -L https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json | jq empty curl -L https://raw.githubusercontent.com/actions/python-versions/main/versions-manifest.json | jq empty
+5 -4
View File
@@ -28,9 +28,10 @@ jobs:
macos-15-intel macos-15-intel
] ]
graalpy: graalpy:
- 'graalpy-22.3'
- 'graalpy-23.0'
- 'graalpy-23.1' - 'graalpy-23.1'
- 'graalpy-24.2' - 'graalpy-24.1'
- 'graalpy-25.0'
steps: steps:
- name: Checkout - name: Checkout
@@ -81,7 +82,7 @@ jobs:
ubuntu-latest, ubuntu-latest,
macos-15-intel macos-15-intel
] ]
graalpy: ['graalpy23.1', 'graalpy24.2', 'graalpy25.0'] graalpy: ['graalpy22.3', 'graalpy23.0', 'graalpy23.1', 'graalpy24.1']
steps: steps:
- name: Checkout - name: Checkout
@@ -112,7 +113,7 @@ jobs:
uses: ./ uses: ./
id: graalpy id: graalpy
with: with:
python-version: 'graalpy-25.x' python-version: 'graalpy-24.x'
check-latest: true check-latest: true
- name: GraalPy and Python version - name: GraalPy and Python version
run: python --version run: python --version
+50 -38
View File
@@ -22,20 +22,27 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
macos-15-intel, ubuntu-22.04,
macos-latest ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
] ]
pypy: pypy:
- 'pypy-3.11' - 'pypy-2.7'
- 'pypy-3.11-v7.3.23' - 'pypy-3.10'
- 'pypy-3.11-v7.3.x' - 'pypy3.9'
- 'pypy-3.11-v7.x' - 'pypy-2.7-v7.3.17'
- 'pypy-3.10-v7.3.17'
- 'pypy-3.10-v7.3.16'
- 'pypy-3.10-v7.3.x'
- 'pypy-3.10-v7.x'
- 'pypy-2.7-v7.3.12rc1'
- 'pypy-3.11-nightly' - 'pypy-3.11-nightly'
- 'pypy3.11-v7.3.23' - 'pypy3.10-v7.3.17'
- 'pypy3.11-v7.3.19'
steps: steps:
- name: Checkout - name: Checkout
@@ -78,15 +85,16 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
os: os:
[ - macos-15-intel
ubuntu-22.04, - macos-14
ubuntu-latest, - macos-15
ubuntu-24.04-arm, - windows-2022
windows-latest, - windows-2025
macos-15-intel, - ubuntu-22.04
macos-latest - ubuntu-24.04
] - ubuntu-22.04-arm
pypy: ['pypy-3.11'] - ubuntu-24.04-arm
pypy: ['pypy-2.7', 'pypy-3.10', 'pypy-3.11']
steps: steps:
- name: Checkout - name: Checkout
@@ -130,14 +138,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
macos-15-intel, ubuntu-22.04,
macos-latest ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
] ]
pypy: ['pypy3.11-nightly', 'pypy3.11'] pypy: ['pypy2.7', 'pypy3.9', 'pypy3.11-nightly', 'pypy3.11']
steps: steps:
- name: Checkout - name: Checkout
@@ -163,12 +172,13 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
macos-15-intel, ubuntu-22.04,
macos-latest ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
] ]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
@@ -204,12 +214,13 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
macos-15-intel, ubuntu-22.04,
macos-latest ubuntu-22.04-arm,
ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
] ]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
@@ -219,6 +230,7 @@ jobs:
python-version: | python-version: |
pypy-3.11-v7.3.x pypy-3.11-v7.3.x
pypy-3.10-v7.3.x pypy-3.10-v7.3.x
pypy3.9
check-latest: true check-latest: true
- name: PyPy and Python version - name: PyPy and Python version
run: python --version run: python --version
@@ -236,13 +248,13 @@ jobs:
run: | run: |
EXECUTABLE="pypy-3.11-v7.3.x" EXECUTABLE="pypy-3.11-v7.3.x"
EXECUTABLE=${EXECUTABLE/-/} # remove the first '-' in "pypy-X.Y" -> "pypyX.Y" to match executable name EXECUTABLE=${EXECUTABLE/-/} # remove the first '-' in "pypy-X.Y" -> "pypyX.Y" to match executable name
EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffix EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffixe
${EXECUTABLE} --version ${EXECUTABLE} --version
shell: bash shell: bash
- name: Assert expected binaries (or symlinks) are present - name: Assert expected binaries (or symlinks) are present
run: | run: |
EXECUTABLE='pypy-3.10-v7.3.x' EXECUTABLE='pypy3.9'
EXECUTABLE=${EXECUTABLE/pypy-/pypy} # remove the first '-' in "pypy-X.Y" -> "pypyX.Y" to match executable name EXECUTABLE=${EXECUTABLE/pypy-/pypy} # remove the first '-' in "pypy-X.Y" -> "pypyX.Y" to match executable name
EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffix EXECUTABLE=${EXECUTABLE%%-*} # remove any -* suffixe
${EXECUTABLE} --version ${EXECUTABLE} --version
shell: bash shell: bash
+105 -105
View File
@@ -22,15 +22,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.13.14t, 3.14.6t] python: [3.13.0t, 3.13.1t, 3.13.2t]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -59,15 +59,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.13.14t, 3.14.6t] python: [3.13.0t, 3.13.1t, 3.13.2t]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -99,15 +99,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.13.14t, 3.14.6t] python: [3.13.0t, 3.13.1t, 3.13.2t]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -137,15 +137,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.13.14, 3.14.6] python: [3.13.0, 3.13.1, 3.13.2]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -178,15 +178,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.13.14, 3.14.6] python: [3.13.0, 3.13.1, 3.13.2]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -219,15 +219,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.13.14t, 3.14.6t, 3.15t-dev] python: [3.13.0t, 3.13.1t, 3.13.2t, 3.14t-dev]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -250,15 +250,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.13t, 3.15t-dev] python: [3.13t, 3.14t-dev]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -290,15 +290,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.13.14t, 3.14.6t, 3.15t-dev] python: [3.13.0t, 3.13.1t, 3.13.2t, 3.14t-dev]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -323,30 +323,30 @@ jobs:
run: python -c 'import math; print(math.factorial(5))' run: python -c 'import math; print(math.factorial(5))'
setup-pre-release-version-from-manifest: setup-pre-release-version-from-manifest:
name: Setup 3.15.0-beta.3 ${{ matrix.os }} name: Setup 3.14.0-alpha.6 ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: setup-python 3.15.0-beta.3 - name: setup-python 3.14.0-alpha.6
id: setup-python id: setup-python
uses: ./ uses: ./
with: with:
python-version: '3.15.0-beta.3' python-version: '3.14.0-alpha.6'
freethreaded: true freethreaded: true
- name: Check python-path - name: Check python-path
@@ -360,67 +360,67 @@ jobs:
run: python -c 'import math; print(math.factorial(5))' run: python -c 'import math; print(math.factorial(5))'
setup-dev-version: setup-dev-version:
name: Setup 3.15t-dev ${{ matrix.os }} name: Setup 3.14t-dev ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
macos-15-intel, ubuntu-22.04-arm,
macos-latest ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: setup-python 3.15t-dev - name: setup-python 3.14t-dev
id: setup-python id: setup-python
uses: ./ uses: ./
with: with:
python-version: '3.15t-dev' python-version: '3.14t-dev'
- name: Check python-path - name: Check python-path
run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}' run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}'
shell: bash shell: bash
- name: Validate version - name: Validate version
run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.15.') }} run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.14.') }}
shell: bash shell: bash
- name: Run simple code - name: Run simple code
run: python -c 'import math; print(math.factorial(5))' run: python -c 'import math; print(math.factorial(5))'
setup-prerelease-version: setup-prerelease-version:
name: Setup 3.15t ${{ matrix.os }} name: Setup 3.14t ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
macos-15-intel, ubuntu-22.04-arm,
macos-latest ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: setup-python 3.15t - name: setup-python 3.14t
id: setup-python id: setup-python
uses: ./ uses: ./
with: with:
python-version: '3.15t' python-version: '3.14t'
allow-prereleases: true allow-prereleases: true
- name: Check python-path - name: Check python-path
@@ -428,7 +428,7 @@ jobs:
shell: bash shell: bash
- name: Validate version - name: Validate version
run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.15.') }} run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.14.') }}
shell: bash shell: bash
- name: Run simple code - name: Run simple code
@@ -442,15 +442,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.13.14t, 3.14.6t] python: [3.13.0t, 3.13.1t, 3.13.2t]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -475,15 +475,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
macos-15-intel, ubuntu-22.04-arm,
macos-latest ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
] ]
python-version: [3.13t, 3.15t-dev] python-version: [3.13t, 3.14t-dev]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Setup Python and check latest - name: Setup Python and check latest
@@ -502,24 +502,24 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
macos-15-intel, ubuntu-22.04-arm,
macos-latest ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
] ]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Setup multiple Python versions - name: Setup Python and check latest
id: setup-python id: setup-python
uses: ./ uses: ./
with: with:
python-version: | python-version: |
3.13.14t 3.13.1t
3.14.6t 3.13.2t
3.15t-dev 3.14t-dev
- name: Verify Python version - name: Verify Python version
run: ${{ steps.setup-python.outputs.python-path }} -VVV run: ${{ steps.setup-python.outputs.python-path }} -VVV
@@ -531,15 +531,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.13.14, 3.14.6, 3.15-dev, 3.15.0-beta.3] python: [3.13.1, 3.13.2, 3.14-dev, 3.14.0-alpha.6]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
+101 -131
View File
@@ -22,18 +22,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.10.11, 3.11.9, 3.12.10, 3.13.14, 3.14.6] python: [3.9.13, 3.10.11, 3.11.9, 3.12.3, 3.13.2]
exclude:
- os: windows-11-arm
python: 3.10.11
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -69,18 +66,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.10.11, 3.11.9, 3.12.10, 3.13.14, 3.14.6] python: [3.9.13, 3.10.11, 3.11.9, 3.12.3, 3.13.2]
exclude:
- os: windows-11-arm
python: 3.10.11
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -119,18 +113,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.10.11, 3.11.9, 3.12.10, 3.13.14, 3.14.6] python: [3.9.13, 3.10.11, 3.11.9, 3.12.3, 3.13.2]
exclude:
- os: windows-11-arm
python: 3.10.11
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -167,18 +158,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.10.11, 3.11.9, '==3.12.10', 3.13.14, 3.14.6] python: [3.9.13, 3.10.11, 3.11.9, '==3.12.3', 3.13.2]
exclude:
- os: windows-11-arm
python: 3.10.11
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -220,18 +208,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.10.11, 3.11.9, 3.12.10, 3.13.14, 3.14.6] python: [3.9.13, 3.10.11, 3.11.9, 3.12.3, 3.13.2]
exclude:
- os: windows-11-arm
python: 3.10.11
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -273,22 +258,18 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [pypy3.11-7.3.18, graalpy-24.1.2, 3.13.14, 3.15-dev] python: [pypy3.11-7.3.18, graalpy-24.1.2, 3.13.2, 3.14-dev]
exclude: exclude:
- os: windows-latest - os: windows-latest
python: graalpy-24.1.2 python: graalpy-24.1.2
- os: windows-11-arm
python: graalpy-24.1.2
- os: windows-11-arm
python: pypy3.11-7.3.18
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -311,18 +292,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.10.11, 3.11.9, 3.13.14, 3.14.6] python: [3.9.13, 3.10.11, 3.11.9, 3.13.2]
exclude:
- os: windows-11-arm
python: 3.10.11
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -364,18 +342,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: [3.10.11, 3.11.9, 3.13.14, 3.14.6] python: [3.9.13, 3.10.11, 3.11.9, 3.13.2]
exclude:
- os: windows-11-arm
python: 3.10.11
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -410,30 +385,30 @@ jobs:
run: python -c 'import math; print(math.factorial(5))' run: python -c 'import math; print(math.factorial(5))'
setup-pre-release-version-from-manifest: setup-pre-release-version-from-manifest:
name: Setup 3.15.0-beta.3 ${{ matrix.os }} name: Setup 3.14.0-alpha.6 ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: setup-python 3.15.0-beta.3 - name: setup-python 3.14.0-alpha.6
id: setup-python id: setup-python
uses: ./ uses: ./
with: with:
python-version: '3.15.0-beta.3' python-version: '3.14.0-alpha.6'
- name: Check python-path - name: Check python-path
run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}' run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}'
@@ -442,8 +417,8 @@ jobs:
- name: Validate version - name: Validate version
run: | run: |
$pythonVersion = (python --version) $pythonVersion = (python --version)
if ("Python 3.15.0b3" -ne "$pythonVersion"){ if ("Python 3.14.0a6" -ne "$pythonVersion"){
Write-Host "The current version is $pythonVersion; expected version is 3.15.0b3" Write-Host "The current version is $pythonVersion; expected version is 3.14.0a6"
exit 1 exit 1
} }
$pythonVersion $pythonVersion
@@ -453,67 +428,67 @@ jobs:
run: python -c 'import math; print(math.factorial(5))' run: python -c 'import math; print(math.factorial(5))'
setup-dev-version: setup-dev-version:
name: Setup 3.15-dev ${{ matrix.os }} name: Setup 3.14-dev ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
macos-15-intel, ubuntu-22.04-arm,
macos-latest ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: setup-python 3.15-dev - name: setup-python 3.14-dev
id: setup-python id: setup-python
uses: ./ uses: ./
with: with:
python-version: '3.15-dev' python-version: '3.14-dev'
- name: Check python-path - name: Check python-path
run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}' run: ./__tests__/check-python-path.sh '${{ steps.setup-python.outputs.python-path }}'
shell: bash shell: bash
- name: Validate version - name: Validate version
run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.15.') }} run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.14.') }}
shell: bash shell: bash
- name: Run simple code - name: Run simple code
run: python -c 'import math; print(math.factorial(5))' run: python -c 'import math; print(math.factorial(5))'
setup-prerelease-version: setup-prerelease-version:
name: Setup 3.15 ${{ matrix.os }} name: Setup 3.14 ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
macos-15-intel, ubuntu-22.04-arm,
macos-latest ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
] ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: setup-python 3.15 - name: setup-python 3.14
id: setup-python id: setup-python
uses: ./ uses: ./
with: with:
python-version: '3.15' python-version: '3.14'
allow-prereleases: true allow-prereleases: true
- name: Check python-path - name: Check python-path
@@ -521,7 +496,7 @@ jobs:
shell: bash shell: bash
- name: Validate version - name: Validate version
run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.15.') }} run: ${{ startsWith(steps.setup-python.outputs.python-version, '3.14.') }}
shell: bash shell: bash
- name: Run simple code - name: Run simple code
@@ -535,18 +510,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
ubuntu-22.04-arm,
macos-15-intel, macos-15-intel,
macos-latest ubuntu-latest,
ubuntu-24.04-arm
] ]
python: ['3.10', '3.11', '3.12', '3.13', '3.14'] python: ['3.9', '3.10', '3.11', '3.12', '3.13']
exclude:
- os: windows-11-arm
python: '3.10'
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -571,18 +543,15 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
macos-15-intel, ubuntu-22.04-arm,
macos-latest ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
] ]
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14'] python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
exclude:
- os: windows-11-arm
python-version: '3.10'
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
- name: Setup Python and check latest - name: Setup Python and check latest
@@ -607,13 +576,13 @@ jobs:
matrix: matrix:
os: os:
[ [
ubuntu-22.04, macos-latest,
ubuntu-latest,
ubuntu-24.04-arm,
windows-latest, windows-latest,
windows-11-arm, ubuntu-22.04,
macos-15-intel, ubuntu-22.04-arm,
macos-latest ubuntu-24.04-arm,
ubuntu-latest,
macos-15-intel
] ]
steps: steps:
- uses: actions/checkout@v6 - uses: actions/checkout@v6
@@ -621,16 +590,17 @@ jobs:
uses: ./ uses: ./
with: with:
python-version: | python-version: |
3.9
3.10
3.11 3.11
3.12 3.12
3.13 3.13
3.14
check-latest: true check-latest: true
- name: Validate version - name: Validate version
run: | run: |
$pythonVersion = (python --version) $pythonVersion = (python --version)
if ("$pythonVersion" -NotMatch "3.14"){ if ("$pythonVersion" -NotMatch "3.13"){
Write-Host "The current version is $pythonVersion; expected version is 3.14" Write-Host "The current version is $pythonVersion; expected version is 3.13"
exit 1 exit 1
} }
$pythonVersion $pythonVersion
-2
View File
@@ -15,5 +15,3 @@ allowed:
reviewed: reviewed:
npm: npm:
- "@actions/http-client" - "@actions/http-client"
- "balanced-match"
- "brace-expansion"
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: "@actions/cache" name: "@actions/cache"
version: 6.2.0 version: 5.1.0
type: npm type: npm
summary: Actions cache lib summary: Actions cache lib
homepage: https://github.com/actions/toolkit/tree/main/packages/cache homepage: https://github.com/actions/toolkit/tree/main/packages/cache
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: "@actions/core" name: "@actions/core"
version: 3.0.1 version: 2.0.3
type: npm type: npm
summary: Actions core lib summary: Actions core lib
homepage: https://github.com/actions/toolkit/tree/main/packages/core homepage: https://github.com/actions/toolkit/tree/main/packages/core
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: "@actions/exec" name: "@actions/exec"
version: 3.0.0 version: 2.0.0
type: npm type: npm
summary: Actions exec lib summary: Actions exec lib
homepage: https://github.com/actions/toolkit/tree/main/packages/exec homepage: https://github.com/actions/toolkit/tree/main/packages/exec
-20
View File
@@ -1,20 +0,0 @@
---
name: "@actions/glob"
version: 0.7.0
type: npm
summary: Actions glob lib
homepage: https://github.com/actions/toolkit/tree/main/packages/glob
license: mit
licenses:
- sources: LICENSE.md
text: |-
The MIT License (MIT)
Copyright 2019 GitHub
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
notices: []
@@ -1,6 +1,6 @@
--- ---
name: "@actions/glob" name: "@actions/glob"
version: 0.6.1 version: 0.5.1
type: npm type: npm
summary: Actions glob lib summary: Actions glob lib
homepage: https://github.com/actions/toolkit/tree/main/packages/glob homepage: https://github.com/actions/toolkit/tree/main/packages/glob
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: "@actions/http-client" name: "@actions/http-client"
version: 4.0.1 version: 3.0.2
type: npm type: npm
summary: Actions Http Client summary: Actions Http Client
homepage: https://github.com/actions/toolkit/tree/main/packages/http-client homepage: https://github.com/actions/toolkit/tree/main/packages/http-client
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: "@actions/io" name: "@actions/io"
version: 3.0.2 version: 2.0.0
type: npm type: npm
summary: Actions io lib summary: Actions io lib
homepage: https://github.com/actions/toolkit/tree/main/packages/io homepage: https://github.com/actions/toolkit/tree/main/packages/io
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: "@actions/tool-cache" name: "@actions/tool-cache"
version: 4.0.0 version: 3.0.1
type: npm type: npm
summary: Actions tool-cache lib summary: Actions tool-cache lib
homepage: https://github.com/actions/toolkit/tree/main/packages/tool-cache homepage: https://github.com/actions/toolkit/tree/main/packages/tool-cache
@@ -1,18 +1,16 @@
--- ---
name: brace-expansion name: "@azure/abort-controller"
version: 5.0.6 version: 1.1.0
type: npm type: npm
summary: Brace expansion as known from sh/bash summary: Microsoft Azure SDK for JavaScript - Aborter
homepage: homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/abort-controller/README.md
license: other license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE
text: | text: |
MIT License The MIT License (MIT)
Copyright Julian Gruber <julian@juliangruber.com> Copyright (c) 2020 Microsoft
TypeScript port Copyright Isaac Z. Schlueter <i@izs.me>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: "@azure/core-client" name: "@azure/core-client"
version: 1.10.2 version: 1.10.1
type: npm type: npm
summary: Core library for interfacing with AutoRest generated code summary: Core library for interfacing with AutoRest generated code
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-client/ homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-client/
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: "@azure/core-http-compat" name: "@azure/core-http-compat"
version: 2.4.0 version: 2.3.1
type: npm type: npm
summary: Core HTTP Compatibility Library to bridge the gap between Core V1 & V2 packages. summary: Core HTTP Compatibility Library to bridge the gap between Core V1 & V2 packages.
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-compat/ homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-compat/
+2 -2
View File
@@ -1,9 +1,9 @@
--- ---
name: "@azure/core-rest-pipeline" name: "@azure/core-rest-pipeline"
version: 1.24.0 version: 1.22.2
type: npm type: npm
summary: Isomorphic client library for making HTTP requests in node.js and browser. summary: Isomorphic client library for making HTTP requests in node.js and browser.
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/core-rest-pipeline/README.md homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-rest-pipeline/
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: "@azure/core-xml" name: "@azure/core-xml"
version: 1.5.1 version: 1.5.0
type: npm type: npm
summary: Core library for interacting with XML payloads summary: Core library for interacting with XML payloads
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-xml/ homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-xml/
+2 -2
View File
@@ -1,9 +1,9 @@
--- ---
name: "@azure/storage-blob" name: "@azure/storage-blob"
version: 12.32.0 version: 12.29.1
type: npm type: npm
summary: Microsoft Azure Storage SDK for JavaScript - Blob summary: Microsoft Azure Storage SDK for JavaScript - Blob
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/storage/storage-blob/README.md homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/storage/storage-blob/
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE
+2 -2
View File
@@ -1,9 +1,9 @@
--- ---
name: "@azure/storage-common" name: "@azure/storage-common"
version: 12.4.0 version: 12.1.1
type: npm type: npm
summary: Azure Storage Common Client Library for JavaScript summary: Azure Storage Common Client Library for JavaScript
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/storage/storage-common/README.md homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/storage/storage-internal-avro/
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE
+2 -2
View File
@@ -1,9 +1,9 @@
--- ---
name: "@typespec/ts-http-runtime" name: "@typespec/ts-http-runtime"
version: 0.3.6 version: 0.3.2
type: npm type: npm
summary: Isomorphic client library for making HTTP requests in node.js and browser. summary: Isomorphic client library for making HTTP requests in node.js and browser.
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/ts-http-runtime/README.md homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/ts-http-runtime/
license: mit license: mit
licenses: licenses:
- sources: LICENSE - sources: LICENSE
-34
View File
@@ -1,34 +0,0 @@
---
name: balanced-match
version: 4.0.4
type: npm
summary: Match balanced character pairs, like "{" and "}"
homepage:
license: other
licenses:
- sources: LICENSE.md
text: |
(MIT)
Original code Copyright Julian Gruber <julian@juliangruber.com>
Port to TypeScript Copyright Isaac Z. Schlueter <i@izs.me>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
notices: []
@@ -1,6 +1,6 @@
--- ---
name: brace-expansion name: brace-expansion
version: 1.1.15 version: 1.1.13
type: npm type: npm
summary: Brace expansion as known from sh/bash summary: Brace expansion as known from sh/bash
homepage: https://github.com/juliangruber/brace-expansion homepage: https://github.com/juliangruber/brace-expansion
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: fast-xml-parser name: fast-xml-parser
version: 5.9.3 version: 5.9.2
type: npm type: npm
summary: Validate XML, Parse XML, Build XML without C/C++ based libraries summary: Validate XML, Parse XML, Build XML without C/C++ based libraries
homepage: homepage:
-66
View File
@@ -1,66 +0,0 @@
---
name: minimatch
version: 10.2.5
type: npm
summary: a glob matcher in javascript
homepage:
license: blueoak-1.0.0
licenses:
- sources: LICENSE.md
text: |
# Blue Oak Model License
Version 1.0.0
## Purpose
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
**_As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim._**
notices: []
+1 -1
View File
@@ -1,6 +1,6 @@
--- ---
name: path-expression-matcher name: path-expression-matcher
version: 1.6.0 version: 1.5.0
type: npm type: npm
summary: Efficient path tracking and pattern matching for XML/JSON parsers summary: Efficient path tracking and pattern matching for XML/JSON parsers
homepage: https://github.com/NaturalIntelligence/path-expression-matcher#readme homepage: https://github.com/NaturalIntelligence/path-expression-matcher#readme
+26
View File
@@ -0,0 +1,26 @@
---
name: semver
version: 6.3.1
type: npm
summary: The semantic version parser used by npm.
homepage:
license: isc
licenses:
- sources: LICENSE
text: |
The ISC License
Copyright (c) Isaac Z. Schlueter and Contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
notices: []
+11
View File
@@ -0,0 +1,11 @@
// This is a reusable configuration file copied from https://github.com/actions/reusable-workflows/tree/main/reusable-configurations. Please don't make changes to this file as it's the subject of an automatic update.
module.exports = {
printWidth: 80,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
trailingComma: 'none',
bracketSpacing: false,
arrowParens: 'avoid'
};
+1 -4
View File
@@ -11,10 +11,6 @@ This action provides the following functionality for GitHub Actions users:
- Optionally caching dependencies for pip, pipenv and poetry - Optionally caching dependencies for pip, pipenv and poetry
- Registering problem matchers for error output - Registering problem matchers for error output
## What's new in V7
- Migrated action internals to ESM for compatibility with latest `@actions/*` packages. No changes to action inputs, outputs, or behavior.
## Breaking changes in V6 ## Breaking changes in V6
- Upgraded action from node20 to node24 - Upgraded action from node20 to node24
@@ -122,6 +118,7 @@ See examples of using `cache` and `cache-dependency-path` for `pipenv` and `poet
- [Using `setup-python` on GHES](docs/advanced-usage.md#using-setup-python-on-ghes) - [Using `setup-python` on GHES](docs/advanced-usage.md#using-setup-python-on-ghes)
- [Allow pre-releases](docs/advanced-usage.md#allow-pre-releases) - [Allow pre-releases](docs/advanced-usage.md#allow-pre-releases)
- [Using the pip-version input](docs/advanced-usage.md#using-the-pip-version-input) - [Using the pip-version input](docs/advanced-usage.md#using-the-pip-version-input)
- [Using the pip-install input](docs/advanced-usage.md#using-the-pip-install-input)
## Recommended permissions ## Recommended permissions
+49 -106
View File
@@ -1,62 +1,10 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import {fileURLToPath} from 'url';
import * as path from 'path'; import * as path from 'path';
import * as core from '@actions/core';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); import * as cache from '@actions/cache';
import * as exec from '@actions/exec';
// Mock @actions modules before importing anything that depends on them import * as io from '@actions/io';
jest.unstable_mockModule('@actions/core', () => ({ import {getCacheDistributor} from '../src/cache-distributions/cache-factory';
info: jest.fn(), import {State} from '../src/cache-distributions/cache-distributor';
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
jest.unstable_mockModule('@actions/cache', () => ({
saveCache: jest.fn(),
restoreCache: jest.fn(),
isFeatureAvailable: jest.fn()
}));
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
jest.unstable_mockModule('@actions/io', () => ({
which: jest.fn(),
mkdirP: jest.fn(),
rmRF: jest.fn(),
mv: jest.fn(),
cp: jest.fn()
}));
// Dynamic imports after mocking
const core = await import('@actions/core');
const cache = await import('@actions/cache');
const exec = await import('@actions/exec');
const io = await import('@actions/io');
const {getCacheDistributor} =
await import('../src/cache-distributions/cache-factory.js');
const {State} = await import('../src/cache-distributions/cache-distributor.js');
describe('restore-cache', () => { describe('restore-cache', () => {
const pipFileLockHash = const pipFileLockHash =
@@ -76,38 +24,43 @@ virtualenvs.in-project = true
virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/pypoetry/virtualenvs virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/pypoetry/virtualenvs
`; `;
let infoSpy: jest.Mock; // core spy
let warningSpy: jest.Mock; let infoSpy: jest.SpyInstance;
let debugSpy: jest.Mock; let warningSpy: jest.SpyInstance;
let saveStateSpy: jest.Mock; let debugSpy: jest.SpyInstance;
let getStateSpy: jest.Mock; let saveStateSpy: jest.SpyInstance;
let setOutputSpy: jest.Mock; let getStateSpy: jest.SpyInstance;
let restoreCacheSpy: jest.Mock; let setOutputSpy: jest.SpyInstance;
let getExecOutputSpy: jest.Mock;
let whichSpy: jest.Mock; // cache spy
let restoreCacheSpy: jest.SpyInstance;
// exec spy
let getExecOutputSpy: jest.SpyInstance;
// io spy
let whichSpy: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
process.env['RUNNER_OS'] = process.env['RUNNER_OS'] ?? 'linux'; process.env['RUNNER_OS'] = process.env['RUNNER_OS'] ?? 'linux';
infoSpy = core.info as jest.Mock; infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => undefined); infoSpy.mockImplementation(input => undefined);
warningSpy = core.warning as jest.Mock; warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => undefined); warningSpy.mockImplementation(input => undefined);
debugSpy = core.debug as jest.Mock; debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => undefined); debugSpy.mockImplementation(input => undefined);
saveStateSpy = core.saveState as jest.Mock; saveStateSpy = jest.spyOn(core, 'saveState');
saveStateSpy.mockImplementation(() => undefined); saveStateSpy.mockImplementation(input => undefined);
getStateSpy = core.getState as jest.Mock; getStateSpy = jest.spyOn(core, 'getState');
getStateSpy.mockImplementation(() => undefined); getStateSpy.mockImplementation(input => undefined);
getExecOutputSpy = exec.getExecOutput as jest.Mock; getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
( getExecOutputSpy.mockImplementation((input: string) => {
getExecOutputSpy as jest.Mock<typeof exec.getExecOutput>
).mockImplementation(async (input: string) => {
if (input.includes('pip')) { if (input.includes('pip')) {
return {stdout: 'pip', stderr: '', exitCode: 0}; return {stdout: 'pip', stderr: '', exitCode: 0};
} }
@@ -121,19 +74,17 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
return {stdout: '', stderr: 'Error occured', exitCode: 2}; return {stdout: '', stderr: 'Error occured', exitCode: 2};
}); });
setOutputSpy = core.setOutput as jest.Mock; setOutputSpy = jest.spyOn(core, 'setOutput');
setOutputSpy.mockImplementation(() => undefined); setOutputSpy.mockImplementation(input => undefined);
restoreCacheSpy = cache.restoreCache as jest.Mock; restoreCacheSpy = jest.spyOn(cache, 'restoreCache');
( restoreCacheSpy.mockImplementation(
restoreCacheSpy as jest.Mock<typeof cache.restoreCache> (cachePaths: string[], primaryKey: string, restoreKey?: string) => {
).mockImplementation( return primaryKey;
(cachePaths: string[], primaryKey: string, restoreKey?: string[]) => {
return Promise.resolve(primaryKey);
} }
); );
whichSpy = io.which as jest.Mock; whichSpy = jest.spyOn(io, 'which');
whichSpy.mockImplementation(() => '/path/to/python'); whichSpy.mockImplementation(() => '/path/to/python');
}); });
@@ -212,13 +163,9 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
fileHash, fileHash,
cachePaths cachePaths
) => { ) => {
( restoreCacheSpy.mockImplementation(
restoreCacheSpy as jest.Mock<typeof cache.restoreCache> (cachePaths: string[], primaryKey: string, restoreKey?: string) => {
).mockImplementation( return primaryKey.includes(fileHash) ? primaryKey : '';
(cachePaths: string[], primaryKey: string, restoreKey?: string[]) => {
return Promise.resolve(
primaryKey.includes(fileHash) ? primaryKey : ''
);
} }
); );
@@ -237,8 +184,8 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
); );
} }
const restoredKeys = await Promise.all( const restoredKeys = restoreCacheSpy.mock.results.map(
restoreCacheSpy.mock.results.map(result => result.value) result => result.value
); );
restoredKeys.forEach(restoredKey => { restoredKeys.forEach(restoredKey => {
@@ -337,13 +284,9 @@ virtualenvs.path = "{cache-dir}/virtualenvs" # /Users/patrick/Library/Caches/py
])( ])(
'restored dependencies for %s by primaryKey', 'restored dependencies for %s by primaryKey',
async (packageManager, pythonVersion, dependencyFile, fileHash) => { async (packageManager, pythonVersion, dependencyFile, fileHash) => {
( restoreCacheSpy.mockImplementation(
restoreCacheSpy as jest.Mock<typeof cache.restoreCache> (cachePaths: string[], primaryKey: string, restoreKey?: string) => {
).mockImplementation( return primaryKey !== fileHash && restoreKey ? pipFileLockHash : '';
(cachePaths: string[], primaryKey: string, restoreKey?: string[]) => {
return Promise.resolve(
primaryKey !== fileHash && restoreKey ? pipFileLockHash : ''
);
} }
); );
const cacheDistributor = getCacheDistributor( const cacheDistributor = getCacheDistributor(
+46 -110
View File
@@ -1,61 +1,8 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals'; import * as core from '@actions/core';
import {fileURLToPath} from 'url'; import * as cache from '@actions/cache';
import path from 'path'; import * as exec from '@actions/exec';
import {run} from '../src/cache-save';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); import {State} from '../src/cache-distributions/cache-distributor';
// Mock @actions modules before importing anything that depends on them
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
class MockValidationError extends Error {
constructor(message: string) {
super(message);
this.name = 'ValidationError';
}
}
jest.unstable_mockModule('@actions/cache', () => ({
saveCache: jest.fn(),
restoreCache: jest.fn(),
isFeatureAvailable: jest.fn(),
ValidationError: MockValidationError,
ReserveCacheError: MockValidationError
}));
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
// Dynamic imports after mocking
const core = await import('@actions/core');
const cache = await import('@actions/cache');
const exec = await import('@actions/exec');
const {run} = await import('../src/cache-save.js');
const {State} = await import('../src/cache-distributions/cache-distributor.js');
describe('run', () => { describe('run', () => {
const pipFileLockHash = const pipFileLockHash =
@@ -67,54 +14,53 @@ describe('run', () => {
const poetryLockHash = const poetryLockHash =
'571bf984f8d210e6a97f854e479fdd4a2b5af67b5fdac109ec337a0ea16e7836'; '571bf984f8d210e6a97f854e479fdd4a2b5af67b5fdac109ec337a0ea16e7836';
let infoSpy: jest.Mock; // core spy
let warningSpy: jest.Mock; let infoSpy: jest.SpyInstance;
let debugSpy: jest.Mock; let warningSpy: jest.SpyInstance;
let saveStateSpy: jest.Mock; let debugSpy: jest.SpyInstance;
let getStateSpy: jest.Mock; let saveStateSpy: jest.SpyInstance;
let getInputSpy: jest.Mock; let getStateSpy: jest.SpyInstance;
let setFailedSpy: jest.Mock; let getInputSpy: jest.SpyInstance;
let saveCacheSpy: jest.Mock; let setFailedSpy: jest.SpyInstance;
let getExecOutputSpy: jest.Mock;
// cache spy
let saveCacheSpy: jest.SpyInstance;
// exec spy
let getExecOutputSpy: jest.SpyInstance;
let inputs = {} as any; let inputs = {} as any;
beforeEach(() => { beforeEach(() => {
process.env['RUNNER_OS'] = process.env['RUNNER_OS'] ?? 'linux'; process.env['RUNNER_OS'] = process.env['RUNNER_OS'] ?? 'linux';
infoSpy = core.info as jest.Mock; infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => undefined); infoSpy.mockImplementation(input => undefined);
warningSpy = core.warning as jest.Mock; warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => undefined); warningSpy.mockImplementation(input => undefined);
debugSpy = core.debug as jest.Mock; debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => undefined); debugSpy.mockImplementation(input => undefined);
saveStateSpy = core.saveState as jest.Mock; saveStateSpy = jest.spyOn(core, 'saveState');
saveStateSpy.mockImplementation(() => undefined); saveStateSpy.mockImplementation(input => undefined);
getStateSpy = core.getState as jest.Mock; getStateSpy = jest.spyOn(core, 'getState');
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation( getStateSpy.mockImplementation(input => {
(input: string) => {
if (input === State.CACHE_PATHS) { if (input === State.CACHE_PATHS) {
return JSON.stringify([__dirname]); return JSON.stringify([__dirname]);
} }
return requirementsHash; return requirementsHash;
} });
);
setFailedSpy = core.setFailed as jest.Mock; setFailedSpy = jest.spyOn(core, 'setFailed');
getInputSpy = core.getInput as jest.Mock; getInputSpy = jest.spyOn(core, 'getInput');
(getInputSpy as jest.Mock<typeof core.getInput>).mockImplementation( getInputSpy.mockImplementation(input => inputs[input]);
(input: string) => inputs[input]
);
getExecOutputSpy = exec.getExecOutput as jest.Mock; getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
( getExecOutputSpy.mockImplementation((input: string) => {
getExecOutputSpy as jest.Mock<typeof exec.getExecOutput>
).mockImplementation(async (input: string) => {
if (input.includes('pip')) { if (input.includes('pip')) {
return {stdout: 'pip', stderr: '', exitCode: 0}; return {stdout: 'pip', stderr: '', exitCode: 0};
} }
@@ -122,7 +68,7 @@ describe('run', () => {
return {stdout: '', stderr: 'Error occured', exitCode: 2}; return {stdout: '', stderr: 'Error occured', exitCode: 2};
}); });
saveCacheSpy = cache.saveCache as jest.Mock; saveCacheSpy = jest.spyOn(cache, 'saveCache');
saveCacheSpy.mockImplementation(() => undefined); saveCacheSpy.mockImplementation(() => undefined);
}); });
@@ -178,8 +124,7 @@ describe('run', () => {
it('saves cache from pip', async () => { it('saves cache from pip', async () => {
inputs['cache'] = 'pip'; inputs['cache'] = 'pip';
inputs['python-version'] = '3.10.0'; inputs['python-version'] = '3.10.0';
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation( getStateSpy.mockImplementation((name: string) => {
(name: string) => {
if (name === State.CACHE_MATCHED_KEY) { if (name === State.CACHE_MATCHED_KEY) {
return requirementsHash; return requirementsHash;
} else if (name === State.CACHE_PATHS) { } else if (name === State.CACHE_PATHS) {
@@ -187,8 +132,7 @@ describe('run', () => {
} else { } else {
return pipFileLockHash; return pipFileLockHash;
} }
} });
);
await run(); await run();
@@ -207,8 +151,7 @@ describe('run', () => {
it('saves cache from pipenv', async () => { it('saves cache from pipenv', async () => {
inputs['cache'] = 'pipenv'; inputs['cache'] = 'pipenv';
inputs['python-version'] = '3.10.0'; inputs['python-version'] = '3.10.0';
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation( getStateSpy.mockImplementation((name: string) => {
(name: string) => {
if (name === State.CACHE_MATCHED_KEY) { if (name === State.CACHE_MATCHED_KEY) {
return pipFileLockHash; return pipFileLockHash;
} else if (name === State.CACHE_PATHS) { } else if (name === State.CACHE_PATHS) {
@@ -216,8 +159,7 @@ describe('run', () => {
} else { } else {
return requirementsHash; return requirementsHash;
} }
} });
);
await run(); await run();
@@ -236,8 +178,7 @@ describe('run', () => {
it('saves cache from poetry', async () => { it('saves cache from poetry', async () => {
inputs['cache'] = 'poetry'; inputs['cache'] = 'poetry';
inputs['python-version'] = '3.10.0'; inputs['python-version'] = '3.10.0';
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation( getStateSpy.mockImplementation((name: string) => {
(name: string) => {
if (name === State.CACHE_MATCHED_KEY) { if (name === State.CACHE_MATCHED_KEY) {
return poetryLockHash; return poetryLockHash;
} else if (name === State.CACHE_PATHS) { } else if (name === State.CACHE_PATHS) {
@@ -245,8 +186,7 @@ describe('run', () => {
} else { } else {
return requirementsHash; return requirementsHash;
} }
} });
);
await run(); await run();
@@ -265,8 +205,7 @@ describe('run', () => {
it('saves with -1 cacheId , should not fail workflow', async () => { it('saves with -1 cacheId , should not fail workflow', async () => {
inputs['cache'] = 'poetry'; inputs['cache'] = 'poetry';
inputs['python-version'] = '3.10.0'; inputs['python-version'] = '3.10.0';
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation( getStateSpy.mockImplementation((name: string) => {
(name: string) => {
if (name === State.STATE_CACHE_PRIMARY_KEY) { if (name === State.STATE_CACHE_PRIMARY_KEY) {
return poetryLockHash; return poetryLockHash;
} else if (name === State.CACHE_PATHS) { } else if (name === State.CACHE_PATHS) {
@@ -274,8 +213,7 @@ describe('run', () => {
} else { } else {
return requirementsHash; return requirementsHash;
} }
} });
);
saveCacheSpy.mockImplementation(() => { saveCacheSpy.mockImplementation(() => {
return -1; return -1;
@@ -296,8 +234,7 @@ describe('run', () => {
it('saves with error from toolkit, should not fail the workflow', async () => { it('saves with error from toolkit, should not fail the workflow', async () => {
inputs['cache'] = 'npm'; inputs['cache'] = 'npm';
inputs['python-version'] = '3.10.0'; inputs['python-version'] = '3.10.0';
(getStateSpy as jest.Mock<typeof core.getState>).mockImplementation( getStateSpy.mockImplementation((name: string) => {
(name: string) => {
if (name === State.STATE_CACHE_PRIMARY_KEY) { if (name === State.STATE_CACHE_PRIMARY_KEY) {
return poetryLockHash; return poetryLockHash;
} else if (name === State.CACHE_PATHS) { } else if (name === State.CACHE_PATHS) {
@@ -305,8 +242,7 @@ describe('run', () => {
} else { } else {
return requirementsHash; return requirementsHash;
} }
} });
);
saveCacheSpy.mockImplementation(() => { saveCacheSpy.mockImplementation(() => {
throw new cache.ValidationError('Validation failed'); throw new cache.ValidationError('Validation failed');
+1 -1
View File
@@ -5,7 +5,7 @@ name = "pypi"
[packages] [packages]
flake8 = "==6.0.0" flake8 = "==6.0.0"
numpy = "==2.4.6" numpy = "==2.2.4"
[dev-packages] [dev-packages]
+58 -75
View File
@@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "0060d5c4915f1f35aae1f3e9fa6de3f65d1a851e6e33ac57a04665d5d1c796fa" "sha256": "9d0bc7ff1f9078396b19f05bc5cd57894f3783c68f96fa9d623b1ea5a1b168f5"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@@ -35,82 +35,65 @@
}, },
"numpy": { "numpy": {
"hashes": [ "hashes": [
"sha256:001fbb8e08d942dd57599e781f2472269ee7f2755fae407b4f67b2f0b17da3f1", "sha256:05c076d531e9998e7e694c36e8b349969c56eadd2cdcd07242958489d79a7286",
"sha256:0280e0356c0829a18d9de1cb7eee50ec22ca639878d7240307ca0943d73cd2c4", "sha256:0d54974f9cf14acf49c60f0f7f4084b6579d24d439453d5fc5805d46a165b542",
"sha256:043191bfa8eab18c776647b62723ac9dddece59743b13f49b2016094129c2b3f", "sha256:11c43995255eb4127115956495f43e9343736edb7fcdb0d973defd9de14cd84f",
"sha256:06ca2f61ec4385a07a6977c55ba998a4466c123642b4a32694d3128fce18c079", "sha256:188dcbca89834cc2e14eb2f106c96d6d46f200fe0200310fc29089657379c58d",
"sha256:0a041d3d761dc3c35cc56ce0351506a02bcbc25f7b169f652435141a17db9096", "sha256:1974afec0b479e50438fc3648974268f972e2d908ddb6d7fb634598cdb8260a0",
"sha256:0ab0a9c4ffb1a6d95ef519fe4247dba8eb6b18ad93999f76b7f657039acabd47", "sha256:1cf4e5c6a278d620dee9ddeb487dc6a860f9b199eadeecc567f777daace1e9e7",
"sha256:0c9136e14ed34a9e343a31c533d78a9813a69a3148332bce5e9821cb2f996e66", "sha256:207a2b8441cc8b6a2a78c9ddc64d00d20c303d79fba08c577752f080c4007ee3",
"sha256:110f8b71aacb688ec69062bb7f6938a0f8acb01b7c1c4beb453c65b6d234584d", "sha256:218f061d2faa73621fa23d6359442b0fc658d5b9a70801373625d958259eaca3",
"sha256:112b06a867b235ef466ed3508ddf0238050df9c727cafb5301ac385b899189a1", "sha256:2aad3c17ed2ff455b8eaafe06bcdae0062a1db77cb99f4b9cbb5f4ecb13c5146",
"sha256:17f9ade344e7d9b464a084d69bcf18fc691cb1db67c62ed80820bf4926d78f0e", "sha256:2fa8fa7697ad1646b5c93de1719965844e004fcad23c91228aca1cf0800044a1",
"sha256:1e254a00cdf42b1e4d5b3d68d33af63268d41340d8885df2ab6470f2e1500147", "sha256:31504f970f563d99f71a3512d0c01a645b692b12a63630d6aafa0939e52361e6",
"sha256:1e978ec1e8bd0e0e4de6bb75de9d30cbb74db6b6a2bb727618613703ca0167dd", "sha256:3387dd7232804b341165cedcb90694565a6015433ee076c6754775e85d86f1fc",
"sha256:25c692919ac5a01f170a3bfcd62d745b24fd095c353d50812637d6fcab442e75", "sha256:4ba5054787e89c59c593a4169830ab362ac2bee8a969249dc56e5d7d20ff8df9",
"sha256:260a5d70215b61ab4fadf5c7baacd64821842975eea312125ed3c39a6391b063", "sha256:4f92084defa704deadd4e0a5ab1dc52d8ac9e8a8ef617f3fbb853e79b0ea3592",
"sha256:2803abfebfc990042cd494d8ce2d5f82e9d847af6d35ec486923aa19dbad5e73", "sha256:65ef3468b53269eb5fdb3a5c09508c032b793da03251d5f8722b1194f1790c00",
"sha256:29a287e0cf63ff528da061de6b9f64a4618da591ca1046aafc54062e40ca7eab", "sha256:6f527d8fdb0286fd2fd97a2a96c6be17ba4232da346931d967a0630050dfd298",
"sha256:29cb7f67d10b479ff07c17d33e39f78c07f71c40ef30d63c153d340e96cd3fb4", "sha256:7051ee569db5fbac144335e0f3b9c2337e0c8d5c9fee015f259a5bd70772b7e8",
"sha256:3213d622a0283a39a93d188f3cf72b26862df52fbb4ca3697f51705016523d41", "sha256:7716e4a9b7af82c06a2543c53ca476fa0b57e4d760481273e09da04b74ee6ee2",
"sha256:33111801a01c12a8a1e3721f0a9232f8cfc8ae2c6b7098167e6f623c6073f402", "sha256:79bd5f0a02aa16808fcbc79a9a376a147cc1045f7dfe44c6e7d53fa8b8a79392",
"sha256:357cc07a6d7b0b182ff02249616a03742827ebb1277546b5c7cd7f7620a45698", "sha256:7a4e84a6283b36632e2a5b56e121961f6542ab886bc9e12f8f9818b3c266bfbb",
"sha256:38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7", "sha256:8120575cb4882318c791f839a4fd66161a6fa46f3f0a5e613071aae35b5dd8f8",
"sha256:4081eb135ac24158bd51cdfbef16f1c64df7063b1143f24731387137c092bec8", "sha256:81413336ef121a6ba746892fad881a83351ee3e1e4011f52e97fba79233611fd",
"sha256:40fdc1ae7125e518ea98e53e69a4ebc27e1fd50510c47b7ea130cf21e5e1d42b", "sha256:8146f3550d627252269ac42ae660281d673eb6f8b32f113538e0cc2a9aed42b9",
"sha256:4cfe66903cc32a9921a6733d96b19bb6abf310397581bbad89c228f5abaf0ee8", "sha256:879cf3a9a2b53a4672a168c21375166171bc3932b7e21f622201811c43cdd3b0",
"sha256:511dbaf848decaaaf4b4ca48032619fb3138710c4bf7da7617765edad1ef96b0", "sha256:892c10d6a73e0f14935c31229e03325a7b3093fafd6ce0af704be7f894d95687",
"sha256:55cced7c52e981362f708ad635198e97a752dfba412cc03c23bbf3bd8d5cd662", "sha256:92bda934a791c01d6d9d8e038363c50918ef7c40601552a58ac84c9613a665bc",
"sha256:56b39e5e0622a09a25bf5baf62f4bcf0cb8a41ae6e2819cf49bbc5a74c083f91", "sha256:9ba03692a45d3eef66559efe1d1096c4b9b75c0986b5dff5530c378fb8331d4f",
"sha256:5dbbdb29840ca3d91ee0fece42fc29278886d908280bfec0a5846c6f901a3eb0", "sha256:9eeea959168ea555e556b8188da5fa7831e21d91ce031e95ce23747b7609f8a4",
"sha256:5f9fb9157b4ce2971008323afe46053787b526ef624fea915b261468a8421a0f", "sha256:a0258ad1f44f138b791327961caedffbf9612bfa504ab9597157806faa95194a",
"sha256:6180d8b35af935aed8ece3a85e0a43f87393ae0ac87c8d2c8bd2c993f7270ef3", "sha256:a761ba0fa886a7bb33c6c8f6f20213735cb19642c580a931c625ee377ee8bd39",
"sha256:68a5124b13fa6cc2086764a20005d30bc0548146f7f5322f02fce212ca14317f", "sha256:a7b9084668aa0f64e64bd00d27ba5146ef1c3a8835f3bd912e7a9e01326804c4",
"sha256:68bb27509ac1b9a3443094260f6326150663b06abe40b73a2f81160623da5b67", "sha256:a84eda42bd12edc36eb5b53bbcc9b406820d3353f1994b6cfe453a33ff101775",
"sha256:6f41ae150c4e32db4f3310cdaf64b1593a03dbabe29eec77fc9b50fe64061df6", "sha256:ab2939cd5bec30a7430cbdb2287b63151b77cf9624de0532d629c9a1c59b1d5c",
"sha256:7265a2f3d436e54ef9f2b52b5c937e6be778781bd97a590319d7348f1c1ca997", "sha256:ac0280f1ba4a4bfff363a99a6aceed4f8e123f8a9b234c89140f5e894e452ecd",
"sha256:72fbe16c6fac95aedf5937fa873445cec2110be35d8a4e9433d7501fd98dae6b", "sha256:adf8c1d66f432ce577d0197dceaac2ac00c0759f573f28516246351c58a85020",
"sha256:7d92c3819208a60205a12a245c91ad70cb0a85336659b19b834205573ac8456e", "sha256:b4adfbbc64014976d2f91084915ca4e626fbf2057fb81af209c1a6d776d23e3d",
"sha256:8155154c7c691289fe18f510b5d4657c68c67989f293f0535a91360392ff6538", "sha256:bb649f8b207ab07caebba230d851b579a3c8711a851d29efe15008e31bb4de24",
"sha256:81a1cca95ed5bb92aa8b10dd2cdc9a0d3853a50fad926c28b5d7e8ea54389627", "sha256:bce43e386c16898b91e162e5baaad90c4b06f9dcbe36282490032cec98dc8ae7",
"sha256:89cd468399cfd2504718f0ba50e410dca55a170b61a02ad92bb18c8a65186e93", "sha256:bd3ad3b0a40e713fc68f99ecfd07124195333f1e689387c180813f0e94309d6f",
"sha256:8ad03c0965fb3c692200e74d458ca28c1dbb4ce96f9a479a8aa041ad5fabca02", "sha256:c3f7ac96b16955634e223b579a3e5798df59007ca43e8d451a0e6a50f6bfdfba",
"sha256:90f9849678c75fe7afa2d348ac842c168b0a4d3d61919687216dfc547976d853", "sha256:cf28633d64294969c019c6df4ff37f5698e8326db68cc2b66576a51fad634880",
"sha256:948424b06129ce883307e8cff868c31396d8dc7630a59c61d70d98dbe70f222c", "sha256:d0f35b19894a9e08639fd60a1ec1978cb7f5f7f1eace62f38dd36be8aecdef4d",
"sha256:9cd5ffd25db4e7ba6a375693b3fc0fc1791ec636c17db3720da19bde7180ec43", "sha256:db1f1c22173ac1c58db249ae48aa7ead29f534b9a948bc56828337aa84a32ed6",
"sha256:a0df0043bdb289bde1f62da130d20df23d58b45429f752bc7a8fc5325a225ecd", "sha256:dbe512c511956b893d2dacd007d955a3f03d555ae05cfa3ff1c1ff6df8851854",
"sha256:a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8", "sha256:df2f57871a96bbc1b69733cd4c51dc33bea66146b8c63cacbfed73eec0883017",
"sha256:a7830bab239b79cda9c08c2da014761cafb48da6150e1da17ac06283f43b6089", "sha256:e2f085ce2e813a50dfd0e01fbfc0c12bbe5d2063d99f8b29da30e544fb6483b8",
"sha256:a7c711e21628b52034bb5ab8d1bce291f752fcc5e92accc615778acee1ff4778", "sha256:e642d86b8f956098b564a45e6f6ce68a22c2c97a04f5acd3f221f57b8cb850ae",
"sha256:aaf159caa35993cb1f56fb9b8e4610d35758e7ca005412eb1daa856a78c9c4b1", "sha256:e9e0a277bb2eb5d8a7407e14688b85fd8ad628ee4e0c7930415687b6564207a4",
"sha256:ae506e6902902557576a26ff33eda8695e7ecb3cb36c3b573a0765dee114ebdb", "sha256:ea2bb7e2ae9e37d96835b3576a4fa4b3a97592fbea8ef7c3587078b0068b8f09",
"sha256:b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261", "sha256:ee4d528022f4c5ff67332469e10efe06a267e32f4067dc76bb7e2cddf3cd25ff",
"sha256:bf162abab1c1a736333192707cef898e735a5ca00f38f27eeedf44b39d9e85eb", "sha256:f05d4198c1bacc9124018109c5fba2f3201dbe7ab6e92ff100494f236209c960",
"sha256:c1a2af6c6ef86344a6b0db6b97834208bf598db514f2b155042439b62605601a", "sha256:f34dc300df798742b3d06515aa2a0aee20941c13579d7a2f2e10af01ae4901ee",
"sha256:c2d37ab77531417474168eb79d6d80b14f821a966818505d03013d0833edb7a8", "sha256:f4162988a360a29af158aeb4a2f4f09ffed6a969c9776f8f3bdee9b06a8ab7e5",
"sha256:c4fc99836233ea196540b17ab0983aff60ed07941751930f5f4d05bc3b3b7359", "sha256:f486038e44caa08dbd97275a9a35a283a8f1d2f0ee60ac260a1790e76660833c",
"sha256:d581b735e177fdcdce6fed8e7e8880a3fb6ee4e3653a3ac6af01c6f4c03effc5", "sha256:f7de08cbe5551911886d1ab60de58448c6df0f67d9feb7d1fb21e9875ef95e91"
"sha256:d6da64deb6b8ed903e7560180a92f2d804ee1ba5eeb849ac2748b8c1aba1f6d7",
"sha256:d8e8286dd7cea7895157318d1b91cdacac64c479f3cbc8dce548331728484751",
"sha256:ddea102b48f9e339f3948bf22040944184627a30fdf7f858667673b9c5f033c8",
"sha256:dfa20cc6ca228e6b155b11da03825975ce66aea520985dbbddf0f2a5a495c605",
"sha256:e3e5193ef5a3dc73bceee50f7fdc2c90dbb76c42df8d8fae3d1067a583df579e",
"sha256:e3eeb0aabd6bd5ce64faae67e9935203a6991b4bc2a485a767fbafb2c5125f45",
"sha256:e5805d5a22fd19c8ccff10a9561f9df94436b0545619ea579db2d3c35294bce2",
"sha256:e85b752a1e912b70eaad4fafbd4d1238007ab221de2009b9a2f5ae7461239895",
"sha256:eaf7fa2de5c0be8ae6ff8e9bea2ccd725e980541244521d8d4b5f3354a27babe",
"sha256:ebfb099f8dcf083deef3ac1ca4c1503f387cf76296fcb3816b66f5ecb5f54fdb",
"sha256:ece3d2cfe132e7d51f44a832b303895e6f2d499c5e74dfbdb06ee246147a304a",
"sha256:ed9749eef4cbd126da3dc1d6bcb3a57f5eb7ac6a6484146bdbf743f552dfc577",
"sha256:ede83e07a75dd06bc501566c1eca2afc0d61677c1472ac9ad93fdee6e638a48d",
"sha256:ef4aea96ce4d3b074422cb4f2f64e216bf9e213004bb58ecfdf50ea02ea8eb9a",
"sha256:f3a3570c4a2a16746ac2c31a7c7c7b0c186b95ce902e33db6f28094ed7387dda",
"sha256:f407cb6b8e9d6d8c626bc73c945db1706035af8fd632295547bf1c9e46d092d6",
"sha256:f74a575920ab21fe304421a3fc28793d82e299cae9eccb37084e9fc7f3617c20"
], ],
"index": "pypi", "index": "pypi",
"markers": "python_version >= '3.11'", "markers": "python_version >= '3.10'",
"version": "==2.4.6" "version": "==2.2.4"
}, },
"pycodestyle": { "pycodestyle": {
"hashes": [ "hashes": [
+70 -127
View File
@@ -1,70 +1,18 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import {fileURLToPath} from 'url';
import fs from 'fs'; import fs from 'fs';
import * as path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); import {HttpClient} from '@actions/http-client';
import * as ifm from '@actions/http-client/lib/interfaces';
import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec';
import * as core from '@actions/core';
import * as path from 'path';
import * as semver from 'semver'; import * as semver from 'semver';
// Mock @actions modules import * as finder from '../src/find-graalpy';
jest.unstable_mockModule('@actions/core', () => ({ import {IGraalPyManifestRelease} from '../src/utils';
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
jest.unstable_mockModule('@actions/tool-cache', () => ({ import manifestData from './data/graalpy.json';
find: jest.fn(),
findAllVersions: jest.fn(),
downloadTool: jest.fn(),
extractZip: jest.fn(),
extractTar: jest.fn(),
extract7z: jest.fn(),
extractXar: jest.fn(),
cacheDir: jest.fn(),
cacheFile: jest.fn(),
getManifestFromRepo: jest.fn(),
findFromManifest: jest.fn(),
evaluateVersions: jest.fn()
}));
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
// Dynamic imports after mocking
const core = await import('@actions/core');
const tc = await import('@actions/tool-cache');
const exec = await import('@actions/exec');
const finder = await import('../src/find-graalpy.js');
const utils = await import('../src/utils.js');
// Non-mocked imports
import {HttpClient} from '@actions/http-client';
import type * as ifm from '@actions/http-client/lib/interfaces';
import type {IGraalPyManifestRelease} from '../src/utils.js';
import manifestData from './data/graalpy.json' with {type: 'json'};
const architecture = 'x64'; const architecture = 'x64';
@@ -93,41 +41,39 @@ describe('parseGraalPyVersion', () => {
describe('findGraalPyToolCache', () => { describe('findGraalPyToolCache', () => {
const actualGraalPyVersion = '23.0.0'; const actualGraalPyVersion = '23.0.0';
const graalpyPath = path.join('GraalPy', actualGraalPyVersion, architecture); const graalpyPath = path.join('GraalPy', actualGraalPyVersion, architecture);
let tcFind: jest.Mock; let tcFind: jest.SpyInstance;
let infoSpy: jest.Mock; let infoSpy: jest.SpyInstance;
let warningSpy: jest.Mock; let warningSpy: jest.SpyInstance;
let debugSpy: jest.Mock; let debugSpy: jest.SpyInstance;
let addPathSpy: jest.Mock; let addPathSpy: jest.SpyInstance;
let exportVariableSpy: jest.Mock; let exportVariableSpy: jest.SpyInstance;
let setOutputSpy: jest.Mock; let setOutputSpy: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
tcFind = tc.find as jest.Mock; tcFind = jest.spyOn(tc, 'find');
(tcFind as jest.Mock<typeof tc.find>).mockImplementation( tcFind.mockImplementation((toolname: string, pythonVersion: string) => {
(toolname: string, pythonVersion: string) => {
const semverVersion = new semver.Range(pythonVersion); const semverVersion = new semver.Range(pythonVersion);
return semver.satisfies(actualGraalPyVersion, semverVersion) return semver.satisfies(actualGraalPyVersion, semverVersion)
? graalpyPath ? graalpyPath
: ''; : '';
} });
);
infoSpy = core.info as jest.Mock; infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => null); infoSpy.mockImplementation(() => null);
warningSpy = core.warning as jest.Mock; warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => null); warningSpy.mockImplementation(() => null);
debugSpy = core.debug as jest.Mock; debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => null); debugSpy.mockImplementation(() => null);
addPathSpy = core.addPath as jest.Mock; addPathSpy = jest.spyOn(core, 'addPath');
addPathSpy.mockImplementation(() => null); addPathSpy.mockImplementation(() => null);
exportVariableSpy = core.exportVariable as jest.Mock; exportVariableSpy = jest.spyOn(core, 'exportVariable');
exportVariableSpy.mockImplementation(() => null); exportVariableSpy.mockImplementation(() => null);
setOutputSpy = core.setOutput as jest.Mock; setOutputSpy = jest.spyOn(core, 'setOutput');
setOutputSpy.mockImplementation(() => null); setOutputSpy.mockImplementation(() => null);
}); });
@@ -160,74 +106,73 @@ describe('findGraalPyToolCache', () => {
}); });
describe('findGraalPyVersion', () => { describe('findGraalPyVersion', () => {
let getBooleanInputSpy: jest.Mock; let getBooleanInputSpy: jest.SpyInstance;
let warningSpy: jest.Mock; let warningSpy: jest.SpyInstance;
let debugSpy: jest.Mock; let debugSpy: jest.SpyInstance;
let infoSpy: jest.Mock; let infoSpy: jest.SpyInstance;
let addPathSpy: jest.Mock; let addPathSpy: jest.SpyInstance;
let exportVariableSpy: jest.Mock; let exportVariableSpy: jest.SpyInstance;
let setOutputSpy: jest.Mock; let setOutputSpy: jest.SpyInstance;
let tcFind: jest.Mock; let tcFind: jest.SpyInstance;
let spyExtractZip: jest.Mock; let spyExtractZip: jest.SpyInstance;
let spyExtractTar: jest.Mock; let spyExtractTar: jest.SpyInstance;
let spyHttpClient: jest.SpiedFunction<typeof HttpClient.prototype.getJson>; let spyHttpClient: jest.SpyInstance;
let spyExistsSync: jest.SpiedFunction<typeof fs.existsSync>; let spyExistsSync: jest.SpyInstance;
let spyExec: jest.Mock; let spyExec: jest.SpyInstance;
let spySymlinkSync: jest.SpiedFunction<typeof fs.symlinkSync>; let spySymlinkSync: jest.SpyInstance;
let spyDownloadTool: jest.Mock; let spyDownloadTool: jest.SpyInstance;
let spyFsReadDir: jest.SpiedFunction<typeof fs.readdirSync>; let spyFsReadDir: jest.SpyInstance;
let spyCacheDir: jest.Mock; let spyCacheDir: jest.SpyInstance;
let spyChmodSync: jest.SpiedFunction<typeof fs.chmodSync>; let spyChmodSync: jest.SpyInstance;
let spyCoreAddPath: jest.Mock; let spyCoreAddPath: jest.SpyInstance;
let spyCoreExportVariable: jest.Mock; let spyCoreExportVariable: jest.SpyInstance;
const env = process.env; const env = process.env;
beforeEach(() => { beforeEach(() => {
getBooleanInputSpy = core.getBooleanInput as jest.Mock; getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(() => false); getBooleanInputSpy.mockImplementation(() => false);
infoSpy = core.info as jest.Mock; infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => {}); infoSpy.mockImplementation(() => {});
warningSpy = core.warning as jest.Mock; warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => null); warningSpy.mockImplementation(() => null);
debugSpy = core.debug as jest.Mock; debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => null); debugSpy.mockImplementation(() => null);
addPathSpy = core.addPath as jest.Mock; addPathSpy = jest.spyOn(core, 'addPath');
addPathSpy.mockImplementation(() => null); addPathSpy.mockImplementation(() => null);
exportVariableSpy = core.exportVariable as jest.Mock; exportVariableSpy = jest.spyOn(core, 'exportVariable');
exportVariableSpy.mockImplementation(() => null); exportVariableSpy.mockImplementation(() => null);
setOutputSpy = core.setOutput as jest.Mock; setOutputSpy = jest.spyOn(core, 'setOutput');
setOutputSpy.mockImplementation(() => null); setOutputSpy.mockImplementation(() => null);
jest.resetModules();
process.env = {...env}; process.env = {...env};
tcFind = tc.find as jest.Mock; tcFind = jest.spyOn(tc, 'find');
(tcFind as jest.Mock<typeof tc.find>).mockImplementation( tcFind.mockImplementation((tool: string, version: string) => {
(tool: string, version: string) => {
const semverRange = new semver.Range(version); const semverRange = new semver.Range(version);
let graalpyPath = ''; let graalpyPath = '';
if (semver.satisfies('23.0.0', semverRange)) { if (semver.satisfies('23.0.0', semverRange)) {
graalpyPath = path.join(toolDir, 'GraalPy', '23.0.0', architecture); graalpyPath = path.join(toolDir, 'GraalPy', '23.0.0', architecture);
} }
return graalpyPath; return graalpyPath;
} });
);
spyDownloadTool = tc.downloadTool as jest.Mock; spyDownloadTool = jest.spyOn(tc, 'downloadTool');
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'GraalPy')); spyDownloadTool.mockImplementation(() => path.join(tempDir, 'GraalPy'));
spyExtractZip = tc.extractZip as jest.Mock; spyExtractZip = jest.spyOn(tc, 'extractZip');
spyExtractZip.mockImplementation(() => tempDir); spyExtractZip.mockImplementation(() => tempDir);
spyExtractTar = tc.extractTar as jest.Mock; spyExtractTar = jest.spyOn(tc, 'extractTar');
spyExtractTar.mockImplementation(() => tempDir); spyExtractTar.mockImplementation(() => tempDir);
spyFsReadDir = jest.spyOn(fs, 'readdirSync'); spyFsReadDir = jest.spyOn(fs, 'readdirSync');
spyFsReadDir.mockImplementation(() => ['GraalPyTest'] as any); spyFsReadDir.mockImplementation((directory: string) => ['GraalPyTest']);
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson'); spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
spyHttpClient.mockImplementation( spyHttpClient.mockImplementation(
@@ -241,7 +186,7 @@ describe('findGraalPyVersion', () => {
} }
); );
spyExec = exec.exec as jest.Mock; spyExec = jest.spyOn(exec, 'exec');
spyExec.mockImplementation(() => undefined); spyExec.mockImplementation(() => undefined);
spySymlinkSync = jest.spyOn(fs, 'symlinkSync'); spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
@@ -250,9 +195,9 @@ describe('findGraalPyVersion', () => {
spyExistsSync = jest.spyOn(fs, 'existsSync'); spyExistsSync = jest.spyOn(fs, 'existsSync');
spyExistsSync.mockReturnValue(true); spyExistsSync.mockReturnValue(true);
spyCoreAddPath = core.addPath as jest.Mock; spyCoreAddPath = jest.spyOn(core, 'addPath');
spyCoreExportVariable = core.exportVariable as jest.Mock; spyCoreExportVariable = jest.spyOn(core, 'exportVariable');
}); });
afterEach(() => { afterEach(() => {
@@ -290,7 +235,7 @@ describe('findGraalPyVersion', () => {
}); });
it('found and install successfully', async () => { it('found and install successfully', async () => {
spyCacheDir = tc.cacheDir as jest.Mock; spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() => spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '23.0.0', architecture) path.join(toolDir, 'GraalPy', '23.0.0', architecture)
); );
@@ -317,7 +262,7 @@ describe('findGraalPyVersion', () => {
}); });
it('found and install successfully without environment update', async () => { it('found and install successfully without environment update', async () => {
spyCacheDir = tc.cacheDir as jest.Mock; spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() => spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '23.0.0', architecture) path.join(toolDir, 'GraalPy', '23.0.0', architecture)
); );
@@ -365,7 +310,7 @@ describe('findGraalPyVersion', () => {
}); });
it('check-latest enabled version found and install successfully', async () => { it('check-latest enabled version found and install successfully', async () => {
spyCacheDir = tc.cacheDir as jest.Mock; spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() => spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '23.0.0', architecture) path.join(toolDir, 'GraalPy', '23.0.0', architecture)
); );
@@ -384,16 +329,14 @@ describe('findGraalPyVersion', () => {
}); });
it('check-latest enabled version is not found and used from toolcache', async () => { it('check-latest enabled version is not found and used from toolcache', async () => {
(tcFind as jest.Mock<typeof tc.find>).mockImplementationOnce( tcFind.mockImplementationOnce((tool: string, version: string) => {
(tool: string, version: string) => {
const semverRange = new semver.Range(version); const semverRange = new semver.Range(version);
let graalpyPath = ''; let graalpyPath = '';
if (semver.satisfies('22.3.4', semverRange)) { if (semver.satisfies('22.3.4', semverRange)) {
graalpyPath = path.join(toolDir, 'GraalPy', '22.3.4', architecture); graalpyPath = path.join(toolDir, 'GraalPy', '22.3.4', architecture);
} }
return graalpyPath; return graalpyPath;
} });
);
await expect( await expect(
finder.findGraalPyVersion( finder.findGraalPyVersion(
'graalpy-22.3.4', 'graalpy-22.3.4',
@@ -410,7 +353,7 @@ describe('findGraalPyVersion', () => {
}); });
it('found and install successfully, pre-release fallback', async () => { it('found and install successfully, pre-release fallback', async () => {
spyCacheDir = tc.cacheDir as jest.Mock; spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() => spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '24.1', architecture) path.join(toolDir, 'GraalPy', '24.1', architecture)
); );
+84 -146
View File
@@ -1,83 +1,23 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import {fileURLToPath} from 'url';
import fs from 'fs'; import fs from 'fs';
import * as path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); import * as utils from '../src/utils';
import {HttpClient} from '@actions/http-client';
import * as ifm from '@actions/http-client/lib/interfaces';
import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec';
import * as core from '@actions/core';
import * as path from 'path';
import * as semver from 'semver'; import * as semver from 'semver';
// Mock @actions modules import * as finder from '../src/find-pypy';
jest.unstable_mockModule('@actions/core', () => ({ import {
info: jest.fn(), IPyPyManifestRelease,
warning: jest.fn(), IS_WINDOWS,
debug: jest.fn(), getPyPyVersionFromPath
error: jest.fn(), } from '../src/utils';
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
jest.unstable_mockModule('@actions/tool-cache', () => ({ import manifestData from './data/pypy.json';
find: jest.fn(),
findAllVersions: jest.fn(),
downloadTool: jest.fn(),
extractZip: jest.fn(),
extractTar: jest.fn(),
extract7z: jest.fn(),
extractXar: jest.fn(),
cacheDir: jest.fn(),
cacheFile: jest.fn(),
getManifestFromRepo: jest.fn(),
findFromManifest: jest.fn(),
evaluateVersions: jest.fn()
}));
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
// Import real utils BEFORE mock registration to get real function references
const realUtils = await import('../src/utils.js');
// Mock local utils module for readExactPyPyVersionFile/writeExactPyPyVersionFile
jest.unstable_mockModule('../src/utils.js', () => ({
...realUtils,
readExactPyPyVersionFile: jest.fn(),
writeExactPyPyVersionFile: jest.fn()
}));
// Dynamic imports after mocking
const core = await import('@actions/core');
const tc = await import('@actions/tool-cache');
const exec = await import('@actions/exec');
const utils = await import('../src/utils.js');
const finder = await import('../src/find-pypy.js');
// Non-mocked imports
import {HttpClient} from '@actions/http-client';
import type * as ifm from '@actions/http-client/lib/interfaces';
import type {IPyPyManifestRelease} from '../src/utils.js';
import manifestData from './data/pypy.json' with {type: 'json'};
const IS_WINDOWS = utils.IS_WINDOWS;
const getPyPyVersionFromPath = utils.getPyPyVersionFromPath;
let architecture: string; let architecture: string;
@@ -139,45 +79,43 @@ describe('findPyPyToolCache', () => {
const actualPythonVersion = '3.6.17'; const actualPythonVersion = '3.6.17';
const actualPyPyVersion = '7.5.4'; const actualPyPyVersion = '7.5.4';
const pypyPath = path.join('PyPy', actualPythonVersion, architecture); const pypyPath = path.join('PyPy', actualPythonVersion, architecture);
let tcFind: jest.Mock; let tcFind: jest.SpyInstance;
let spyReadExactPyPyVersion: jest.Mock; let spyReadExactPyPyVersion: jest.SpyInstance;
let infoSpy: jest.Mock; let infoSpy: jest.SpyInstance;
let warningSpy: jest.Mock; let warningSpy: jest.SpyInstance;
let debugSpy: jest.Mock; let debugSpy: jest.SpyInstance;
let addPathSpy: jest.Mock; let addPathSpy: jest.SpyInstance;
let exportVariableSpy: jest.Mock; let exportVariableSpy: jest.SpyInstance;
let setOutputSpy: jest.Mock; let setOutputSpy: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
tcFind = tc.find as jest.Mock; tcFind = jest.spyOn(tc, 'find');
(tcFind as jest.Mock<typeof tc.find>).mockImplementation( tcFind.mockImplementation((toolname: string, pythonVersion: string) => {
(toolname: string, pythonVersion: string) => {
const semverVersion = new semver.Range(pythonVersion); const semverVersion = new semver.Range(pythonVersion);
return semver.satisfies(actualPythonVersion, semverVersion) return semver.satisfies(actualPythonVersion, semverVersion)
? pypyPath ? pypyPath
: ''; : '';
} });
);
spyReadExactPyPyVersion = utils.readExactPyPyVersionFile as jest.Mock; spyReadExactPyPyVersion = jest.spyOn(utils, 'readExactPyPyVersionFile');
spyReadExactPyPyVersion.mockImplementation(() => actualPyPyVersion); spyReadExactPyPyVersion.mockImplementation(() => actualPyPyVersion);
infoSpy = core.info as jest.Mock; infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => null); infoSpy.mockImplementation(() => null);
warningSpy = core.warning as jest.Mock; warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => null); warningSpy.mockImplementation(() => null);
debugSpy = core.debug as jest.Mock; debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => null); debugSpy.mockImplementation(() => null);
addPathSpy = core.addPath as jest.Mock; addPathSpy = jest.spyOn(core, 'addPath');
addPathSpy.mockImplementation(() => null); addPathSpy.mockImplementation(() => null);
exportVariableSpy = core.exportVariable as jest.Mock; exportVariableSpy = jest.spyOn(core, 'exportVariable');
exportVariableSpy.mockImplementation(() => null); exportVariableSpy.mockImplementation(() => null);
setOutputSpy = core.setOutput as jest.Mock; setOutputSpy = jest.spyOn(core, 'setOutput');
setOutputSpy.mockImplementation(() => null); setOutputSpy.mockImplementation(() => null);
}); });
@@ -221,82 +159,84 @@ describe('findPyPyToolCache', () => {
}); });
describe('findPyPyVersion', () => { describe('findPyPyVersion', () => {
let getBooleanInputSpy: jest.Mock; let getBooleanInputSpy: jest.SpyInstance;
let warningSpy: jest.Mock; let warningSpy: jest.SpyInstance;
let debugSpy: jest.Mock; let debugSpy: jest.SpyInstance;
let infoSpy: jest.Mock; let infoSpy: jest.SpyInstance;
let addPathSpy: jest.Mock; let addPathSpy: jest.SpyInstance;
let exportVariableSpy: jest.Mock; let exportVariableSpy: jest.SpyInstance;
let setOutputSpy: jest.Mock; let setOutputSpy: jest.SpyInstance;
let tcFind: jest.Mock; let tcFind: jest.SpyInstance;
let spyExtractZip: jest.Mock; let spyExtractZip: jest.SpyInstance;
let spyExtractTar: jest.Mock; let spyExtractTar: jest.SpyInstance;
let spyHttpClient: jest.SpiedFunction<typeof HttpClient.prototype.getJson>; let spyHttpClient: jest.SpyInstance;
let spyExistsSync: jest.SpiedFunction<typeof fs.existsSync>; let spyExistsSync: jest.SpyInstance;
let spyExec: jest.Mock; let spyExec: jest.SpyInstance;
let spySymlinkSync: jest.SpiedFunction<typeof fs.symlinkSync>; let spySymlinkSync: jest.SpyInstance;
let spyDownloadTool: jest.Mock; let spyDownloadTool: jest.SpyInstance;
let spyReadExactPyPyVersion: jest.Mock; let spyReadExactPyPyVersion: jest.SpyInstance;
let spyFsReadDir: jest.SpiedFunction<typeof fs.readdirSync>; let spyFsReadDir: jest.SpyInstance;
let spyWriteExactPyPyVersionFile: jest.Mock; let spyWriteExactPyPyVersionFile: jest.SpyInstance;
let spyCacheDir: jest.Mock; let spyCacheDir: jest.SpyInstance;
let spyChmodSync: jest.SpiedFunction<typeof fs.chmodSync>; let spyChmodSync: jest.SpyInstance;
let spyCoreAddPath: jest.Mock; let spyCoreAddPath: jest.SpyInstance;
let spyCoreExportVariable: jest.Mock; let spyCoreExportVariable: jest.SpyInstance;
const env = process.env; const env = process.env;
beforeEach(() => { beforeEach(() => {
getBooleanInputSpy = core.getBooleanInput as jest.Mock; getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(() => false); getBooleanInputSpy.mockImplementation(() => false);
infoSpy = core.info as jest.Mock; infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => {}); infoSpy.mockImplementation(() => {});
warningSpy = core.warning as jest.Mock; warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => null); warningSpy.mockImplementation(() => null);
debugSpy = core.debug as jest.Mock; debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => null); debugSpy.mockImplementation(() => null);
addPathSpy = core.addPath as jest.Mock; addPathSpy = jest.spyOn(core, 'addPath');
addPathSpy.mockImplementation(() => null); addPathSpy.mockImplementation(() => null);
exportVariableSpy = core.exportVariable as jest.Mock; exportVariableSpy = jest.spyOn(core, 'exportVariable');
exportVariableSpy.mockImplementation(() => null); exportVariableSpy.mockImplementation(() => null);
setOutputSpy = core.setOutput as jest.Mock; setOutputSpy = jest.spyOn(core, 'setOutput');
setOutputSpy.mockImplementation(() => null); setOutputSpy.mockImplementation(() => null);
jest.resetModules();
process.env = {...env}; process.env = {...env};
tcFind = tc.find as jest.Mock; tcFind = jest.spyOn(tc, 'find');
(tcFind as jest.Mock<typeof tc.find>).mockImplementation( tcFind.mockImplementation((tool: string, version: string) => {
(tool: string, version: string) => {
const semverRange = new semver.Range(version); const semverRange = new semver.Range(version);
let pypyPath = ''; let pypyPath = '';
if (semver.satisfies('3.6.12', semverRange)) { if (semver.satisfies('3.6.12', semverRange)) {
pypyPath = path.join(toolDir, 'PyPy', '3.6.12', architecture); pypyPath = path.join(toolDir, 'PyPy', '3.6.12', architecture);
} }
return pypyPath; return pypyPath;
} });
);
spyWriteExactPyPyVersionFile = utils.writeExactPyPyVersionFile as jest.Mock; spyWriteExactPyPyVersionFile = jest.spyOn(
utils,
'writeExactPyPyVersionFile'
);
spyWriteExactPyPyVersionFile.mockImplementation(() => null); spyWriteExactPyPyVersionFile.mockImplementation(() => null);
spyReadExactPyPyVersion = utils.readExactPyPyVersionFile as jest.Mock; spyReadExactPyPyVersion = jest.spyOn(utils, 'readExactPyPyVersionFile');
spyReadExactPyPyVersion.mockImplementation(() => '7.3.3'); spyReadExactPyPyVersion.mockImplementation(() => '7.3.3');
spyDownloadTool = tc.downloadTool as jest.Mock; spyDownloadTool = jest.spyOn(tc, 'downloadTool');
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'PyPy')); spyDownloadTool.mockImplementation(() => path.join(tempDir, 'PyPy'));
spyExtractZip = tc.extractZip as jest.Mock; spyExtractZip = jest.spyOn(tc, 'extractZip');
spyExtractZip.mockImplementation(() => tempDir); spyExtractZip.mockImplementation(() => tempDir);
spyExtractTar = tc.extractTar as jest.Mock; spyExtractTar = jest.spyOn(tc, 'extractTar');
spyExtractTar.mockImplementation(() => tempDir); spyExtractTar.mockImplementation(() => tempDir);
spyFsReadDir = jest.spyOn(fs, 'readdirSync'); spyFsReadDir = jest.spyOn(fs, 'readdirSync');
spyFsReadDir.mockImplementation(() => ['PyPyTest'] as any); spyFsReadDir.mockImplementation((directory: string) => ['PyPyTest']);
spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson'); spyHttpClient = jest.spyOn(HttpClient.prototype, 'getJson');
spyHttpClient.mockImplementation( spyHttpClient.mockImplementation(
@@ -310,7 +250,7 @@ describe('findPyPyVersion', () => {
} }
); );
spyExec = exec.exec as jest.Mock; spyExec = jest.spyOn(exec, 'exec');
spyExec.mockImplementation(() => undefined); spyExec.mockImplementation(() => undefined);
spySymlinkSync = jest.spyOn(fs, 'symlinkSync'); spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
@@ -319,9 +259,9 @@ describe('findPyPyVersion', () => {
spyExistsSync = jest.spyOn(fs, 'existsSync'); spyExistsSync = jest.spyOn(fs, 'existsSync');
spyExistsSync.mockReturnValue(true); spyExistsSync.mockReturnValue(true);
spyCoreAddPath = core.addPath as jest.Mock; spyCoreAddPath = jest.spyOn(core, 'addPath');
spyCoreExportVariable = core.exportVariable as jest.Mock; spyCoreExportVariable = jest.spyOn(core, 'exportVariable');
}); });
afterEach(() => { afterEach(() => {
@@ -368,7 +308,7 @@ describe('findPyPyVersion', () => {
}); });
it('found and install successfully', async () => { it('found and install successfully', async () => {
spyCacheDir = tc.cacheDir as jest.Mock; spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() => spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'PyPy', '3.7.7', architecture) path.join(toolDir, 'PyPy', '3.7.7', architecture)
); );
@@ -398,7 +338,7 @@ describe('findPyPyVersion', () => {
}); });
it('found and install successfully without environment update', async () => { it('found and install successfully without environment update', async () => {
spyCacheDir = tc.cacheDir as jest.Mock; spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() => spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'PyPy', '3.7.7', architecture) path.join(toolDir, 'PyPy', '3.7.7', architecture)
); );
@@ -454,7 +394,7 @@ describe('findPyPyVersion', () => {
}); });
it('check-latest enabled version found and install successfully', async () => { it('check-latest enabled version found and install successfully', async () => {
spyCacheDir = tc.cacheDir as jest.Mock; spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() => spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'PyPy', '3.7.7', architecture) path.join(toolDir, 'PyPy', '3.7.7', architecture)
); );
@@ -478,16 +418,14 @@ describe('findPyPyVersion', () => {
}); });
it('check-latest enabled version is not found and used from toolcache', async () => { it('check-latest enabled version is not found and used from toolcache', async () => {
(tcFind as jest.Mock<typeof tc.find>).mockImplementationOnce( tcFind.mockImplementationOnce((tool: string, version: string) => {
(tool: string, version: string) => {
const semverRange = new semver.Range(version); const semverRange = new semver.Range(version);
let pypyPath = ''; let pypyPath = '';
if (semver.satisfies('3.8.8', semverRange)) { if (semver.satisfies('3.8.8', semverRange)) {
pypyPath = path.join(toolDir, 'PyPy', '3.8.8', architecture); pypyPath = path.join(toolDir, 'PyPy', '3.8.8', architecture);
} }
return pypyPath; return pypyPath;
} });
);
await expect( await expect(
finder.findPyPyVersion( finder.findPyPyVersion(
'pypy-3.8-v7.3.x', 'pypy-3.8-v7.3.x',
@@ -507,7 +445,7 @@ describe('findPyPyVersion', () => {
}); });
it('found and install successfully, pre-release fallback', async () => { it('found and install successfully, pre-release fallback', async () => {
spyCacheDir = tc.cacheDir as jest.Mock; spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() => spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'PyPy', '3.8.12', architecture) path.join(toolDir, 'PyPy', '3.8.12', architecture)
); );
+1 -2
View File
@@ -1,5 +1,4 @@
import {describe, it, expect} from '@jest/globals'; import {desugarVersion, pythonVersionToSemantic} from '../src/find-python';
import {desugarVersion, pythonVersionToSemantic} from '../src/find-python.js';
describe('desugarVersion', () => { describe('desugarVersion', () => {
it.each([ it.each([
+59 -97
View File
@@ -1,12 +1,8 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import {fileURLToPath} from 'url';
import * as io from '@actions/io'; import * as io from '@actions/io';
import os from 'os'; import os from 'os';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const toolDir = path.join( const toolDir = path.join(
__dirname, __dirname,
'runner', 'runner',
@@ -23,78 +19,26 @@ const tempDir = path.join(
process.env['RUNNER_TOOL_CACHE'] = toolDir; process.env['RUNNER_TOOL_CACHE'] = toolDir;
process.env['RUNNER_TEMP'] = tempDir; process.env['RUNNER_TEMP'] = tempDir;
// Mock @actions modules import * as tc from '@actions/tool-cache';
jest.unstable_mockModule('@actions/core', () => ({ import * as core from '@actions/core';
info: jest.fn(), import * as finder from '../src/find-python';
warning: jest.fn(), import * as installer from '../src/install-python';
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
// Pre-import real @actions/tool-cache before any mocks import manifestData from './data/versions-manifest.json';
const realTc = await import('@actions/tool-cache');
jest.unstable_mockModule('@actions/tool-cache', () => ({
...realTc,
find: jest.fn(realTc.find),
getManifestFromRepo: jest.fn()
}));
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
// Pre-import real install-python AFTER all its dependency mocks are registered
// so it captures the mocked @actions/tool-cache, @actions/core, @actions/exec
const realInstaller = await import('../src/install-python.js');
// Mock local install-python module - keep real getManifest/findReleaseFromManifest
jest.unstable_mockModule('../src/install-python.js', () => ({
...realInstaller,
installCpythonFromRelease: jest.fn()
}));
// Dynamic imports after mocking
const core = await import('@actions/core');
const tc = await import('@actions/tool-cache');
const finder = await import('../src/find-python.js');
const installer = await import('../src/install-python.js');
import manifestData from './data/versions-manifest.json' with {type: 'json'};
describe('Finder tests', () => { describe('Finder tests', () => {
let writeSpy: jest.SpiedFunction<typeof process.stdout.write>; let writeSpy: jest.SpyInstance;
let spyCoreAddPath: jest.Mock; let spyCoreAddPath: jest.SpyInstance;
let spyCoreExportVariable: jest.Mock; let spyCoreExportVariable: jest.SpyInstance;
const env = process.env; const env = process.env;
beforeEach(() => { beforeEach(() => {
writeSpy = jest.spyOn(process.stdout, 'write'); writeSpy = jest.spyOn(process.stdout, 'write');
writeSpy.mockImplementation(() => true); writeSpy.mockImplementation(() => {});
jest.resetModules();
process.env = {...env}; process.env = {...env};
spyCoreAddPath = core.addPath as jest.Mock; spyCoreAddPath = jest.spyOn(core, 'addPath');
spyCoreExportVariable = core.exportVariable as jest.Mock; spyCoreExportVariable = jest.spyOn(core, 'exportVariable');
// Restore real tc.find default (cleared by jest.resetAllMocks)
(tc.find as jest.Mock).mockImplementation(realTc.find as any);
}); });
afterEach(() => { afterEach(() => {
@@ -105,8 +49,8 @@ describe('Finder tests', () => {
}); });
it('Finds Python if it is installed', async () => { it('Finds Python if it is installed', async () => {
const getBooleanInputSpy = core.getBooleanInput as jest.Mock; const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(() => false); getBooleanInputSpy.mockImplementation(input => false);
const pythonDir: string = path.join(toolDir, 'Python', '3.0.0', 'x64'); const pythonDir: string = path.join(toolDir, 'Python', '3.0.0', 'x64');
await io.mkdirP(pythonDir); await io.mkdirP(pythonDir);
@@ -135,13 +79,16 @@ describe('Finder tests', () => {
}); });
it('Finds stable Python version if it is not installed, but exists in the manifest', async () => { it('Finds stable Python version if it is not installed, but exists in the manifest', async () => {
const findSpy = tc.getManifestFromRepo as jest.Mock; const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
findSpy.mockImplementation(() => manifestData); findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
const getBooleanInputSpy = core.getBooleanInput as jest.Mock; const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(() => false); getBooleanInputSpy.mockImplementation(input => false);
const installSpy = installer.installCpythonFromRelease as jest.Mock; const installSpy: jest.SpyInstance = jest.spyOn(
installer,
'installCpythonFromRelease'
);
installSpy.mockImplementation(async () => { installSpy.mockImplementation(async () => {
const pythonDir: string = path.join(toolDir, 'Python', '1.2.3', 'x64'); const pythonDir: string = path.join(toolDir, 'Python', '1.2.3', 'x64');
await io.mkdirP(pythonDir); await io.mkdirP(pythonDir);
@@ -166,13 +113,16 @@ describe('Finder tests', () => {
}); });
it('Finds pre-release Python version in the manifest', async () => { it('Finds pre-release Python version in the manifest', async () => {
const findSpy = tc.getManifestFromRepo as jest.Mock; const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
findSpy.mockImplementation(() => manifestData); findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
const getBooleanInputSpy = core.getBooleanInput as jest.Mock; const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(() => false); getBooleanInputSpy.mockImplementation(input => false);
const installSpy = installer.installCpythonFromRelease as jest.Mock; const installSpy: jest.SpyInstance = jest.spyOn(
installer,
'installCpythonFromRelease'
);
installSpy.mockImplementation(async () => { installSpy.mockImplementation(async () => {
const pythonDir: string = path.join( const pythonDir: string = path.join(
toolDir, toolDir,
@@ -200,34 +150,40 @@ describe('Finder tests', () => {
}); });
it('Check-latest true, finds the latest version in the manifest', async () => { it('Check-latest true, finds the latest version in the manifest', async () => {
const findSpy = tc.getManifestFromRepo as jest.Mock; const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
findSpy.mockImplementation(() => manifestData); findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
const getBooleanInputSpy = core.getBooleanInput as jest.Mock; const getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
getBooleanInputSpy.mockImplementation(() => true); getBooleanInputSpy.mockImplementation(input => true);
const cnSpy = jest.spyOn(process.stdout, 'write'); const cnSpy: jest.SpyInstance = jest.spyOn(process.stdout, 'write');
cnSpy.mockImplementation(() => true); cnSpy.mockImplementation(line => {
// uncomment to debug
// process.stderr.write('write:' + line + '\n');
});
const addPathSpy = core.addPath as jest.Mock; const addPathSpy: jest.SpyInstance = jest.spyOn(core, 'addPath');
addPathSpy.mockImplementation(() => null); addPathSpy.mockImplementation(() => null);
const infoSpy = core.info as jest.Mock; const infoSpy: jest.SpyInstance = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => {}); infoSpy.mockImplementation(() => {});
const debugSpy = core.debug as jest.Mock; const debugSpy: jest.SpyInstance = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => {}); debugSpy.mockImplementation(() => {});
const pythonDir: string = path.join(toolDir, 'Python', '1.2.2', 'x64'); const pythonDir: string = path.join(toolDir, 'Python', '1.2.2', 'x64');
const expPath: string = path.join(toolDir, 'Python', '1.2.3', 'x64'); const expPath: string = path.join(toolDir, 'Python', '1.2.3', 'x64');
const installSpy = installer.installCpythonFromRelease as jest.Mock; const installSpy: jest.SpyInstance = jest.spyOn(
installer,
'installCpythonFromRelease'
);
installSpy.mockImplementation(async () => { installSpy.mockImplementation(async () => {
await io.mkdirP(expPath); await io.mkdirP(expPath);
fs.writeFileSync(`${expPath}.complete`, 'hello'); fs.writeFileSync(`${expPath}.complete`, 'hello');
}); });
const tcFindSpy = tc.find as jest.Mock; const tcFindSpy: jest.SpyInstance = jest.spyOn(tc, 'find');
tcFindSpy tcFindSpy
.mockImplementationOnce(() => '') .mockImplementationOnce(() => '')
.mockImplementationOnce(() => expPath); .mockImplementationOnce(() => expPath);
@@ -268,10 +224,13 @@ describe('Finder tests', () => {
}); });
it('Finds stable Python version if it is not installed, but exists in the manifest, skipping newer pre-release', async () => { it('Finds stable Python version if it is not installed, but exists in the manifest, skipping newer pre-release', async () => {
const findSpy = tc.getManifestFromRepo as jest.Mock; const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
findSpy.mockImplementation(() => manifestData); findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
const installSpy = installer.installCpythonFromRelease as jest.Mock; const installSpy: jest.SpyInstance = jest.spyOn(
installer,
'installCpythonFromRelease'
);
installSpy.mockImplementation(async () => { installSpy.mockImplementation(async () => {
const pythonDir: string = path.join(toolDir, 'Python', '1.2.3', 'x64'); const pythonDir: string = path.join(toolDir, 'Python', '1.2.3', 'x64');
await io.mkdirP(pythonDir); await io.mkdirP(pythonDir);
@@ -287,10 +246,13 @@ describe('Finder tests', () => {
}); });
it('Finds Python version if it is not installed, but exists in the manifest, pre-release fallback', async () => { it('Finds Python version if it is not installed, but exists in the manifest, pre-release fallback', async () => {
const findSpy = tc.getManifestFromRepo as jest.Mock; const findSpy: jest.SpyInstance = jest.spyOn(tc, 'getManifestFromRepo');
findSpy.mockImplementation(() => manifestData); findSpy.mockImplementation(() => <tc.IToolRelease[]>manifestData);
const installSpy = installer.installCpythonFromRelease as jest.Mock; const installSpy: jest.SpyInstance = jest.spyOn(
installer,
'installCpythonFromRelease'
);
installSpy.mockImplementation(async () => { installSpy.mockImplementation(async () => {
const pythonDir: string = path.join( const pythonDir: string = path.join(
toolDir, toolDir,
+44 -105
View File
@@ -1,75 +1,20 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import {fileURLToPath} from 'url';
import fs from 'fs'; import fs from 'fs';
import {HttpClient} from '@actions/http-client';
import * as ifm from '@actions/http-client/lib/interfaces';
import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec';
import * as core from '@actions/core';
import * as path from 'path'; import * as path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); import * as installer from '../src/install-graalpy';
import {
// Mock @actions modules
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
jest.unstable_mockModule('@actions/tool-cache', () => ({
find: jest.fn(),
findAllVersions: jest.fn(),
downloadTool: jest.fn(),
extractZip: jest.fn(),
extractTar: jest.fn(),
extract7z: jest.fn(),
extractXar: jest.fn(),
cacheDir: jest.fn(),
cacheFile: jest.fn(),
getManifestFromRepo: jest.fn(),
findFromManifest: jest.fn(),
evaluateVersions: jest.fn()
}));
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
// Dynamic imports after mocking
const core = await import('@actions/core');
const tc = await import('@actions/tool-cache');
const exec = await import('@actions/exec');
// Non-mocked imports
import {HttpClient} from '@actions/http-client';
import type * as ifm from '@actions/http-client/lib/interfaces';
const installer = await import('../src/install-graalpy.js');
const utils = await import('../src/utils.js');
import type {
IGraalPyManifestRelease, IGraalPyManifestRelease,
IGraalPyManifestAsset IGraalPyManifestAsset,
} from '../src/utils.js'; IS_WINDOWS
import manifestData from './data/graalpy.json' with {type: 'json'}; } from '../src/utils';
const IS_WINDOWS = utils.IS_WINDOWS; import manifestData from './data/graalpy.json';
const architecture = 'x64'; const architecture = 'x64';
@@ -103,18 +48,18 @@ describe('findRelease', () => {
browser_download_url: `https://github.com/graalvm/graal-languages-ea-builds/releases/download/graalpy-24.1.0-ea.09/graalpy-24.1.0-ea.09-${extensionName}` browser_download_url: `https://github.com/graalvm/graal-languages-ea-builds/releases/download/graalpy-24.1.0-ea.09/graalpy-24.1.0-ea.09-${extensionName}`
}; };
let warningSpy: jest.Mock; let warningSpy: jest.SpyInstance;
let debugSpy: jest.Mock; let debugSpy: jest.SpyInstance;
let infoSpy: jest.Mock; let infoSpy: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
infoSpy = core.info as jest.Mock; infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => {}); infoSpy.mockImplementation(() => {});
warningSpy = core.warning as jest.Mock; warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => null); warningSpy.mockImplementation(() => null);
debugSpy = core.debug as jest.Mock; debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => null); debugSpy.mockImplementation(() => null);
}); });
@@ -170,57 +115,51 @@ describe('findRelease', () => {
resolvedGraalPyVersion: '24.1.0-ea.9' resolvedGraalPyVersion: '24.1.0-ea.9'
}); });
}); });
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
jest.restoreAllMocks();
});
}); });
describe('installGraalPy', () => { describe('installGraalPy', () => {
let tcFind: jest.Mock; let tcFind: jest.SpyInstance;
let infoSpy: jest.Mock; let warningSpy: jest.SpyInstance;
let warningSpy: jest.Mock; let debugSpy: jest.SpyInstance;
let debugSpy: jest.Mock; let infoSpy: jest.SpyInstance;
let spyExtractZip: jest.Mock; let spyExtractZip: jest.SpyInstance;
let spyExtractTar: jest.Mock; let spyExtractTar: jest.SpyInstance;
let spyFsReadDir: jest.SpiedFunction<typeof fs.readdirSync>; let spyFsReadDir: jest.SpyInstance;
let spyFsWriteFile: jest.SpiedFunction<typeof fs.writeFileSync>; let spyFsWriteFile: jest.SpyInstance;
let spyHttpClient: jest.SpiedFunction<typeof HttpClient.prototype.getJson>; let spyHttpClient: jest.SpyInstance;
let spyExistsSync: jest.SpiedFunction<typeof fs.existsSync>; let spyExistsSync: jest.SpyInstance;
let spyExec: jest.Mock; let spyExec: jest.SpyInstance;
let spySymlinkSync: jest.SpiedFunction<typeof fs.symlinkSync>; let spySymlinkSync: jest.SpyInstance;
let spyDownloadTool: jest.Mock; let spyDownloadTool: jest.SpyInstance;
let spyCacheDir: jest.Mock; let spyCacheDir: jest.SpyInstance;
let spyChmodSync: jest.SpiedFunction<typeof fs.chmodSync>; let spyChmodSync: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
tcFind = tc.find as jest.Mock; tcFind = jest.spyOn(tc, 'find');
tcFind.mockImplementation(() => tcFind.mockImplementation(() =>
path.join('GraalPy', '3.6.12', architecture) path.join('GraalPy', '3.6.12', architecture)
); );
spyDownloadTool = tc.downloadTool as jest.Mock; spyDownloadTool = jest.spyOn(tc, 'downloadTool');
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'GraalPy')); spyDownloadTool.mockImplementation(() => path.join(tempDir, 'GraalPy'));
spyExtractZip = tc.extractZip as jest.Mock; spyExtractZip = jest.spyOn(tc, 'extractZip');
spyExtractZip.mockImplementation(() => tempDir); spyExtractZip.mockImplementation(() => tempDir);
spyExtractTar = tc.extractTar as jest.Mock; spyExtractTar = jest.spyOn(tc, 'extractTar');
spyExtractTar.mockImplementation(() => tempDir); spyExtractTar.mockImplementation(() => tempDir);
infoSpy = core.info as jest.Mock; infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => {}); infoSpy.mockImplementation(() => {});
warningSpy = core.warning as jest.Mock; warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => null); warningSpy.mockImplementation(() => null);
debugSpy = core.debug as jest.Mock; debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => null); debugSpy.mockImplementation(() => null);
spyFsReadDir = jest.spyOn(fs, 'readdirSync'); spyFsReadDir = jest.spyOn(fs, 'readdirSync');
spyFsReadDir.mockImplementation(() => ['GraalPyTest'] as any); spyFsReadDir.mockImplementation(() => ['GraalPyTest']);
spyFsWriteFile = jest.spyOn(fs, 'writeFileSync'); spyFsWriteFile = jest.spyOn(fs, 'writeFileSync');
spyFsWriteFile.mockImplementation(() => undefined); spyFsWriteFile.mockImplementation(() => undefined);
@@ -237,7 +176,7 @@ describe('installGraalPy', () => {
} }
); );
spyExec = exec.exec as jest.Mock; spyExec = jest.spyOn(exec, 'exec');
spyExec.mockImplementation(() => undefined); spyExec.mockImplementation(() => undefined);
spySymlinkSync = jest.spyOn(fs, 'symlinkSync'); spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
@@ -266,7 +205,7 @@ describe('installGraalPy', () => {
}); });
it('found and install GraalPy', async () => { it('found and install GraalPy', async () => {
spyCacheDir = tc.cacheDir as jest.Mock; spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() => spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '21.3.0', architecture) path.join(toolDir, 'GraalPy', '21.3.0', architecture)
); );
@@ -288,7 +227,7 @@ describe('installGraalPy', () => {
}); });
it('found and install GraalPy, pre-release fallback', async () => { it('found and install GraalPy, pre-release fallback', async () => {
spyCacheDir = tc.cacheDir as jest.Mock; spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() => spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'GraalPy', '24.1.0', architecture) path.join(toolDir, 'GraalPy', '24.1.0', architecture)
); );
+47 -103
View File
@@ -1,72 +1,20 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
import {fileURLToPath} from 'url';
import fs from 'fs'; import fs from 'fs';
import {HttpClient} from '@actions/http-client';
import * as ifm from '@actions/http-client/lib/interfaces';
import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec';
import * as core from '@actions/core';
import * as path from 'path'; import * as path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); import * as installer from '../src/install-pypy';
import {
IPyPyManifestRelease,
IPyPyManifestAsset,
IS_WINDOWS
} from '../src/utils';
// Mock @actions modules import manifestData from './data/pypy.json';
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
jest.unstable_mockModule('@actions/tool-cache', () => ({
find: jest.fn(),
findAllVersions: jest.fn(),
downloadTool: jest.fn(),
extractZip: jest.fn(),
extractTar: jest.fn(),
extract7z: jest.fn(),
extractXar: jest.fn(),
cacheDir: jest.fn(),
cacheFile: jest.fn(),
getManifestFromRepo: jest.fn(),
findFromManifest: jest.fn(),
evaluateVersions: jest.fn()
}));
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
// Dynamic imports after mocking
const core = await import('@actions/core');
const tc = await import('@actions/tool-cache');
const exec = await import('@actions/exec');
// Non-mocked imports
import {HttpClient} from '@actions/http-client';
import type * as ifm from '@actions/http-client/lib/interfaces';
const installer = await import('../src/install-pypy.js');
const utils = await import('../src/utils.js');
import type {IPyPyManifestRelease, IPyPyManifestAsset} from '../src/utils.js';
import manifestData from './data/pypy.json' with {type: 'json'};
const IS_WINDOWS = utils.IS_WINDOWS;
let architecture: string; let architecture: string;
if (IS_WINDOWS) { if (IS_WINDOWS) {
@@ -110,18 +58,19 @@ describe('findRelease', () => {
download_url: `https://test.download.python.org/pypy/pypy3.6-v7.4.0rc1-${extensionName}` download_url: `https://test.download.python.org/pypy/pypy3.6-v7.4.0rc1-${extensionName}`
}; };
let infoSpy: jest.Mock; let getBooleanInputSpy: jest.SpyInstance;
let warningSpy: jest.Mock; let warningSpy: jest.SpyInstance;
let debugSpy: jest.Mock; let debugSpy: jest.SpyInstance;
let infoSpy: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
infoSpy = core.info as jest.Mock; infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => {}); infoSpy.mockImplementation(() => {});
warningSpy = core.warning as jest.Mock; warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => null); warningSpy.mockImplementation(() => null);
debugSpy = core.debug as jest.Mock; debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => null); debugSpy.mockImplementation(() => null);
}); });
@@ -266,55 +215,50 @@ describe('findRelease', () => {
resolvedPyPyVersion: pypyVersion resolvedPyPyVersion: pypyVersion
}); });
}); });
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
jest.restoreAllMocks();
});
}); });
describe('installPyPy', () => { describe('installPyPy', () => {
let tcFind: jest.Mock; let tcFind: jest.SpyInstance;
let infoSpy: jest.Mock; let getBooleanInputSpy: jest.SpyInstance;
let warningSpy: jest.Mock; let warningSpy: jest.SpyInstance;
let debugSpy: jest.Mock; let debugSpy: jest.SpyInstance;
let spyExtractZip: jest.Mock; let infoSpy: jest.SpyInstance;
let spyExtractTar: jest.Mock; let spyExtractZip: jest.SpyInstance;
let spyFsReadDir: jest.SpiedFunction<typeof fs.readdirSync>; let spyExtractTar: jest.SpyInstance;
let spyFsWriteFile: jest.SpiedFunction<typeof fs.writeFileSync>; let spyFsReadDir: jest.SpyInstance;
let spyHttpClient: jest.SpiedFunction<typeof HttpClient.prototype.getJson>; let spyFsWriteFile: jest.SpyInstance;
let spyExistsSync: jest.SpiedFunction<typeof fs.existsSync>; let spyHttpClient: jest.SpyInstance;
let spyExec: jest.Mock; let spyExistsSync: jest.SpyInstance;
let spySymlinkSync: jest.SpiedFunction<typeof fs.symlinkSync>; let spyExec: jest.SpyInstance;
let spyDownloadTool: jest.Mock; let spySymlinkSync: jest.SpyInstance;
let spyCacheDir: jest.Mock; let spyDownloadTool: jest.SpyInstance;
let spyChmodSync: jest.SpiedFunction<typeof fs.chmodSync>; let spyCacheDir: jest.SpyInstance;
let spyChmodSync: jest.SpyInstance;
beforeEach(() => { beforeEach(() => {
tcFind = tc.find as jest.Mock; tcFind = jest.spyOn(tc, 'find');
tcFind.mockImplementation(() => path.join('PyPy', '3.6.12', architecture)); tcFind.mockImplementation(() => path.join('PyPy', '3.6.12', architecture));
spyDownloadTool = tc.downloadTool as jest.Mock; spyDownloadTool = jest.spyOn(tc, 'downloadTool');
spyDownloadTool.mockImplementation(() => path.join(tempDir, 'PyPy')); spyDownloadTool.mockImplementation(() => path.join(tempDir, 'PyPy'));
spyExtractZip = tc.extractZip as jest.Mock; spyExtractZip = jest.spyOn(tc, 'extractZip');
spyExtractZip.mockImplementation(() => tempDir); spyExtractZip.mockImplementation(() => tempDir);
spyExtractTar = tc.extractTar as jest.Mock; spyExtractTar = jest.spyOn(tc, 'extractTar');
spyExtractTar.mockImplementation(() => tempDir); spyExtractTar.mockImplementation(() => tempDir);
infoSpy = core.info as jest.Mock; infoSpy = jest.spyOn(core, 'info');
infoSpy.mockImplementation(() => {}); infoSpy.mockImplementation(() => {});
warningSpy = core.warning as jest.Mock; warningSpy = jest.spyOn(core, 'warning');
warningSpy.mockImplementation(() => null); warningSpy.mockImplementation(() => null);
debugSpy = core.debug as jest.Mock; debugSpy = jest.spyOn(core, 'debug');
debugSpy.mockImplementation(() => null); debugSpy.mockImplementation(() => null);
spyFsReadDir = jest.spyOn(fs, 'readdirSync'); spyFsReadDir = jest.spyOn(fs, 'readdirSync');
spyFsReadDir.mockImplementation(() => ['PyPyTest'] as any); spyFsReadDir.mockImplementation(() => ['PyPyTest']);
spyFsWriteFile = jest.spyOn(fs, 'writeFileSync'); spyFsWriteFile = jest.spyOn(fs, 'writeFileSync');
spyFsWriteFile.mockImplementation(() => undefined); spyFsWriteFile.mockImplementation(() => undefined);
@@ -331,7 +275,7 @@ describe('installPyPy', () => {
} }
); );
spyExec = exec.exec as jest.Mock; spyExec = jest.spyOn(exec, 'exec');
spyExec.mockImplementation(() => undefined); spyExec.mockImplementation(() => undefined);
spySymlinkSync = jest.spyOn(fs, 'symlinkSync'); spySymlinkSync = jest.spyOn(fs, 'symlinkSync');
@@ -360,7 +304,7 @@ describe('installPyPy', () => {
}); });
it('found and install PyPy', async () => { it('found and install PyPy', async () => {
spyCacheDir = tc.cacheDir as jest.Mock; spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() => spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'PyPy', '3.6.12', architecture) path.join(toolDir, 'PyPy', '3.6.12', architecture)
); );
@@ -384,7 +328,7 @@ describe('installPyPy', () => {
}); });
it('found and install PyPy, pre-release fallback', async () => { it('found and install PyPy, pre-release fallback', async () => {
spyCacheDir = tc.cacheDir as jest.Mock; spyCacheDir = jest.spyOn(tc, 'cacheDir');
spyCacheDir.mockImplementation(() => spyCacheDir.mockImplementation(() =>
path.join(toolDir, 'PyPy', '3.6.12', architecture) path.join(toolDir, 'PyPy', '3.6.12', architecture)
); );
+21 -151
View File
@@ -1,76 +1,16 @@
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals'; import {
getManifest,
getManifestFromRepo,
getManifestFromURL
} from '../src/install-python';
import * as httpm from '@actions/http-client';
import * as tc from '@actions/tool-cache';
class MockHttpClientError extends Error { jest.mock('@actions/http-client');
statusCode: number; jest.mock('@actions/tool-cache');
constructor(message: string, statusCode: number) { jest.mock('@actions/tool-cache', () => ({
super(message);
this.name = 'HttpClientError';
this.statusCode = statusCode;
}
}
// Mock @actions/http-client
jest.unstable_mockModule('@actions/http-client', () => ({
HttpClient: jest.fn().mockImplementation(() => ({
getJson: jest.fn()
})),
HttpClientError: MockHttpClientError,
HttpCodes: {
OK: 200,
NotFound: 404,
InternalServerError: 500
}
}));
// Mock @actions/cache (needed transitively by utils.ts)
jest.unstable_mockModule('@actions/cache', () => ({
saveCache: jest.fn(),
restoreCache: jest.fn(),
isFeatureAvailable: jest.fn()
}));
// Mock @actions/tool-cache
jest.unstable_mockModule('@actions/tool-cache', () => ({
getManifestFromRepo: jest.fn() getManifestFromRepo: jest.fn()
})); }));
// Mock @actions/core (needed by install-python.ts)
jest.unstable_mockModule('@actions/core', () => ({
info: jest.fn(),
warning: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
setFailed: jest.fn(),
setOutput: jest.fn(),
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
startGroup: jest.fn(),
endGroup: jest.fn(),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
// Mock @actions/exec (needed by install-python.ts)
jest.unstable_mockModule('@actions/exec', () => ({
exec: jest.fn(),
getExecOutput: jest.fn()
}));
// Dynamic imports after mocking
const httpm = await import('@actions/http-client');
const tc = await import('@actions/tool-cache');
const {getManifest, getManifestFromRepo, getManifestFromURL} =
await import('../src/install-python.js');
const mockManifest = [ const mockManifest = [
{ {
version: '1.0.0', version: '1.0.0',
@@ -91,115 +31,45 @@ describe('getManifest', () => {
jest.resetAllMocks(); jest.resetAllMocks();
}); });
afterEach(() => {
jest.useRealTimers();
});
it('should return manifest from repo', async () => { it('should return manifest from repo', async () => {
(tc.getManifestFromRepo as jest.Mock<any>).mockResolvedValue(mockManifest); (tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest);
const manifest = await getManifest(); const manifest = await getManifest();
expect(manifest).toEqual(mockManifest); expect(manifest).toEqual(mockManifest);
}); });
it('should return manifest from URL if repo fetch fails', async () => { it('should return manifest from URL if repo fetch fails', async () => {
jest.useFakeTimers(); (tc.getManifestFromRepo as jest.Mock).mockRejectedValue(
(tc.getManifestFromRepo as jest.Mock<any>).mockRejectedValue(
new Error('Fetch failed') new Error('Fetch failed')
); );
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({ (httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
getJson: jest.fn(async () => ({result: mockManifest})) result: mockManifest
}));
const promise = getManifest();
await jest.runAllTimersAsync();
const manifest = await promise;
expect(manifest).toEqual(mockManifest);
}); });
it('should fall back to URL if repo returns a truncated/empty manifest', async () => {
jest.useFakeTimers();
(tc.getManifestFromRepo as jest.Mock<any>).mockResolvedValue([]);
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
getJson: jest.fn(async () => ({result: mockManifest}))
}));
const promise = getManifest();
await jest.runAllTimersAsync();
const manifest = await promise;
expect(manifest).toEqual(mockManifest);
});
it('should retry on a transient invalid manifest and then succeed', async () => {
jest.useFakeTimers();
(tc.getManifestFromRepo as jest.Mock<any>)
.mockResolvedValueOnce([])
.mockResolvedValueOnce(mockManifest);
const promise = getManifest();
await jest.runAllTimersAsync();
const manifest = await promise;
expect(manifest).toEqual(mockManifest);
expect(tc.getManifestFromRepo as jest.Mock<any>).toHaveBeenCalledTimes(2);
});
it('should fail loudly when the manifest is truncated/empty on every source', async () => {
jest.useFakeTimers();
(tc.getManifestFromRepo as jest.Mock<any>).mockResolvedValue([]);
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
getJson: jest.fn(async () => ({result: []}))
}));
const promise = getManifest();
// Prevent unhandled rejection before timers advance.
const catchPromise = promise.catch(() => {});
await jest.runAllTimersAsync();
await catchPromise;
await expect(promise).rejects.toThrow(
'Failed to fetch the Python versions manifest'
);
});
it('should not retry the API on a rate-limit error and fall back to URL immediately', async () => {
const rateLimitError = Object.assign(new Error('API rate limit exceeded'), {
httpStatusCode: 403
});
(tc.getManifestFromRepo as jest.Mock<any>).mockRejectedValue(
rateLimitError
);
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({
getJson: jest.fn(async () => ({result: mockManifest}))
}));
const manifest = await getManifest(); const manifest = await getManifest();
expect(manifest).toEqual(mockManifest); expect(manifest).toEqual(mockManifest);
expect(tc.getManifestFromRepo as jest.Mock<any>).toHaveBeenCalledTimes(1);
}); });
}); });
describe('getManifestFromRepo', () => { describe('getManifestFromRepo', () => {
beforeEach(() => {
jest.resetAllMocks();
});
it('should return manifest from repo', async () => { it('should return manifest from repo', async () => {
(tc.getManifestFromRepo as jest.Mock<any>).mockResolvedValue(mockManifest); (tc.getManifestFromRepo as jest.Mock).mockResolvedValue(mockManifest);
const manifest = await getManifestFromRepo(); const manifest = await getManifestFromRepo();
expect(manifest).toEqual(mockManifest); expect(manifest).toEqual(mockManifest);
}); });
}); });
describe('getManifestFromURL', () => { describe('getManifestFromURL', () => {
beforeEach(() => {
jest.resetAllMocks();
});
it('should return manifest from URL', async () => { it('should return manifest from URL', async () => {
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({ (httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
getJson: jest.fn(async () => ({result: mockManifest})) result: mockManifest
})); });
const manifest = await getManifestFromURL(); const manifest = await getManifestFromURL();
expect(manifest).toEqual(mockManifest); expect(manifest).toEqual(mockManifest);
}); });
it('should throw error if unable to get manifest from URL', async () => { it('should throw error if unable to get manifest from URL', async () => {
(httpm.HttpClient as jest.Mock<any>).mockImplementation(() => ({ (httpm.HttpClient.prototype.getJson as jest.Mock).mockResolvedValue({
getJson: jest.fn(async () => ({result: null})) result: null
})); });
await expect(getManifestFromURL()).rejects.toThrow( await expect(getManifestFromURL()).rejects.toThrow(
'Unable to get manifest from' 'Unable to get manifest from'
); );
+14 -61
View File
@@ -1,57 +1,11 @@
import { import * as cache from '@actions/cache';
jest, import * as core from '@actions/core';
describe,
it,
expect,
afterAll,
afterEach,
beforeEach
} from '@jest/globals';
import {fileURLToPath} from 'url';
import path from 'path';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Mock @actions/cache
jest.unstable_mockModule('@actions/cache', () => ({
isFeatureAvailable: jest.fn(),
saveCache: jest.fn(),
restoreCache: jest.fn()
}));
// Mock @actions/core
jest.unstable_mockModule('@actions/core', () => ({
getInput: jest.fn(),
getBooleanInput: jest.fn(),
getMultilineInput: jest.fn(),
setOutput: jest.fn(),
setFailed: jest.fn(),
warning: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
error: jest.fn(),
notice: jest.fn(),
startGroup: jest.fn(),
endGroup: jest.fn(),
addPath: jest.fn(),
exportVariable: jest.fn(),
saveState: jest.fn(),
getState: jest.fn(),
setSecret: jest.fn(),
isDebug: jest.fn(() => false),
group: jest.fn((_name: string, fn: () => Promise<unknown>) => fn()),
toPlatformPath: jest.fn((p: string) => p),
toWin32Path: jest.fn((p: string) => p),
toPosixPath: jest.fn((p: string) => p)
}));
const cache = await import('@actions/cache');
const core = await import('@actions/core');
import * as io from '@actions/io'; import * as io from '@actions/io';
import fs from 'fs'; import fs from 'fs';
import path from 'path';
const { import {
validateVersion, validateVersion,
validatePythonVersionFormatForPyPy, validatePythonVersionFormatForPyPy,
isCacheFeatureAvailable, isCacheFeatureAvailable,
@@ -64,7 +18,10 @@ const {
IS_WINDOWS, IS_WINDOWS,
getDownloadFileName, getDownloadFileName,
getVersionInputFromToolVersions getVersionInputFromToolVersions
} = await import('../src/utils.js'); } from '../src/utils';
jest.mock('@actions/cache');
jest.mock('@actions/core');
describe('validatePythonVersionFormatForPyPy', () => { describe('validatePythonVersionFormatForPyPy', () => {
it.each([ it.each([
@@ -98,8 +55,8 @@ describe('validateVersion', () => {
describe('isCacheFeatureAvailable', () => { describe('isCacheFeatureAvailable', () => {
it('isCacheFeatureAvailable disabled on GHES', () => { it('isCacheFeatureAvailable disabled on GHES', () => {
(cache.isFeatureAvailable as jest.Mock).mockImplementation(() => false); jest.spyOn(cache, 'isFeatureAvailable').mockImplementation(() => false);
const infoMock = core.warning as jest.Mock; const infoMock = jest.spyOn(core, 'warning');
const message = const message =
'Caching is only supported on GHES version >= 3.5. If you are on a version >= 3.5, please check with your GHES admin if the Actions cache service is enabled or not.'; 'Caching is only supported on GHES version >= 3.5. If you are on a version >= 3.5, please check with your GHES admin if the Actions cache service is enabled or not.';
try { try {
@@ -112,8 +69,8 @@ describe('isCacheFeatureAvailable', () => {
}); });
it('isCacheFeatureAvailable disabled on dotcom', () => { it('isCacheFeatureAvailable disabled on dotcom', () => {
(cache.isFeatureAvailable as jest.Mock).mockImplementation(() => false); jest.spyOn(cache, 'isFeatureAvailable').mockImplementation(() => false);
const infoMock = core.warning as jest.Mock; const infoMock = jest.spyOn(core, 'warning');
const message = const message =
'The runner was not able to contact the cache service. Caching will be skipped'; 'The runner was not able to contact the cache service. Caching will be skipped';
try { try {
@@ -126,14 +83,9 @@ describe('isCacheFeatureAvailable', () => {
}); });
it('isCacheFeatureAvailable is enabled', () => { it('isCacheFeatureAvailable is enabled', () => {
(cache.isFeatureAvailable as jest.Mock).mockImplementation(() => true); jest.spyOn(cache, 'isFeatureAvailable').mockImplementation(() => true);
expect(isCacheFeatureAvailable()).toBe(true); expect(isCacheFeatureAvailable()).toBe(true);
}); });
afterEach(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});
}); });
const tempDir = path.join( const tempDir = path.join(
@@ -393,6 +345,7 @@ describe('isGhes', () => {
const pristineEnv = process.env; const pristineEnv = process.env;
beforeEach(() => { beforeEach(() => {
jest.resetModules();
process.env = {...pristineEnv}; process.env = {...pristineEnv};
}); });
+2
View File
@@ -31,6 +31,8 @@ inputs:
default: false default: false
pip-version: pip-version:
description: "Used to specify the version of pip to install with the Python. Supported format: major[.minor][.patch]." description: "Used to specify the version of pip to install with the Python. Supported format: major[.minor][.patch]."
pip-install:
description: "Used to specify the packages to install with pip after setting up Python. Can be a requirements file or package names."
outputs: outputs:
python-version: python-version:
description: "The installed Python or PyPy version. Useful when given a version range as input." description: "The installed Python or PyPy version. Useful when given a version range as input."
+48540 -52509
View File
File diff suppressed because one or more lines are too long
-3
View File
@@ -1,3 +0,0 @@
{
"type": "module"
}
+52678 -57850
View File
File diff suppressed because one or more lines are too long
-3
View File
@@ -1,3 +0,0 @@
{
"type": "module"
}
+20
View File
@@ -23,6 +23,7 @@
- [Using `setup-python` on GHES](advanced-usage.md#using-setup-python-on-ghes) - [Using `setup-python` on GHES](advanced-usage.md#using-setup-python-on-ghes)
- [Allow pre-releases](advanced-usage.md#allow-pre-releases) - [Allow pre-releases](advanced-usage.md#allow-pre-releases)
- [Using the pip-version input](advanced-usage.md#using-the-pip-version-input) - [Using the pip-version input](advanced-usage.md#using-the-pip-version-input)
- [Using the pip-install input](advanced-usage.md#using-the-pip-install-input)
## Using the `python-version` input ## Using the `python-version` input
@@ -672,3 +673,22 @@ The version of Pip should be specified in the format `major`, `major.minor`, or
> The `pip-version` input is supported only with standard Python versions. It is not available when using PyPy or GraalPy. > The `pip-version` input is supported only with standard Python versions. It is not available when using PyPy or GraalPy.
> Using a specific or outdated version of pip may result in compatibility or security issues and can cause job failures. For best practices and guidance, refer to the official [pip documentation](https://pip.pypa.io/en/stable/). > Using a specific or outdated version of pip may result in compatibility or security issues and can cause job failures. For best practices and guidance, refer to the official [pip documentation](https://pip.pypa.io/en/stable/).
## Using the pip-install input
The `pip-install` input allows you to install dependencies as part of the Python setup step.
```yaml
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.13'
pip-install: -r requirements.txt
```
> Note: This feature is intended for standard pip-based dependency installations.
For complex workflows, or alternative package managers (e.g., poetry, pipenv), we recommend using separate steps to maintain clarity and flexibility.
> The `pip-install` input mirrors the flexibility of a standard pip install command and supports most of its arguments.
+11
View File
@@ -0,0 +1,11 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
testRunner: 'jest-circus/runner',
transform: {
'^.+\\.ts$': 'ts-jest'
},
verbose: true
}
-24
View File
@@ -1,24 +0,0 @@
export default {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
roots: ['<rootDir>'],
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.ts$': [
'ts-jest',
{
useESM: true,
diagnostics: {
ignoreCodes: [151002]
}
}
]
},
extensionsToTreatAsEsm: ['.ts'],
transformIgnorePatterns: ['node_modules/(?!(@actions)/)'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1'
},
verbose: true
}
+1744 -2312
View File
File diff suppressed because it is too large Load Diff
+30 -29
View File
@@ -1,7 +1,6 @@
{ {
"name": "setup-python", "name": "setup-python",
"version": "7.0.0", "version": "6.3.0",
"type": "module",
"private": true, "private": true,
"description": "Setup python action", "description": "Setup python action",
"main": "dist/index.js", "main": "dist/index.js",
@@ -10,12 +9,12 @@
}, },
"scripts": { "scripts": {
"build": "ncc build -o dist/setup src/setup-python.ts && ncc build -o dist/cache-save src/cache-save.ts", "build": "ncc build -o dist/setup src/setup-python.ts && ncc build -o dist/cache-save src/cache-save.ts",
"format": "prettier --no-error-on-unmatched-pattern --write \"**/*.{ts,yml,yaml}\"", "format": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --write \"**/*.{ts,yml,yaml}\"",
"format-check": "prettier --no-error-on-unmatched-pattern --check \"**/*.{ts,yml,yaml}\"", "format-check": "prettier --no-error-on-unmatched-pattern --config ./.prettierrc.js --check \"**/*.{ts,yml,yaml}\"",
"lint": "eslint \"**/*.ts\"", "lint": "eslint --config ./.eslintrc.js \"**/*.ts\"",
"lint:fix": "eslint \"**/*.ts\" --fix", "lint:fix": "eslint --config ./.eslintrc.js \"**/*.ts\" --fix",
"release": "ncc build -o dist/setup src/setup-python.ts && ncc build -o dist/cache-save src/cache-save.ts && git add -f dist/", "release": "ncc build -o dist/setup src/setup-python.ts && ncc build -o dist/cache-save src/cache-save.ts && git add -f dist/",
"test": "node --experimental-vm-modules ./node_modules/jest/bin/jest.js --runInBand --coverage" "test": "jest --runInBand --coverage"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@@ -29,32 +28,34 @@
"author": "GitHub", "author": "GitHub",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/cache": "^6.2.0", "@actions/cache": "^5.1.0",
"@actions/core": "^3.0.1", "@actions/core": "^2.0.3",
"@actions/exec": "^3.0.0", "@actions/exec": "^2.0.0",
"@actions/glob": "^0.7.0", "@actions/glob": "^0.5.1",
"@actions/http-client": "^4.0.1", "@actions/http-client": "^3.0.2",
"@actions/io": "^3.0.2", "@actions/io": "^2.0.0",
"@actions/tool-cache": "^4.0.0", "@actions/tool-cache": "^3.0.1",
"@iarna/toml": "^3.0.0", "@iarna/toml": "^3.0.0",
"semver": "^7.8.5" "semver": "^7.7.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@types/jest": "^29.5.12",
"@jest/globals": "^30.4.1",
"@types/node": "^24.10.1", "@types/node": "^24.10.1",
"@types/semver": "^7.7.0", "@types/semver": "^7.7.0",
"@typescript-eslint/eslint-plugin": "^8.62.0", "@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^8.62.0", "@typescript-eslint/parser": "^5.54.0",
"@vercel/ncc": "^0.44.0", "@vercel/ncc": "^0.38.3",
"eslint": "^10.5.0", "eslint": "^8.57.0",
"eslint-config-prettier": "^10.0.0", "eslint-config-prettier": "^8.6.0",
"eslint-plugin-jest": "^29.15.2", "eslint-plugin-jest": "^27.9.0",
"eslint-plugin-n": "^18.1.0", "eslint-plugin-node": "^11.1.0",
"globals": "^17.7.0", "jest": "^29.7.0",
"jest": "^30.4.2", "jest-circus": "^29.7.0",
"prettier": "^3.8.4", "prettier": "^3.6.2",
"ts-jest": "^29.4.11", "ts-jest": "^29.3.2",
"typescript": "^6.0.3" "typescript": "^5.9.3"
},
"overrides": {
"fast-xml-parser": "^5.9.2"
} }
} }
+2 -2
View File
@@ -1,7 +1,7 @@
import * as cache from '@actions/cache'; import * as cache from '@actions/cache';
import * as core from '@actions/core'; import * as core from '@actions/core';
import {getOSInfo, IS_LINUX} from '../utils.js'; import {getOSInfo, IS_LINUX} from '../utils';
import {CACHE_DEPENDENCY_BACKUP_PATH} from './constants.js'; import {CACHE_DEPENDENCY_BACKUP_PATH} from './constants';
export enum State { export enum State {
STATE_CACHE_PRIMARY_KEY = 'cache-primary-key', STATE_CACHE_PRIMARY_KEY = 'cache-primary-key',
+3 -3
View File
@@ -1,6 +1,6 @@
import PipCache from './pip-cache.js'; import PipCache from './pip-cache';
import PipenvCache from './pipenv-cache.js'; import PipenvCache from './pipenv-cache';
import PoetryCache from './poetry-cache.js'; import PoetryCache from './poetry-cache';
export enum PackageManagers { export enum PackageManagers {
Pip = 'pip', Pip = 'pip',
+3 -3
View File
@@ -6,9 +6,9 @@ import utils from 'util';
import * as path from 'path'; import * as path from 'path';
import os from 'os'; import os from 'os';
import CacheDistributor from './cache-distributor.js'; import CacheDistributor from './cache-distributor';
import {IS_WINDOWS} from '../utils.js'; import {IS_WINDOWS} from '../utils';
import {CACHE_DEPENDENCY_BACKUP_PATH} from './constants.js'; import {CACHE_DEPENDENCY_BACKUP_PATH} from './constants';
class PipCache extends CacheDistributor { class PipCache extends CacheDistributor {
private cacheDependencyBackupPath: string = CACHE_DEPENDENCY_BACKUP_PATH; private cacheDependencyBackupPath: string = CACHE_DEPENDENCY_BACKUP_PATH;
+1 -1
View File
@@ -3,7 +3,7 @@ import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import * as core from '@actions/core'; import * as core from '@actions/core';
import CacheDistributor from './cache-distributor.js'; import CacheDistributor from './cache-distributor';
class PipenvCache extends CacheDistributor { class PipenvCache extends CacheDistributor {
constructor( constructor(
+2 -2
View File
@@ -4,8 +4,8 @@ import * as path from 'path';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
import * as core from '@actions/core'; import * as core from '@actions/core';
import CacheDistributor from './cache-distributor.js'; import CacheDistributor from './cache-distributor';
import {logWarning} from '../utils.js'; import {logWarning} from '../utils';
class PoetryCache extends CacheDistributor { class PoetryCache extends CacheDistributor {
constructor( constructor(
+1 -1
View File
@@ -2,7 +2,7 @@ import * as core from '@actions/core';
import * as cache from '@actions/cache'; import * as cache from '@actions/cache';
import fs from 'fs'; import fs from 'fs';
import {State} from './cache-distributions/cache-distributor.js'; import {State} from './cache-distributions/cache-distributor';
// Added early exit to resolve issue with slow post action step: // Added early exit to resolve issue with slow post action step:
// - https://github.com/actions/setup-node/issues/878 // - https://github.com/actions/setup-node/issues/878
+2 -2
View File
@@ -1,6 +1,6 @@
import * as path from 'path'; import * as path from 'path';
import * as graalpyInstall from './install-graalpy.js'; import * as graalpyInstall from './install-graalpy';
import {IS_WINDOWS, validateVersion, IGraalPyManifestRelease} from './utils.js'; import {IS_WINDOWS, validateVersion, IGraalPyManifestRelease} from './utils';
import * as semver from 'semver'; import * as semver from 'semver';
import * as core from '@actions/core'; import * as core from '@actions/core';
+2 -2
View File
@@ -1,5 +1,5 @@
import * as path from 'path'; import * as path from 'path';
import * as pypyInstall from './install-pypy.js'; import * as pypyInstall from './install-pypy';
import { import {
IS_WINDOWS, IS_WINDOWS,
WINDOWS_ARCHS, WINDOWS_ARCHS,
@@ -9,7 +9,7 @@ import {
validatePythonVersionFormatForPyPy, validatePythonVersionFormatForPyPy,
IPyPyManifestRelease, IPyPyManifestRelease,
getBinaryDirectory getBinaryDirectory
} from './utils.js'; } from './utils';
import * as semver from 'semver'; import * as semver from 'semver';
import * as core from '@actions/core'; import * as core from '@actions/core';
+2 -2
View File
@@ -1,10 +1,10 @@
import * as os from 'os'; import * as os from 'os';
import * as path from 'path'; import * as path from 'path';
import {IS_WINDOWS, IS_LINUX, getOSInfo} from './utils.js'; import {IS_WINDOWS, IS_LINUX, getOSInfo} from './utils';
import * as semver from 'semver'; import * as semver from 'semver';
import * as installer from './install-python.js'; import * as installer from './install-python';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as tc from '@actions/tool-cache'; import * as tc from '@actions/tool-cache';
+1 -1
View File
@@ -16,7 +16,7 @@ import {
createSymlinkInFolder, createSymlinkInFolder,
isNightlyKeyword, isNightlyKeyword,
getNextPageUrl getNextPageUrl
} from './utils.js'; } from './utils';
const TOKEN = core.getInput('token'); const TOKEN = core.getInput('token');
const AUTH = !TOKEN ? undefined : `token ${TOKEN}`; const AUTH = !TOKEN ? undefined : `token ${TOKEN}`;
+1 -1
View File
@@ -17,7 +17,7 @@ import {
writeExactPyPyVersionFile, writeExactPyPyVersionFile,
getBinaryDirectory, getBinaryDirectory,
getDownloadFileName getDownloadFileName
} from './utils.js'; } from './utils';
export async function installPyPy( export async function installPyPy(
pypyVersion: string, pypyVersion: string,
+16 -92
View File
@@ -2,11 +2,11 @@ import * as path from 'path';
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as tc from '@actions/tool-cache'; import * as tc from '@actions/tool-cache';
import * as exec from '@actions/exec'; import * as exec from '@actions/exec';
import {ExecOptions} from '@actions/exec';
import * as httpm from '@actions/http-client'; import * as httpm from '@actions/http-client';
import * as fs from 'fs'; import * as fs from 'fs';
import * as semver from 'semver'; import * as semver from 'semver';
import {IS_WINDOWS, IS_LINUX, getDownloadFileName} from './utils.js'; import {ExecOptions} from '@actions/exec/lib/interfaces';
import {IS_WINDOWS, IS_LINUX, getDownloadFileName} from './utils';
import {IToolRelease} from '@actions/tool-cache'; import {IToolRelease} from '@actions/tool-cache';
const TOKEN = core.getInput('token'); const TOKEN = core.getInput('token');
@@ -80,9 +80,6 @@ function findRhelRelease(
return undefined; return undefined;
} }
const MANIFEST_FETCH_MAX_ATTEMPTS = 3;
const MANIFEST_FETCH_RETRY_BASE_DELAY_MS = 1000;
export async function findReleaseFromManifest( export async function findReleaseFromManifest(
semanticVersionSpec: string, semanticVersionSpec: string,
architecture: string, architecture: string,
@@ -136,96 +133,28 @@ function isIToolRelease(obj: any): obj is IToolRelease {
); );
} }
// Rejects empty or truncated manifest responses.
function isValidManifest(manifest: unknown): manifest is tc.IToolRelease[] {
return (
Array.isArray(manifest) &&
manifest.length > 0 &&
manifest.every(isIToolRelease)
);
}
function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}
// HTTP 403/429 from http-client (`statusCode`) or tool-cache (`httpStatusCode`).
function isRateLimitError(err: unknown): boolean {
const e = err as
| {httpStatusCode?: number; statusCode?: number}
| null
| undefined;
const status = e?.httpStatusCode ?? e?.statusCode;
return status === 403 || status === 429;
}
// Fetches and validates a manifest, retrying transient failures with backoff.
async function fetchValidManifest(
source: string,
fetcher: () => Promise<tc.IToolRelease[]>
): Promise<tc.IToolRelease[]> {
let lastError: Error | undefined;
let attempts = 0;
for (let attempt = 1; attempt <= MANIFEST_FETCH_MAX_ATTEMPTS; attempt++) {
attempts = attempt;
try {
const manifest = await fetcher();
if (isValidManifest(manifest)) {
return manifest;
}
throw new Error(
`The manifest fetched from ${source} is empty, truncated, or does not contain any valid tool release entries.`
);
} catch (err) {
lastError = err instanceof Error ? err : new Error(String(err));
core.debug(
`Attempt ${attempt}/${MANIFEST_FETCH_MAX_ATTEMPTS} to fetch the manifest from ${source} failed: ${lastError.message}`
);
// Rate limits won't clear within the backoff window; fall back instead.
if (isRateLimitError(err)) {
core.debug(
`${source} is rate-limited; skipping retries for this source.`
);
break;
}
if (attempt < MANIFEST_FETCH_MAX_ATTEMPTS) {
const delay = MANIFEST_FETCH_RETRY_BASE_DELAY_MS * 2 ** (attempt - 1);
core.debug(`Retrying in ${delay}ms...`);
await sleep(delay);
}
}
}
throw new Error(
`Failed to fetch a valid manifest from ${source} after ${attempts} attempt(s): ${lastError?.message}`
);
}
export async function getManifest(): Promise<tc.IToolRelease[]> { export async function getManifest(): Promise<tc.IToolRelease[]> {
try { try {
return await fetchValidManifest('the GitHub API', getManifestFromRepo); const repoManifest = await getManifestFromRepo();
if (
Array.isArray(repoManifest) &&
repoManifest.length &&
repoManifest.every(isIToolRelease)
) {
return repoManifest;
}
throw new Error(
'The repository manifest is invalid or does not include any valid tool release (IToolRelease) entries.'
);
} catch (err) { } catch (err) {
core.debug('Fetching the manifest via the API failed.'); core.debug('Fetching the manifest via the API failed.');
if (err instanceof Error) { if (err instanceof Error) {
core.debug(err.message); core.debug(err.message);
} else { } else {
core.debug('An unexpected error occurred while fetching the manifest.'); core.error('An unexpected error occurred while fetching the manifest.');
} }
} }
return await getManifestFromURL();
try {
return await fetchValidManifest('the raw URL', getManifestFromURL);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
// Fail loudly so the action doesn't exit 0 without installing Python.
throw new Error(
`Failed to fetch the Python versions manifest. The response was empty, truncated, or invalid, and all retries were exhausted. ${message}`,
{cause: err}
);
}
} }
export function getManifestFromRepo(): Promise<tc.IToolRelease[]> { export function getManifestFromRepo(): Promise<tc.IToolRelease[]> {
@@ -264,12 +193,7 @@ async function installPython(workingDirectory: string) {
core.info(data.toString().trim()); core.info(data.toString().trim());
}, },
stderr: (data: Buffer) => { stderr: (data: Buffer) => {
const msg = data.toString().trim(); core.error(data.toString().trim());
if (/^WARNING:/im.test(msg)) {
core.warning(msg);
} else {
core.error(msg);
}
} }
} }
}; };
+24 -11
View File
@@ -1,19 +1,19 @@
import * as core from '@actions/core'; import * as core from '@actions/core';
import * as finder from './find-python.js'; import * as finder from './find-python';
import * as finderPyPy from './find-pypy.js'; import * as finderPyPy from './find-pypy';
import * as finderGraalPy from './find-graalpy.js'; import * as finderGraalPy from './find-graalpy';
import * as path from 'path'; import * as path from 'path';
import * as os from 'os'; import * as os from 'os';
import {fileURLToPath} from 'url';
import fs from 'fs'; import fs from 'fs';
import {getCacheDistributor} from './cache-distributions/cache-factory.js'; import {getCacheDistributor} from './cache-distributions/cache-factory';
import { import {
isCacheFeatureAvailable, isCacheFeatureAvailable,
logWarning, logWarning,
IS_MAC, IS_MAC,
getVersionInputFromFile, getVersionInputFromFile,
getVersionsInputFromPlainFile getVersionsInputFromPlainFile
} from './utils.js'; } from './utils';
import {exec} from '@actions/exec';
function isPyPyVersion(versionSpec: string) { function isPyPyVersion(versionSpec: string) {
return versionSpec.startsWith('pypy'); return versionSpec.startsWith('pypy');
@@ -23,6 +23,19 @@ function isGraalPyVersion(versionSpec: string) {
return versionSpec.startsWith('graalpy'); return versionSpec.startsWith('graalpy');
} }
async function installPipPackages(pipInstall: string) {
core.info(`Installing pip packages: ${pipInstall}`);
try {
const installArgs = pipInstall.trim().split(/\s+/);
await exec('python', ['-m', 'pip', 'install', ...installArgs]);
core.info('Successfully installed pip packages');
} catch (error) {
core.setFailed(
`Failed to install pip packages from "${pipInstall}". Please verify that the package names, versions, or requirements files provided are correct and installable, that the specified packages and versions can be resolved from PyPI or the configured package index, and that your network connection is stable and allows access to the package index.`
);
}
}
async function cacheDependencies(cache: string, pythonVersion: string) { async function cacheDependencies(cache: string, pythonVersion: string) {
const cacheDependencyPath = const cacheDependencyPath =
core.getInput('cache-dependency-path') || undefined; core.getInput('cache-dependency-path') || undefined;
@@ -146,16 +159,16 @@ async function run() {
if (cache && isCacheFeatureAvailable()) { if (cache && isCacheFeatureAvailable()) {
await cacheDependencies(cache, pythonVersion); await cacheDependencies(cache, pythonVersion);
} }
const pipInstall = core.getInput('pip-install');
if (pipInstall) {
await installPipPackages(pipInstall);
}
} else { } else {
core.warning( core.warning(
'The `python-version` input is not set. The version of Python currently in `PATH` will be used.' 'The `python-version` input is not set. The version of Python currently in `PATH` will be used.'
); );
} }
const matchersPath = path.join( const matchersPath = path.join(__dirname, '../..', '.github');
path.dirname(fileURLToPath(import.meta.url)),
'../..',
'.github'
);
core.info(`##[add-matcher]${path.join(matchersPath, 'python.json')}`); core.info(`##[add-matcher]${path.join(matchersPath, 'python.json')}`);
} catch (err) { } catch (err) {
core.setFailed((err as Error).message); core.setFailed((err as Error).message);
+3 -3
View File
@@ -258,7 +258,7 @@ export function getVersionInputFromTomlFile(versionFile: string): string[] {
pyprojectFile = pyprojectFile.replace(/\r\n/g, '\n'); pyprojectFile = pyprojectFile.replace(/\r\n/g, '\n');
const pyprojectConfig = toml.parse(pyprojectFile); const pyprojectConfig = toml.parse(pyprojectFile);
let keys: string[] = []; let keys = [];
if ('project' in pyprojectConfig) { if ('project' in pyprojectConfig) {
// standard project metadata (PEP 621) // standard project metadata (PEP 621)
@@ -267,7 +267,7 @@ export function getVersionInputFromTomlFile(versionFile: string): string[] {
// python poetry // python poetry
keys = ['tool', 'poetry', 'dependencies', 'python']; keys = ['tool', 'poetry', 'dependencies', 'python'];
} }
const versions: string[] = []; const versions = [];
const version = extractValue(pyprojectConfig, keys); const version = extractValue(pyprojectConfig, keys);
if (version !== undefined) { if (version !== undefined) {
versions.push(version); versions.push(version);
@@ -376,7 +376,7 @@ export function getVersionInputFromPipfileFile(versionFile: string): string[] {
} else { } else {
keys.push('python_version'); keys.push('python_version');
} }
const versions: string[] = []; const versions = [];
const version = extractValue(pipfileConfig, keys); const version = extractValue(pipfileConfig, keys);
if (version !== undefined) { if (version !== undefined) {
versions.push(version); versions.push(version);
+2 -2
View File
@@ -3,7 +3,7 @@
/* Basic Options */ /* Basic Options */
// "incremental": true, /* Enable incremental compilation */ // "incremental": true, /* Enable incremental compilation */
"target": "ES2022", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ "target": "ES2022", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
"module": "NodeNext", /* Specify module code generation. */ "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
// "allowJs": true, /* Allow javascript files to be compiled. */ // "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */ // "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
@@ -60,5 +60,5 @@
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
}, },
"exclude": ["node_modules", "**/*.test.ts", "jest.config.ts"] "exclude": ["node_modules", "**/*.test.ts"]
} }