Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🔧 Add UV cache functionality for improved performance #72

Merged
merged 14 commits into from
Sep 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 33 additions & 20 deletions .github/workflows/default.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
name: default
name: CI

on:
pull_request:
push:
branches:
- main
pull_request: {}

jobs:
# run linters and unit tests
lint-and-test-units:
runs-on: ubuntu-latest
steps:
Expand All @@ -20,25 +22,15 @@ jobs:
- run: npm run format:check
- run: npm run test

# run action on a clean machine without building to check that it works as expected
test-integration:
runs-on: ${{ matrix.os }}

strategy:
matrix:
python-version:
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
uv-version:
- "0.1.12"
- ""
os:
- macos-latest
- ubuntu-latest
- windows-latest
python-version: ["3.10"]
uv-version: ["0.3.0", ""]
os: [macos-latest, ubuntu-latest, windows-latest]
cache: [true, false]
venv-name: [".venv"]
fail-fast: true

steps:
Expand All @@ -49,7 +41,8 @@ jobs:
- uses: ./
with:
uv-version: ${{ matrix.uv-version }}
uv-venv: "my_virt_env"
uv-venv: ${{ matrix.venv-name }}
uv-cache: ${{ matrix.cache }}
- run: uv --version
- name: Get python executable on Windows
if: runner.os == 'Windows'
Expand All @@ -60,4 +53,24 @@ jobs:
run: |
which python
- name: Check inside virtual environment
run: python -c "import sys; exit(0 if sys.prefix != sys.base_prefix else 1)"
run: python -c "import sys; exit(0 if sys.prefix != sys.base_prefix else 1)"
- name: init Project
run: uv init
- name: Install Pydantic and Ruff
run: |
uv add pydantic ruff
- name: Check installed packages
run: |
uv pip list
- name: Check cache directory (non-Windows)
if: runner.os != 'Windows' && matrix.cache == 'true'
run: |
ls -la ${{ env.UV_CACHE_DIR || '/tmp/.uv-cache' }}
echo "Checking for Pydantic and Ruff in cache"
find ${{ env.UV_CACHE_DIR || '/tmp/.uv-cache' }} -name "*pydantic*" -o -name "*ruff*"
- name: Check cache directory (Windows)
if: runner.os == 'Windows' && matrix.cache == 'true'
run: |
Get-ChildItem -Force ${{ env.UV_CACHE_DIR || '$env:TEMP\.uv-cache' }}
echo "Checking for Pydantic and Ruff in cache"
Get-ChildItem -Recurse ${{ env.UV_CACHE_DIR || '$env:TEMP\.uv-cache' }} | Where-Object { $_.Name -like "*pydantic*" -or $_.Name -like "*ruff*" }
142 changes: 130 additions & 12 deletions __tests__/input.test.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,173 @@
import { getInputs, getVenvInput, getVersionInput } from '../src/inputs'
import {
getInputs,
getVenvInput,
getVersionInput,
getCacheInput,
isCacheAllowed
} from '../src/inputs'
import * as core from '@actions/core'

jest.mock('@actions/core')

const mockedCore = core as jest.Mocked<typeof core>

const TEST_ENV_VARS = {
INPUT_MISSING: '',
INPUT_FALSY: 'false',
INPUT_TRUTHY: 'true',
INPUT_VERSION_UNSUPPORTED: '0.0.3',
INPUT_VERSION_SUPPORTED: '0.1.2',

INPUT_VERSION_CACHE_SUPPORTED: '0.3.0',
INPUT_VERSION_LATEST: 'latest',
'INPUT_UV-VERSION': '0.1.2',
'INPUT_UV-VENV': 'my_venv'
'INPUT_UV-VENV': 'my_venv',
'INPUT_UV-CACHE': 'true'
}

describe('options', () => {
beforeEach(() => {
jest.resetAllMocks()
for (const key in TEST_ENV_VARS) {
process.env[key] = TEST_ENV_VARS[key as keyof typeof TEST_ENV_VARS]
}
})

afterEach(() => {
for (const key in TEST_ENV_VARS) {
Reflect.deleteProperty(TEST_ENV_VARS, key)
delete process.env[key]
}
})

it('getVersionInput returns null if input is missing', () => {
mockedCore.getInput.mockReturnValue('')
expect(getVersionInput('missing')).toBeNull()
expect(mockedCore.notice).toHaveBeenCalledWith('Using latest uv version')
})

it('getInputs returns inputs', () => {
expect(getInputs()).toStrictEqual({
version: '0.1.2',
venv: 'my_venv'
})
it('getVersionInput returns null if input is "latest"', () => {
mockedCore.getInput.mockReturnValue('latest')
expect(getVersionInput('version_latest')).toBeNull()
expect(mockedCore.notice).toHaveBeenCalledWith('Using latest uv version')
})

it('getVersionInput throws if input is not valid', () => {
mockedCore.getInput.mockReturnValue('false')
expect(() => getVersionInput('falsy')).toThrow(
"Passed uv version 'false' is not a valid"
"Passed uv version 'false' is not valid"
)
})

it('getVersionInput returns version if input is supported', () => {
expect(getVersionInput('version_supported')).toBe('0.1.2')
it('getVersionInput warns if version is unsupported', () => {
mockedCore.getInput.mockReturnValue('0.2.9')
expect(getVersionInput('version_unsupported')).toBe('0.2.9')
expect(mockedCore.warning).toHaveBeenCalledWith(
"Passed uv version '0.2.9' is less than 0.3.0. Caching will be disabled."
)
expect(mockedCore.warning).toHaveBeenCalledWith(
'Using uv version 0.2.9. This may not be the latest version.'
)
})

it('getVersionInput warns for supported version', () => {
mockedCore.getInput.mockReturnValue('0.3.0')
expect(getVersionInput('version_supported')).toBe('0.3.0')
expect(mockedCore.warning).toHaveBeenCalledWith(
'Using uv version 0.3.0. This may not be the latest version.'
)
})

it('getVenvInput returns venv name if input is valid', () => {
mockedCore.getInput.mockReturnValue('my_venv')
expect(getVenvInput('uv-venv')).toBe('my_venv')
})

it('getVenvInput returns null if input is not provided', () => {
mockedCore.getInput.mockReturnValue('')
expect(getVenvInput('SOMETHING')).toBeNull()
})

it('getInputs returns inputs including cache', () => {
mockedCore.getInput.mockImplementation(name => {
if (name === 'uv-version') {
return '0.3.0'
}
if (name === 'uv-venv') {
return 'my_venv'
}
if (name === 'uv-cache') {
return 'true'
}
return ''
})
expect(getInputs()).toStrictEqual({
version: '0.3.0',
venv: 'my_venv',
cache: true
})
expect(mockedCore.warning).toHaveBeenCalledWith(
'Using uv version 0.3.0. This may not be the latest version.'
)
})

it('getCacheInput returns true if input is true and version is supported', () => {
mockedCore.getInput.mockReturnValue('true')
expect(getCacheInput('uv-cache', '0.3.0')).toBe(true)
})

it('getCacheInput returns false if input is true but version is not supported', () => {
mockedCore.getInput.mockReturnValue('true')
expect(getCacheInput('uv-cache', '0.2.9')).toBe(false)
expect(mockedCore.warning).toHaveBeenCalledWith(
'Cache requested but uv version is less than 0.3.0. Caching will be disabled.'
)
})

it('getCacheInput returns false if input is false', () => {
mockedCore.getInput.mockReturnValue('false')
expect(getCacheInput('uv-cache', '0.3.0')).toBe(false)
})

it('getCacheInput returns true if input is true and version is null (latest)', () => {
mockedCore.getInput.mockReturnValue('true')
expect(getCacheInput('uv-cache', null)).toBe(true)
})

it('isCacheAllowed returns true for supported versions', () => {
expect(isCacheAllowed('0.3.0')).toBe(true)
expect(isCacheAllowed('0.4.0')).toBe(true)
expect(isCacheAllowed(null)).toBe(true) // null represents latest version
})

it('isCacheAllowed returns false for unsupported versions', () => {
expect(isCacheAllowed('0.2.9')).toBe(false)
expect(isCacheAllowed('0.1.0')).toBe(false)
})

it('getCacheInput returns false if cache is not requested, even if version is supported', () => {
mockedCore.getInput.mockReturnValue('false')
expect(getCacheInput('uv-cache', '0.3.0')).toBe(false)
})

it('getInputs returns cache as false when cache is not requested, even if version is supported', () => {
mockedCore.getInput.mockImplementation(name => {
if (name === 'uv-version') {
return '0.3.0'
}
if (name === 'uv-venv') {
return 'my_venv'
}
if (name === 'uv-cache') {
return 'false'
}
return ''
})
expect(getInputs()).toStrictEqual({
version: '0.3.0',
venv: 'my_venv',
cache: false
})
expect(mockedCore.warning).toHaveBeenCalledWith(
'Using uv version 0.3.0. This may not be the latest version.'
)
})
})
14 changes: 12 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,26 @@
name: Setup uv
description: Set up your GitHub Actions workflow with a specific version of uv
author: Yasser Tahiri

inputs:
uv-version:
description: uv version to use, if version is not provided then latest stable version will be used
description: uv version to use. If not provided, the latest stable version will be used.
required: false
uv-venv:
description: virtual environment name to create and activate.
description: Virtual environment name to create and activate.
required: false
uv-cache:
description: Whether to cache uv packages. Only supported for uv versions >= 0.3.0.
required: false
default: 'false'
uv-cache-dir:
description: Directory to use for uv cache. If not specified, a platform-appropriate default will be used.
required: false

runs:
using: node20
main: dist/index.js

branding:
icon: package
color: purple
Loading
Loading