mirror of
https://github.com/actions/setup-go.git
synced 2026-11-12 23:23:48 +03:00
Compare commits
33
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff769c7905 | ||
|
|
57097ea729 | ||
|
|
aa2cac01fd | ||
|
|
07a8784aee | ||
|
|
ad9941188f | ||
|
|
90ad2b35f6 | ||
|
|
0464ec84ca | ||
|
|
3bfff43db6 | ||
|
|
e626ada899 | ||
|
|
468e940882 | ||
|
|
34829cb9ac | ||
|
|
addc0880c9 | ||
|
|
fba3fb4ead | ||
|
|
5064d95ba1 | ||
|
|
b7ad1dad31 | ||
|
|
0778a10ce4 | ||
|
|
924ae3a1cd | ||
|
|
e91cc3bfe0 | ||
|
|
4a2405e6ae | ||
|
|
78961f6f84 | ||
|
|
4a3601121d | ||
|
|
8f19afcc70 | ||
|
|
27fdb267c1 | ||
|
|
def8c394e3 | ||
|
|
4b73464bb3 | ||
|
|
a5f9b05d2d | ||
|
|
7a3fe6cf4c | ||
|
|
b9adafd441 | ||
|
|
d73f6bcfc2 | ||
|
|
ae252ee6fb | ||
|
|
bf7446afaf | ||
|
|
02aadfee7f | ||
|
|
4aaadf4266 |
No files matched your search
@@ -1,6 +0,0 @@
|
||||
# Ignore list
|
||||
/*
|
||||
|
||||
# Do not ignore these folders:
|
||||
!__tests__/
|
||||
!src/
|
||||
@@ -1,51 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,143 @@
|
||||
name: Validate Microsoft build of Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
|
||||
jobs:
|
||||
microsoft-basic:
|
||||
name: 'Microsoft build of Go ${{ matrix.go-version }} on ${{ matrix.os }}'
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
go-version: ['1.25', '1.24']
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Setup Microsoft build of Go ${{ matrix.go-version }}
|
||||
uses: ./
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
go-download-base-url: 'https://aka.ms/golang/release/latest'
|
||||
cache: false
|
||||
|
||||
- name: Verify Go installation
|
||||
run: go version
|
||||
|
||||
- name: Verify Go env
|
||||
run: go env
|
||||
|
||||
- name: Verify Go is functional
|
||||
shell: bash
|
||||
run: |
|
||||
# Create a simple Go program and run it
|
||||
mkdir -p /tmp/test-go && cd /tmp/test-go
|
||||
cat > main.go << 'EOF'
|
||||
package main
|
||||
import "fmt"
|
||||
func main() {
|
||||
fmt.Println("Hello from Microsoft build of Go!")
|
||||
}
|
||||
EOF
|
||||
go run main.go
|
||||
|
||||
microsoft-env-var:
|
||||
name: 'Microsoft build of Go via env var on ${{ matrix.os }}'
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
env:
|
||||
GO_DOWNLOAD_BASE_URL: 'https://aka.ms/golang/release/latest'
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Setup Microsoft build of Go via environment variable
|
||||
uses: ./
|
||||
with:
|
||||
go-version: '1.25'
|
||||
cache: false
|
||||
|
||||
- name: Verify Go installation
|
||||
run: go version
|
||||
|
||||
- name: Verify Go is functional
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p /tmp/test-go && cd /tmp/test-go
|
||||
cat > main.go << 'EOF'
|
||||
package main
|
||||
import "fmt"
|
||||
func main() {
|
||||
fmt.Println("Hello from Microsoft build of Go via env var!")
|
||||
}
|
||||
EOF
|
||||
go run main.go
|
||||
|
||||
microsoft-architecture:
|
||||
name: 'Microsoft build of Go arch ${{ matrix.architecture }} on ${{ matrix.os }}'
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest]
|
||||
architecture: [x64]
|
||||
include:
|
||||
- os: macos-latest
|
||||
architecture: arm64
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Setup Microsoft build of Go with architecture
|
||||
uses: ./
|
||||
with:
|
||||
go-version: '1.25'
|
||||
go-download-base-url: 'https://aka.ms/golang/release/latest'
|
||||
architecture: ${{ matrix.architecture }}
|
||||
cache: false
|
||||
|
||||
- name: Verify Go installation
|
||||
run: go version
|
||||
|
||||
microsoft-with-cache:
|
||||
name: 'Microsoft build of Go with caching on ${{ matrix.os }}'
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Setup Microsoft build of Go with caching
|
||||
uses: ./
|
||||
with:
|
||||
go-version: '1.25'
|
||||
go-download-base-url: 'https://aka.ms/golang/release/latest'
|
||||
cache: true
|
||||
|
||||
- name: Verify Go installation
|
||||
run: go version
|
||||
|
||||
- name: Verify Go is functional
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p /tmp/test-go && cd /tmp/test-go
|
||||
go mod init test
|
||||
cat > main.go << 'EOF'
|
||||
package main
|
||||
import "fmt"
|
||||
func main() {
|
||||
fmt.Println("Hello from cached Microsoft build of Go!")
|
||||
}
|
||||
EOF
|
||||
go run main.go
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checking out
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Publish
|
||||
id: publish
|
||||
uses: actions/publish-immutable-action@v0.0.4
|
||||
@@ -18,9 +18,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go Stable
|
||||
uses: ./
|
||||
with:
|
||||
@@ -33,9 +33,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go oldStable
|
||||
uses: ./
|
||||
with:
|
||||
@@ -43,21 +43,46 @@ jobs:
|
||||
- name: Verify Go
|
||||
run: go version
|
||||
|
||||
go-env-outputs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go
|
||||
id: setup-go
|
||||
uses: ./
|
||||
with:
|
||||
go-version: stable
|
||||
- name: Show Go environment outputs
|
||||
run: |
|
||||
echo "GOPATH: ${{ steps.setup-go.outputs.GOPATH }}"
|
||||
echo "GOBIN: ${{ steps.setup-go.outputs.GOBIN }}"
|
||||
echo "go-bin-path: ${{ steps.setup-go.outputs.go-bin-path }}"
|
||||
echo "GOROOT: ${{ steps.setup-go.outputs.GOROOT }}"
|
||||
echo "GOCACHE: ${{ steps.setup-go.outputs.GOCACHE }}"
|
||||
echo "GOMODCACHE: ${{ steps.setup-go.outputs.GOMODCACHE }}"
|
||||
echo "GOOS: ${{ steps.setup-go.outputs.GOOS }}"
|
||||
echo "GOARCH: ${{ steps.setup-go.outputs.GOARCH }}"
|
||||
echo "GOTOOLDIR: ${{ steps.setup-go.outputs.GOTOOLDIR }}"
|
||||
|
||||
aliases-arch:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
version: [stable, oldstable]
|
||||
architecture: [x64, x32]
|
||||
exclude:
|
||||
- os: macos-latest
|
||||
architecture: x32
|
||||
- os: macos-latest-large
|
||||
- os: macos-15-intel
|
||||
architecture: x32
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go ${{ matrix.version }} ${{ matrix.architecture }}
|
||||
uses: ./
|
||||
with:
|
||||
@@ -72,7 +97,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest, macos-latest-large]
|
||||
os: [macos-latest, windows-latest, ubuntu-latest, macos-15-intel]
|
||||
go: [1.21.13, 1.22.8, 1.23.2]
|
||||
include:
|
||||
- os: windows-latest
|
||||
@@ -82,7 +107,7 @@ jobs:
|
||||
go: 1.23.2
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: setup-go ${{ matrix.go }}
|
||||
uses: ./
|
||||
@@ -98,10 +123,10 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
go-version: ['1.20', '1.21', '1.22', '1.23']
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go and check latest
|
||||
uses: ./
|
||||
with:
|
||||
@@ -115,9 +140,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go and check latest
|
||||
uses: ./
|
||||
with:
|
||||
@@ -131,9 +156,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go and check latest
|
||||
uses: ./
|
||||
with:
|
||||
@@ -147,9 +172,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go and check latest
|
||||
uses: ./
|
||||
with:
|
||||
@@ -163,9 +188,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go from .go-version file
|
||||
uses: ./
|
||||
with:
|
||||
@@ -179,11 +204,11 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-latest, windows-latest, ubuntu-latest, macos-latest-large]
|
||||
os: [macos-latest, windows-latest, ubuntu-latest, macos-15-intel]
|
||||
go: [1.20.14, 1.21.10, 1.22.8, 1.23.2]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: setup-go ${{ matrix.go }}
|
||||
uses: ./
|
||||
@@ -199,11 +224,11 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [windows-latest, ubuntu-latest, macos-latest-large]
|
||||
os: [windows-latest, ubuntu-latest, macos-15-intel]
|
||||
go: [1.11.12]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: setup-go ${{ matrix.go }}
|
||||
uses: ./
|
||||
@@ -219,7 +244,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-latest-large]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest, macos-15-intel]
|
||||
go-version: [1.20.14, 1.21, 1.22, 1.23]
|
||||
include:
|
||||
- os: macos-latest
|
||||
@@ -228,10 +253,10 @@ jobs:
|
||||
architecture: x64
|
||||
- os: windows-latest
|
||||
architecture: x64
|
||||
- os: macos-latest-large
|
||||
- os: macos-15-intel
|
||||
architecture: x64
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Setup Go and check latest
|
||||
uses: ./
|
||||
with:
|
||||
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
cache: [false, true]
|
||||
go: [1.20.1]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: 'Setup ${{ matrix.cache }}, cache: ${{ matrix.go }}'
|
||||
uses: ./
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
matrix:
|
||||
cache: [false, true]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: 'Setup default go, cache: ${{ matrix.cache }}'
|
||||
uses: ./
|
||||
@@ -121,7 +121,7 @@ jobs:
|
||||
cache: [false]
|
||||
go: [1.20.1]
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: 'Setup ${{ matrix.go }}, cache: ${{ matrix.cache }}'
|
||||
uses: ./
|
||||
|
||||
+5
-1
@@ -10,6 +10,10 @@ allowed:
|
||||
- cc0-1.0
|
||||
- unlicense
|
||||
- 0bsd
|
||||
- blueoak-1.0.0
|
||||
|
||||
reviewed:
|
||||
npm:
|
||||
npm:
|
||||
- "@actions/http-client"
|
||||
- "balanced-match"
|
||||
- "brace-expansion"
|
||||
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Binary file not shown.
Generated
BIN
Binary file not shown.
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
Binary file not shown.
Generated
BIN
Binary file not shown.
Generated
BIN
Binary file not shown.
@@ -1,11 +0,0 @@
|
||||
// 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'
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"printWidth": 80,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": false,
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
@@ -5,375 +5,54 @@
|
||||
|
||||
This action sets up a Go environment for use in GitHub Actions by:
|
||||
|
||||
- Optionally downloading and caching a version of Go by version and adding to PATH
|
||||
- Optionally downloading and caching a version of Go by version and adding it to the PATH
|
||||
- Optionally caching Go modules and build outputs
|
||||
- Registering problem matchers for error output
|
||||
- Providing intelligent caching for Go modules and build outputs
|
||||
|
||||
## Quick Start
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.23'
|
||||
- run: go version
|
||||
```
|
||||
## What's new in V7
|
||||
|
||||
## Breaking Changes
|
||||
The V7 edition of the action includes:
|
||||
- Migrated action internals to ESM for compatibility with latest `@actions/*` packages. No changes to action inputs, outputs, or behavior.
|
||||
|
||||
### V6 Changes
|
||||
## Breaking changes in V6
|
||||
|
||||
#### Node Runtime Upgrade
|
||||
- **Upgraded from Node 20 to Node 24**
|
||||
- ⚠️ **Action Required**: Ensure your runner is on version v2.327.1 or later for compatibility
|
||||
- See [Release Notes](https://github.com/actions/runner/releases/tag/v2.327.1) for more details
|
||||
The V6 edition of the action includes:
|
||||
- **Upgraded Node.js runtime from node20 to node24**
|
||||
> Make sure your runner is on version v2.327.1 or later to ensure compatibility with this release. See [Release Notes](https://github.com/actions/runner/releases/tag/v2.327.1)
|
||||
|
||||
#### Enhanced Go Toolchain Management
|
||||
- **Go toolchain**
|
||||
- Supports both `go` and `toolchain` directives in `go.mod`. If the `toolchain` directive is present, its version is used; otherwise, the action falls back to the `go` directive.
|
||||
|
||||
V6 introduces significant improvements for reliable and consistent Go version selection:
|
||||
- **Cache key update**
|
||||
- By default, the cache key for Go modules is based on `go.mod`. To use `go.sum`, configure the `cache-dependency-path` input.
|
||||
|
||||
**Toolchain Directive Support**
|
||||
Now correctly interprets both `go` and `toolchain` directives from `go.mod`:
|
||||
```go
|
||||
go 1.23.0 // Minimum required version
|
||||
toolchain go1.23.2 // V6 uses this exact version
|
||||
```
|
||||
|
||||
**Intelligent Caching**
|
||||
Cache keys now incorporate the `toolchain` directive version from `go.mod`, eliminating cache conflicts when switching between different toolchain versions within the same Go minor release.
|
||||
|
||||
For more details, see the [full release notes](https://github.com/actions/setup-go/releases/tag/v6.0.0).
|
||||
|
||||
### V5 Changes
|
||||
|
||||
- **Upgraded Node.js runtime from node16 to node20**
|
||||
- See [full release notes](https://github.com/actions/setup-go/releases) for complete details
|
||||
|
||||
## Version Resolution Behavior
|
||||
|
||||
The action follows this resolution order:
|
||||
1. **Local cache** - Checks for a cached version match
|
||||
2. **go-versions repository** - Pulls from the main branch of the [go-versions repository](https://github.com/actions/go-versions/blob/main/versions-manifest.json)
|
||||
3. **Direct download** - Falls back to downloading directly from [go.dev](https://go.dev/dl)
|
||||
|
||||
To change the default behavior, use the `check-latest` input.
|
||||
|
||||
> **Note**: The setup-go action uses executable binaries built by the Golang team. The action does not build golang from source code.
|
||||
See full release notes on the [releases page](https://github.com/actions/setup-go/releases).
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Setup
|
||||
See [action.yml](action.yml).
|
||||
|
||||
<!-- start usage -->
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.23'
|
||||
- run: go run hello.go
|
||||
```
|
||||
|
||||
### Version Specifications
|
||||
|
||||
#### Semantic Versioning
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '^1.23.1' # The Go version to download (if necessary) and use.
|
||||
- run: go version
|
||||
```
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '>=1.22.0'
|
||||
- run: go version
|
||||
```
|
||||
|
||||
> **Important**: Due to YAML parsing behavior, always wrap version numbers in single quotes:
|
||||
> ```yaml
|
||||
> go-version: '1.22' # Correct
|
||||
> go-version: 1.22 # Incorrect - YAML parser interprets as 1.2
|
||||
> ```
|
||||
|
||||
#### Pre-release Versions
|
||||
|
||||
```yaml
|
||||
# RC version
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.24.0-rc.1' # The Go version to download (if necessary) and use
|
||||
- run: go version
|
||||
```
|
||||
|
||||
```yaml
|
||||
# Beta version
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.23.0-beta.1' # The Go version to download (if necessary) and use
|
||||
- run: go version
|
||||
```
|
||||
|
||||
#### Version Aliases
|
||||
|
||||
**Stable Release**
|
||||
|
||||
If `stable` is provided, action will get the latest stable version from the [go-versions](https://github.com/actions/go-versions/blob/main/versions-manifest.json) repository manifest.
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 'stable' # Latest stable version
|
||||
- run: go version
|
||||
```
|
||||
|
||||
**Previous Stable Release**
|
||||
|
||||
If `oldstable` is provided, when the current release is 1.23.x, the action will resolve version as 1.22.x, where x is the latest patch release.
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 'oldstable' # Previous stable version
|
||||
- run: go version
|
||||
```
|
||||
|
||||
> **Note**: Using aliases is equivalent to using the corresponding minor release with `check-latest: true`
|
||||
|
||||
### go-version-file
|
||||
|
||||
The action can automatically detect the Go version from various project files using the `go-version-file` input. This parameter supports `go.mod`, `go.work`, `.go-version`, and `.tool-versions` files.
|
||||
|
||||
> **Note**: If both `go-version` and `go-version-file` are provided, `go-version` takes precedence.
|
||||
|
||||
#### go.mod File
|
||||
|
||||
Automatically detect the Go version from your project's `go.mod` file:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
- run: go version
|
||||
```
|
||||
|
||||
**Version Resolution from go.mod:**
|
||||
1. Uses the `toolchain` directive version if present
|
||||
2. Falls back to the `go` directive version
|
||||
3. If no patch version is specified, uses the latest available patch
|
||||
|
||||
#### go.work File
|
||||
|
||||
Use the Go version specified in your `go.work` file:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'go.work'
|
||||
- run: go version
|
||||
```
|
||||
|
||||
#### .go-version File
|
||||
|
||||
Read the Go version from a `.go-version` file:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: '.go-version'
|
||||
- run: go version
|
||||
```
|
||||
|
||||
#### .tool-versions File
|
||||
|
||||
Use the Go version from an [`.tool-versions`](https://asdf-vm.com/manage/configuration.html#tool-versions) file:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: '.tool-versions'
|
||||
- run: go version
|
||||
```
|
||||
|
||||
#### Custom File Paths
|
||||
|
||||
The action searches for version files relative to the repository root by default. You can specify a custom path:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'path/to/.go-version'
|
||||
- run: go version
|
||||
```
|
||||
|
||||
**Supported Version Formats:**
|
||||
- Major and minor only: `1.25` (action will use the latest patch version, e.g., `1.25.4`)
|
||||
- Major, minor, and patch: `1.25.4` (exact version)
|
||||
|
||||
### Check Latest Version
|
||||
|
||||
The check-latest flag defaults to false for stability. This ensures your workflow uses a specific, predictable Go version.
|
||||
When `check-latest: true`, the action verifies if your cached Go version is the latest available. If not, it downloads and uses the newest version.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.23'
|
||||
check-latest: true # Always check for the latest patch release
|
||||
- run: go version
|
||||
```
|
||||
|
||||
**Performance Considerations:**
|
||||
- `check-latest: false` (default) - Uses cached versions for faster builds
|
||||
- `check-latest: true` - Downloads the latest version, slower but ensures up-to-date releases
|
||||
|
||||
### Caching
|
||||
|
||||
The action features integrated caching for Go modules and build outputs. Built on [toolkit/cache](https://github.com/actions/toolkit/tree/main/packages/cache), it simplifies the caching process by requiring fewer configuration options. The cache input is optional, and caching is turned on by default.
|
||||
|
||||
#### Automatic Caching
|
||||
|
||||
Default behavior: Searches for `go.sum` in the repository root and uses its hash for the cache key.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.23'
|
||||
# cache: true (default)
|
||||
- run: go run hello.go
|
||||
```
|
||||
|
||||
#### Advanced Caching Scenarios
|
||||
|
||||
For advanced scenarios, use `cache-dependency-path` to specify:
|
||||
- **Multiple dependency files**: When your project has dependencies in different directories
|
||||
- **Custom locations**: When your `go.sum` files are not in the repository root
|
||||
- **Monorepos**: When managing multiple Go modules in a single repository
|
||||
- **Glob patterns**: For flexible file matching
|
||||
|
||||
```yaml
|
||||
# Example: Monorepo with multiple go.sum files
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.23'
|
||||
check-latest: true
|
||||
cache-dependency-path: |
|
||||
subdir/go.sum
|
||||
tools/go.sum
|
||||
- run: go run hello.go
|
||||
```
|
||||
|
||||
```yaml
|
||||
# Example: Using glob patterns to match all go.sum files
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.23'
|
||||
cache-dependency-path: "**/*.sum"
|
||||
- run: go run hello.go
|
||||
```
|
||||
|
||||
#### Disable Caching
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: '1.23'
|
||||
cache: false
|
||||
- run: go run hello.go
|
||||
```
|
||||
|
||||
> **Note**: If caching fails, the action logs a warning but continues execution without interrupting your workflow.
|
||||
|
||||
### Matrix Testing
|
||||
|
||||
Test across multiple Go versions:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: ['1.21', '1.22', '1.23']
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- run: go test ./...
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Supported Version Syntax
|
||||
|
||||
The `go-version` input supports the following syntax:
|
||||
|
||||
| Syntax Type | Example | Description |
|
||||
|-------------|---------|-------------|
|
||||
| Specific version | `1.23.2` | Installs this exact version |
|
||||
| Semantic range (caret) | `^1.23.0` | Allows newer minor/patch versions (1.24.x, 1.25.x, etc.) |
|
||||
| Semantic range (tilde) | `~1.23.0` | Allows newer patch versions only (1.23.1, 1.23.2, etc.) |
|
||||
| Comparison (gte) | `>=1.22.0` | Any version equal to or newer than specified |
|
||||
| Comparison (lt) | `<1.24.0` | Any version older than specified |
|
||||
| Comparison range | `>=1.22.0 <1.24.0` | Any version within the specified range |
|
||||
| Wildcard (patch) | `1.23.x` | Latest patch version of the specified minor release |
|
||||
| Wildcard (minor) | `1.*` | Latest available version in the major version |
|
||||
| Pre-release | `1.24.0-rc.1` | Beta/RC versions for testing upcoming releases |
|
||||
| Aliases | `stable`, `oldstable` | Latest stable or previous stable release |
|
||||
|
||||
For more information about semantic versioning, see the [semver documentation](https://semver.org/).
|
||||
|
||||
### Complete Input Reference
|
||||
|
||||
```yaml
|
||||
- uses: actions/setup-go@v6
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
# Version or version range of Go to use
|
||||
go-version: '1.23'
|
||||
|
||||
# Path to go.mod, go.work, .go-version, or .tool-versions file
|
||||
# Note: if both go-version and go-version-file are provided, go-version takes precedence.
|
||||
go-version-file: 'go.mod'
|
||||
|
||||
# Check for latest version
|
||||
# Set this option if you want the action to check for the latest available version
|
||||
# Default: false
|
||||
check-latest: false
|
||||
|
||||
# GitHub token for authentication
|
||||
token: ${{ github.token }}
|
||||
|
||||
# Enable/disable caching
|
||||
# Used to specify whether caching is needed.
|
||||
# Default: true
|
||||
cache: true
|
||||
|
||||
# Path to dependency files for caching
|
||||
@@ -381,44 +60,98 @@ For more information about semantic versioning, see the [semver documentation](h
|
||||
|
||||
# Architecture to install (auto-detected if not specified)
|
||||
architecture: 'x64'
|
||||
|
||||
# Custom base URL for Go downloads (e.g., for mirrors)
|
||||
go-download-base-url: ''
|
||||
```
|
||||
<!-- end usage -->
|
||||
|
||||
## Using setup-go on GHES
|
||||
|
||||
setup-go comes pre-installed on GHES when Actions is enabled. For dynamic Go version downloads, the action fetches distributions from the [go-versions repository](https://github.com/actions/go-versions/) on github.com (external to your appliance).
|
||||
|
||||
These calls to `actions/go-versions` are made via unauthenticated requests, which are limited to 60 requests per hour per IP. If more requests are made within the time frame, then the action leverages the raw API to retrieve the version-manifest. This approach does not impose a rate limit and hence facilitates unrestricted consumption. This is particularly beneficial for GHES runners, which often share the same IP, to avoid the quick exhaustion of the unauthenticated rate limit. If that fails as well the action will try to download versions directly from [go.dev](https://go.dev/dl).
|
||||
|
||||
If that fails as well you can get a higher rate limit with generating a personal access token on github.com and passing it as the token input to the action:
|
||||
**Basic:**
|
||||
|
||||
```yaml
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
token: ${{ secrets.GH_DOTCOM_TOKEN }}
|
||||
go-version: '1.23'
|
||||
steps:
|
||||
- uses: actions/checkout@v7
|
||||
- uses: actions/setup-go@v7
|
||||
with:
|
||||
go-version: '1.25' # The Go version to download (if necessary) and use.
|
||||
- run: go run hello.go
|
||||
```
|
||||
|
||||
### Offline Runners
|
||||
**Version resolution behavior:**
|
||||
|
||||
For runners without github.com access, Go versions must be pre-cached in the runner's tool cache. See "[Setting up the tool cache on self-hosted runners without internet access](https://docs.github.com/en/enterprise-server@3.2/admin/github-actions/managing-access-to-actions-from-githubcom/setting-up-the-tool-cache-on-self-hosted-runners-without-internet-access)".
|
||||
The action resolves the requested version in the following order:
|
||||
1. **Local cache** - Checks the local tool cache for a matching [semver](https://github.com/npm/node-semver#versions) version.
|
||||
2. **go-versions repository** - If the requested version isn’t available in the tool cache, it pulls the version manifest from the `main` branch of the [go-versions repository](https://github.com/actions/go-versions/blob/main/versions-manifest.json).
|
||||
3. **Direct download** - If that lookup misses or fails, it will fall back to downloading directly from the [official Go distribution site](https://go.dev/dl).
|
||||
|
||||
## Recommended Permissions
|
||||
To change the default behavior, please use
|
||||
the [check-latest input](docs/advanced-usage.md#check-latest-version).
|
||||
|
||||
When using the setup-go action in your GitHub Actions workflow, it is recommended to set the following permissions to ensure proper functionality:
|
||||
> **Note**: The `setup-go` action uses executable binaries built by the Go team and does not build Go binaries from source code.
|
||||
|
||||
## Supported version syntax
|
||||
|
||||
The `go-version` input supports the following syntax:
|
||||
|
||||
- Specific versions: `1.25`, `1.24.11`, `1.24.0-rc.1`, `1.23.0-beta.1`
|
||||
- SemVer version range syntax: `^1.25.1`, `~1.24.1`, `>=1.25.0-rc.1`, `<1.25.0`, `>=1.22.0 <1.24.0`
|
||||
- Aliases: `stable`, `oldstable`
|
||||
- Wildcards: `1.25.x`, `1.x`
|
||||
|
||||
For details on Semantic Versioning, see [the semver package documentation](https://github.com/npm/node-semver).
|
||||
|
||||
> **Note**: Due to the peculiarities of YAML parsing, it is recommended to wrap the version in single quotation marks:
|
||||
>
|
||||
> ```yaml
|
||||
> go-version: '1.20'
|
||||
> ```
|
||||
>
|
||||
> The recommendation is based on the YAML parser's behavior, which interprets non-wrapped values as numbers and, in the case of version `1.20`, trims it down to `1.2`, which may not be very obvious.
|
||||
|
||||
For more usage examples, please refer to the section: [Using go-version input](docs/advanced-usage.md#using-the-go-version-input) of the [Advanced usage](docs/advanced-usage.md) guide.
|
||||
|
||||
## Recommended permissions
|
||||
|
||||
When using the `setup-go` action in your GitHub Actions workflow, it is recommended to set the following permissions to ensure proper functionality:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
contents: read # Required to checkout code and install dependencies
|
||||
contents: read # access to check out code and install dependencies
|
||||
```
|
||||
|
||||
## Caching dependency files and build outputs
|
||||
|
||||
The action includes built-in caching and restoration for Go modules and build outputs. It uses
|
||||
[toolkit/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under the hood, but requires less configuration.
|
||||
The `cache` input is optional, and caching is enabled by default. To disable caching, set `cache: false`.
|
||||
|
||||
By default, the action looks for `go.mod` in the repository root and uses its hash as part of the cache key. Use the `cache-dependency-path` input when you have multiple dependency files, or when they’re located in different subdirectories. This input supports glob patterns.
|
||||
|
||||
Module sources (`GOMODCACHE`) and build outputs (`GOCACHE`) are cached separately. On the same runner OS, module sources can be reused across Go versions and runner architectures, while build outputs remain partitioned by Go version, runner architecture, and Linux runner image. The `cache-hit` output is `true` only when both cache entries are restored on their primary keys.
|
||||
|
||||
If caching cannot be performed for any reason, the action logs a warning and continues workflow execution.
|
||||
|
||||
For examples of using `cache-dependency-path`, see the [Caching](docs/advanced-usage.md#caching) section of the [Advanced usage](docs/advanced-usage.md) guide.
|
||||
|
||||
|
||||
## Advanced usage
|
||||
|
||||
- [Using the go-version input](docs/advanced-usage.md#using-the-go-version-input)
|
||||
- [Using the go-version-file input](docs/advanced-usage.md#using-the-go-version-file-input)
|
||||
- [Check latest version](docs/advanced-usage.md#check-latest-version)
|
||||
- [Caching](docs/advanced-usage.md#caching)
|
||||
- [Outputs](docs/advanced-usage.md#outputs)
|
||||
- [Custom download URL](docs/advanced-usage.md#custom-download-url)
|
||||
- [Using `setup-go` on GHES](docs/advanced-usage.md#using-setup-go-on-ghes)
|
||||
|
||||
## License
|
||||
|
||||
The scripts and documentation in this project are released under the [MIT License](LICENSE).
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions are welcome! See our [Contributor's Guide](docs/contributors.md) for details.
|
||||
Contributions are welcome! See our [Contributor's Guide](docs/contributors.md).
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
👋 Be nice. See our [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
:wave: Be nice. See [our code of conduct](CODE_OF_CONDUCT.md)
|
||||
+344
-70
@@ -1,99 +1,373 @@
|
||||
import * as cache from '@actions/cache';
|
||||
import * as core from '@actions/core';
|
||||
import * as glob from '@actions/glob';
|
||||
/// <reference types="node" />
|
||||
|
||||
import * as cacheRestore from '../src/cache-restore';
|
||||
import * as cacheUtils from '../src/cache-utils';
|
||||
import {PackageManagerInfo} from '../src/package-managers';
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
import fs from 'fs';
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/glob', () => ({
|
||||
create: jest.fn(),
|
||||
hashFiles: jest.fn()
|
||||
}));
|
||||
|
||||
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)
|
||||
}));
|
||||
|
||||
// Import real cache-utils (with mocked @actions) before mocking it
|
||||
const realCacheUtils = await import('../src/cache-utils.js');
|
||||
|
||||
jest.unstable_mockModule('../src/cache-utils.js', () => ({
|
||||
...realCacheUtils,
|
||||
getCacheDirectoryPath: jest.fn()
|
||||
}));
|
||||
|
||||
const cache = await import('@actions/cache');
|
||||
const core = await import('@actions/core');
|
||||
const glob = await import('@actions/glob');
|
||||
const cacheRestore = await import('../src/cache-restore.js');
|
||||
const cacheUtils = await import('../src/cache-utils.js');
|
||||
const {run: saveCaches} = await import('../src/cache-save.js');
|
||||
import type {CacheDirectory} from '../src/cache-utils.js';
|
||||
|
||||
function deferred() {
|
||||
let resolve!: () => void;
|
||||
const promise = new Promise<void>(done => {
|
||||
resolve = done;
|
||||
});
|
||||
return {promise, resolve};
|
||||
}
|
||||
|
||||
describe('restoreCache', () => {
|
||||
//Arrange
|
||||
const hashFilesSpy = jest.spyOn(glob, 'hashFiles');
|
||||
const getCacheDirectoryPathSpy = jest.spyOn(
|
||||
cacheUtils,
|
||||
'getCacheDirectoryPath'
|
||||
);
|
||||
const restoreCacheSpy = jest.spyOn(cache, 'restoreCache');
|
||||
const infoSpy = jest.spyOn(core, 'info');
|
||||
const setOutputSpy = jest.spyOn(core, 'setOutput');
|
||||
const hashFilesSpy = glob.hashFiles as jest.Mock<typeof glob.hashFiles>;
|
||||
const getCacheDirectoryPathSpy =
|
||||
cacheUtils.getCacheDirectoryPath as jest.Mock<
|
||||
typeof cacheUtils.getCacheDirectoryPath
|
||||
>;
|
||||
const restoreCacheSpy = cache.restoreCache as jest.Mock<
|
||||
typeof cache.restoreCache
|
||||
>;
|
||||
const saveCacheSpy = cache.saveCache as jest.Mock<typeof cache.saveCache>;
|
||||
const infoSpy = core.info as jest.Mock<typeof core.info>;
|
||||
const setOutputSpy = core.setOutput as jest.Mock<typeof core.setOutput>;
|
||||
|
||||
const versionSpec = '1.13.1';
|
||||
const packageManager = 'default';
|
||||
const cacheDependencyPath = 'path';
|
||||
const modulePath = 'module_cache_directory';
|
||||
const buildPath = 'build_cache_directory';
|
||||
const directories: CacheDirectory[] = [
|
||||
{kind: 'modules', path: modulePath},
|
||||
{kind: 'build', path: buildPath}
|
||||
];
|
||||
const originalEnv = {...process.env};
|
||||
const originalArch = Object.getOwnPropertyDescriptor(process, 'arch')!;
|
||||
|
||||
beforeEach(() => {
|
||||
getCacheDirectoryPathSpy.mockImplementation(
|
||||
(PackageManager: PackageManagerInfo) => {
|
||||
return new Promise<string[]>(resolve => {
|
||||
resolve(['cache_directory_path', 'cache_directory_path']);
|
||||
});
|
||||
}
|
||||
);
|
||||
jest.resetAllMocks();
|
||||
process.env = {
|
||||
...originalEnv,
|
||||
GITHUB_WORKSPACE: '/test/workspace',
|
||||
RUNNER_OS: 'Linux',
|
||||
ImageOS: 'ubuntu24'
|
||||
};
|
||||
Object.defineProperty(process, 'arch', {value: 'x64'});
|
||||
hashFilesSpy.mockResolvedValue('file_hash');
|
||||
getCacheDirectoryPathSpy.mockResolvedValue(directories);
|
||||
restoreCacheSpy.mockResolvedValue(undefined);
|
||||
saveCacheSpy.mockResolvedValue(1);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
process.env = {...originalEnv};
|
||||
Object.defineProperty(process, 'arch', originalArch);
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
const restore = (version = versionSpec) =>
|
||||
cacheRestore.restoreCache(version, packageManager, cacheDependencyPath);
|
||||
|
||||
const requestedKeys = () => {
|
||||
const moduleCall = restoreCacheSpy.mock.calls.find(
|
||||
([paths]) => paths[0] === modulePath
|
||||
);
|
||||
const buildCall = restoreCacheSpy.mock.calls.find(
|
||||
([paths]) => paths[0] === buildPath
|
||||
);
|
||||
expect(moduleCall).toBeDefined();
|
||||
expect(buildCall).toBeDefined();
|
||||
return {modules: moduleCall![1], build: buildCall![1]};
|
||||
};
|
||||
|
||||
it('should throw if dependency file path is not valid', async () => {
|
||||
//Arrange
|
||||
hashFilesSpy.mockImplementation((somePath: string) => {
|
||||
return new Promise<string>(resolve => {
|
||||
resolve('');
|
||||
});
|
||||
});
|
||||
hashFilesSpy.mockResolvedValue('');
|
||||
|
||||
//Act + Assert
|
||||
await expect(async () => {
|
||||
await cacheRestore.restoreCache(
|
||||
versionSpec,
|
||||
packageManager,
|
||||
cacheDependencyPath
|
||||
);
|
||||
}).rejects.toThrow(
|
||||
await expect(restore()).rejects.toThrow(
|
||||
'Some specified paths were not resolved, unable to cache dependencies.'
|
||||
);
|
||||
expect(restoreCacheSpy).not.toHaveBeenCalled();
|
||||
expect(setOutputSpy).toHaveBeenLastCalledWith('cache-hit', false);
|
||||
});
|
||||
|
||||
it('should inform if cache hit is not occured', async () => {
|
||||
//Arrange
|
||||
hashFilesSpy.mockImplementation((somePath: string) => {
|
||||
return new Promise<string>(resolve => {
|
||||
resolve('file_hash');
|
||||
});
|
||||
});
|
||||
it('restores modules and build outputs as separate cache entries', async () => {
|
||||
await restore();
|
||||
|
||||
restoreCacheSpy.mockImplementation(() => {
|
||||
return new Promise<string>(resolve => {
|
||||
resolve('');
|
||||
});
|
||||
});
|
||||
const keys = requestedKeys();
|
||||
expect(keys.modules).not.toBe(keys.build);
|
||||
expect(restoreCacheSpy).toHaveBeenCalledTimes(2);
|
||||
expect(restoreCacheSpy).toHaveBeenCalledWith([modulePath], keys.modules);
|
||||
expect(restoreCacheSpy).toHaveBeenCalledWith([buildPath], keys.build);
|
||||
expect(infoSpy).toHaveBeenCalledWith('modules cache is not found');
|
||||
expect(infoSpy).toHaveBeenCalledWith('build cache is not found');
|
||||
});
|
||||
|
||||
it.each([false, true])(
|
||||
'restores entries sequentially and waits for both (first fails=%s)',
|
||||
async firstFails => {
|
||||
const started = [deferred(), deferred()];
|
||||
const release = [deferred(), deferred()];
|
||||
let completed = false;
|
||||
restoreCacheSpy.mockImplementation(async (paths, key) => {
|
||||
const index = paths[0] === modulePath ? 0 : 1;
|
||||
started[index].resolve();
|
||||
await release[index].promise;
|
||||
if (index === 0 && firstFails) {
|
||||
throw new Error('Module cache unavailable');
|
||||
}
|
||||
return key;
|
||||
});
|
||||
|
||||
const pending = restore().then(() => {
|
||||
completed = true;
|
||||
});
|
||||
try {
|
||||
await started[0].promise;
|
||||
expect(restoreCacheSpy).toHaveBeenCalledTimes(1);
|
||||
expect(core.saveState).not.toHaveBeenCalled();
|
||||
expect(setOutputSpy).not.toHaveBeenCalledWith('cache-hit', true);
|
||||
|
||||
release[0].resolve();
|
||||
await started[1].promise;
|
||||
expect(restoreCacheSpy).toHaveBeenCalledTimes(2);
|
||||
expect(completed).toBe(false);
|
||||
expect(core.saveState).not.toHaveBeenCalled();
|
||||
expect(setOutputSpy).not.toHaveBeenCalledWith('cache-hit', true);
|
||||
} finally {
|
||||
release.forEach(signal => signal.resolve());
|
||||
await pending;
|
||||
}
|
||||
|
||||
expect(setOutputSpy).toHaveBeenLastCalledWith('cache-hit', !firstFails);
|
||||
expect(core.saveState).toHaveBeenCalledTimes(1);
|
||||
}
|
||||
);
|
||||
|
||||
it.each([
|
||||
{modules: false, build: false},
|
||||
{modules: true, build: false},
|
||||
{modules: false, build: true},
|
||||
{modules: true, build: true}
|
||||
])(
|
||||
'reports hits and saves misses independently (modules=$modules, build=$build)',
|
||||
async hits => {
|
||||
const state = new Map<string, string>();
|
||||
(core.saveState as jest.Mock<typeof core.saveState>).mockImplementation(
|
||||
(name, value) => {
|
||||
state.set(name, JSON.stringify(value));
|
||||
}
|
||||
);
|
||||
(core.getState as jest.Mock<typeof core.getState>).mockImplementation(
|
||||
name => state.get(name) || ''
|
||||
);
|
||||
(
|
||||
core.getBooleanInput as jest.Mock<typeof core.getBooleanInput>
|
||||
).mockReturnValue(true);
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
|
||||
restoreCacheSpy.mockImplementation(async (paths, key) =>
|
||||
hits[paths[0] === modulePath ? 'modules' : 'build'] ? key : undefined
|
||||
);
|
||||
|
||||
await restore();
|
||||
const keys = requestedKeys();
|
||||
expect(setOutputSpy).toHaveBeenLastCalledWith(
|
||||
'cache-hit',
|
||||
hits.modules && hits.build
|
||||
);
|
||||
|
||||
// Changing the Go environment later must not change the saved paths.
|
||||
process.env.GOMODCACHE = 'different_module_cache';
|
||||
process.env.GOCACHE = 'different_build_cache';
|
||||
await saveCaches();
|
||||
|
||||
for (const {kind, path} of directories) {
|
||||
if (hits[kind]) {
|
||||
expect(saveCacheSpy).not.toHaveBeenCalledWith([path], keys[kind]);
|
||||
} else {
|
||||
expect(saveCacheSpy).toHaveBeenCalledWith([path], keys[kind]);
|
||||
}
|
||||
}
|
||||
expect(saveCacheSpy).toHaveBeenCalledTimes(
|
||||
Number(!hits.modules) + Number(!hits.build)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it('does not report a full hit for a non-primary cache match', async () => {
|
||||
restoreCacheSpy.mockImplementation(async (_paths, key) => `${key}-other`);
|
||||
|
||||
await restore();
|
||||
|
||||
expect(setOutputSpy).toHaveBeenLastCalledWith('cache-hit', false);
|
||||
});
|
||||
|
||||
it.each(directories)(
|
||||
'continues restoring when the $kind cache fails',
|
||||
async ({kind, path}) => {
|
||||
restoreCacheSpy.mockImplementation(async (paths, key) => {
|
||||
if (paths[0] === path) {
|
||||
throw new Error('Unable to reach the service');
|
||||
}
|
||||
return key;
|
||||
});
|
||||
|
||||
await restore();
|
||||
|
||||
expect(restoreCacheSpy).toHaveBeenCalledTimes(2);
|
||||
expect(core.warning).toHaveBeenCalledWith(
|
||||
`Restore ${kind} cache failed: Unable to reach the service`
|
||||
);
|
||||
expect(setOutputSpy).toHaveBeenLastCalledWith('cache-hit', false);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(directories)(
|
||||
'restores only $kind when the other directory cannot be resolved',
|
||||
async directory => {
|
||||
getCacheDirectoryPathSpy.mockResolvedValue([directory]);
|
||||
restoreCacheSpy.mockImplementation(async (_paths, key) => key);
|
||||
|
||||
await restore();
|
||||
|
||||
expect(restoreCacheSpy).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheSpy).toHaveBeenCalledWith(
|
||||
[directory.path],
|
||||
expect.any(String)
|
||||
);
|
||||
expect(setOutputSpy).toHaveBeenLastCalledWith('cache-hit', false);
|
||||
}
|
||||
);
|
||||
|
||||
it('reuses modules across Go versions without reusing build outputs', async () => {
|
||||
await restore();
|
||||
const before = requestedKeys();
|
||||
restoreCacheSpy.mockClear();
|
||||
|
||||
await restore('1.26.1');
|
||||
const after = requestedKeys();
|
||||
|
||||
expect(after.modules).toBe(before.modules);
|
||||
expect(after.build).not.toBe(before.build);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: 'runner architecture',
|
||||
change: () => Object.defineProperty(process, 'arch', {value: 'arm64'})
|
||||
},
|
||||
{
|
||||
name: 'runner image',
|
||||
change: () => {
|
||||
process.env.ImageOS = 'ubuntu26';
|
||||
}
|
||||
}
|
||||
])('reuses only modules when the $name changes', async ({change}) => {
|
||||
await restore();
|
||||
const before = requestedKeys();
|
||||
restoreCacheSpy.mockClear();
|
||||
|
||||
change();
|
||||
await restore();
|
||||
const after = requestedKeys();
|
||||
|
||||
expect(after.modules).toBe(before.modules);
|
||||
expect(after.build).not.toBe(before.build);
|
||||
});
|
||||
|
||||
it('keeps both cache entries isolated by runner OS', async () => {
|
||||
await restore();
|
||||
const before = requestedKeys();
|
||||
restoreCacheSpy.mockClear();
|
||||
|
||||
process.env.RUNNER_OS = 'Windows';
|
||||
await restore();
|
||||
const after = requestedKeys();
|
||||
|
||||
expect(after.modules).not.toBe(before.modules);
|
||||
expect(after.build).not.toBe(before.build);
|
||||
});
|
||||
|
||||
it('invalidates both cache entries when the dependency hash changes', async () => {
|
||||
await restore();
|
||||
const before = requestedKeys();
|
||||
restoreCacheSpy.mockClear();
|
||||
|
||||
hashFilesSpy.mockResolvedValue('different_file_hash');
|
||||
await restore();
|
||||
const after = requestedKeys();
|
||||
|
||||
expect(after.modules).not.toBe(before.modules);
|
||||
expect(after.build).not.toBe(before.build);
|
||||
});
|
||||
|
||||
it('hashes the configured dependency paths for both cache entries', async () => {
|
||||
const dependencyPaths = 'go.sum\ntools/go.sum';
|
||||
|
||||
//Act + Assert
|
||||
await cacheRestore.restoreCache(
|
||||
versionSpec,
|
||||
packageManager,
|
||||
cacheDependencyPath
|
||||
dependencyPaths
|
||||
);
|
||||
expect(infoSpy).toHaveBeenCalledWith(`Cache is not found`);
|
||||
|
||||
expect(hashFilesSpy).toHaveBeenCalledWith(dependencyPaths);
|
||||
expect(restoreCacheSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('should set output if cache hit is occured', async () => {
|
||||
//Arrange
|
||||
hashFilesSpy.mockImplementation((somePath: string) => {
|
||||
return new Promise<string>(resolve => {
|
||||
resolve('file_hash');
|
||||
});
|
||||
});
|
||||
it('should throw if dependency file is not found in workspace', async () => {
|
||||
jest.spyOn(fs, 'readdirSync').mockReturnValue(['main.go'] as any);
|
||||
|
||||
restoreCacheSpy.mockImplementation(() => {
|
||||
return new Promise<string>(resolve => {
|
||||
resolve('cache_key');
|
||||
});
|
||||
});
|
||||
|
||||
//Act + Assert
|
||||
await cacheRestore.restoreCache(
|
||||
versionSpec,
|
||||
packageManager,
|
||||
cacheDependencyPath
|
||||
await expect(
|
||||
cacheRestore.restoreCache(
|
||||
versionSpec,
|
||||
packageManager
|
||||
// No cacheDependencyPath
|
||||
)
|
||||
).rejects.toThrow(
|
||||
'Dependencies file is not found in /test/workspace. Supported file pattern: go.mod'
|
||||
);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('cache-hit', true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,285 @@
|
||||
/// <reference types="node" />
|
||||
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
getState: jest.fn(),
|
||||
saveState: jest.fn()
|
||||
}));
|
||||
|
||||
const realFs = (await import('fs')).default;
|
||||
const fsExports = {...realFs, existsSync: jest.fn()};
|
||||
jest.unstable_mockModule('fs', () => ({...fsExports, default: fsExports}));
|
||||
|
||||
const cache = await import('@actions/cache');
|
||||
const core = await import('@actions/core');
|
||||
const fs = (await import('fs')).default;
|
||||
const {run} = await import('../src/cache-save.js');
|
||||
import type {CacheEntry} from '../src/cache-utils.js';
|
||||
|
||||
function deferred() {
|
||||
let resolve!: () => void;
|
||||
const promise = new Promise<void>(done => {
|
||||
resolve = done;
|
||||
});
|
||||
return {promise, resolve};
|
||||
}
|
||||
|
||||
describe('cache-save', () => {
|
||||
let entries: CacheEntry[];
|
||||
|
||||
const getBooleanInputSpy = jest.mocked(core.getBooleanInput);
|
||||
const getStateSpy = jest.mocked(core.getState);
|
||||
const infoSpy = jest.mocked(core.info);
|
||||
const warningSpy = jest.mocked(core.warning);
|
||||
const debugSpy = jest.mocked(core.debug);
|
||||
const setFailedSpy = jest.mocked(core.setFailed);
|
||||
const saveCacheSpy = jest.mocked(cache.saveCache);
|
||||
const existsSpy = jest.mocked(fs.existsSync);
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
entries = [
|
||||
{
|
||||
kind: 'modules',
|
||||
path: 'module_cache_directory',
|
||||
primaryKey: 'modules-primary-key'
|
||||
},
|
||||
{
|
||||
kind: 'build',
|
||||
path: 'build_cache_directory',
|
||||
primaryKey: 'build-primary-key'
|
||||
}
|
||||
];
|
||||
|
||||
getBooleanInputSpy.mockReturnValue(true);
|
||||
getStateSpy.mockImplementation(() => JSON.stringify(entries));
|
||||
saveCacheSpy.mockResolvedValue(0);
|
||||
existsSpy.mockReturnValue(true);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it('does not save cache when the cache input is false', async () => {
|
||||
getBooleanInputSpy.mockReturnValue(false);
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheSpy).not.toHaveBeenCalled();
|
||||
expect(warningSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([false, true])(
|
||||
'starts both saves concurrently and waits before exiting (first fails=%s)',
|
||||
async firstFails => {
|
||||
const started = [deferred(), deferred()];
|
||||
const release = [deferred(), deferred()];
|
||||
const exitSpy = jest.spyOn(process, 'exit').mockImplementation(() => {
|
||||
return undefined as never;
|
||||
});
|
||||
saveCacheSpy.mockImplementation(async paths => {
|
||||
const index = paths[0] === entries[0].path ? 0 : 1;
|
||||
started[index].resolve();
|
||||
await release[index].promise;
|
||||
if (index === 0 && firstFails) {
|
||||
throw new Error('Module cache unavailable');
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
|
||||
const pending = run(true);
|
||||
try {
|
||||
await Promise.all(started.map(signal => signal.promise));
|
||||
release[0].resolve();
|
||||
// Let the first completion propagate while the second stays blocked.
|
||||
await new Promise<void>(resolve => setImmediate(resolve));
|
||||
expect(exitSpy).not.toHaveBeenCalled();
|
||||
} finally {
|
||||
release.forEach(signal => signal.resolve());
|
||||
await pending;
|
||||
}
|
||||
|
||||
expect(exitSpy).toHaveBeenCalledTimes(1);
|
||||
expect(exitSpy).toHaveBeenCalledWith(0);
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Cache saved with the key: ${entries[1].primaryKey}`
|
||||
);
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
|
||||
it('does not save cache when there are no cache folders on the disk', async () => {
|
||||
existsSpy.mockReturnValue(false);
|
||||
|
||||
await run();
|
||||
|
||||
for (const {kind, path} of entries) {
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`[warning]${kind} cache folder doesn't exist on disk: ${path}`
|
||||
);
|
||||
}
|
||||
expect(saveCacheSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(['modules', 'build'])(
|
||||
'saves the other cache when the %s directory is missing',
|
||||
async kind => {
|
||||
const missing = entries.find(entry => entry.kind === kind)!;
|
||||
const existing = entries.find(entry => entry.kind !== kind)!;
|
||||
existsSpy.mockImplementation(path => path !== missing.path);
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheSpy).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheSpy).toHaveBeenCalledWith(
|
||||
[existing.path],
|
||||
existing.primaryKey
|
||||
);
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
|
||||
it('does not save cache when no cache entries were prepared', async () => {
|
||||
getStateSpy.mockReturnValue('');
|
||||
|
||||
await run();
|
||||
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
'Cache keys were not generated. Please check the log messages above for more errors or information'
|
||||
);
|
||||
expect(saveCacheSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not fail the workflow if the saved state is invalid', async () => {
|
||||
getStateSpy.mockReturnValue('{');
|
||||
|
||||
await run();
|
||||
|
||||
expect(warningSpy).toHaveBeenCalledWith(expect.any(String));
|
||||
expect(saveCacheSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('does not save either cache when both primary keys were hit', async () => {
|
||||
for (const entry of entries) {
|
||||
entry.matchedKey = entry.primaryKey;
|
||||
}
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheSpy).not.toHaveBeenCalled();
|
||||
expect(warningSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(['modules', 'build'])(
|
||||
'skips only the %s cache when its primary key was hit',
|
||||
async kind => {
|
||||
const hit = entries.find(entry => entry.kind === kind)!;
|
||||
const miss = entries.find(entry => entry.kind !== kind)!;
|
||||
hit.matchedKey = hit.primaryKey;
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheSpy).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheSpy).toHaveBeenCalledWith([miss.path], miss.primaryKey);
|
||||
expect(warningSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
|
||||
it('saves both cache directories under separate keys on a cold run', async () => {
|
||||
await run();
|
||||
|
||||
expect(saveCacheSpy).toHaveBeenCalledTimes(2);
|
||||
for (const {path, primaryKey} of entries) {
|
||||
expect(saveCacheSpy).toHaveBeenCalledWith([path], primaryKey);
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Cache saved with the key: ${primaryKey}`
|
||||
);
|
||||
}
|
||||
expect(warningSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('saves caches restored from non-primary matches', async () => {
|
||||
for (const entry of entries) {
|
||||
entry.matchedKey = `${entry.primaryKey}-other`;
|
||||
}
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheSpy).toHaveBeenCalledTimes(2);
|
||||
expect(warningSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each(['modules', 'build'])(
|
||||
'continues saving when the %s cache reservation fails',
|
||||
async kind => {
|
||||
const failed = entries.find(entry => entry.kind === kind)!;
|
||||
const saved = entries.find(entry => entry.kind !== kind)!;
|
||||
saveCacheSpy.mockImplementation(async paths =>
|
||||
paths[0] === failed.path ? -1 : 1
|
||||
);
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheSpy).toHaveBeenCalledTimes(2);
|
||||
expect(debugSpy).toHaveBeenCalledWith(
|
||||
`Cache was not saved for the key: ${failed.primaryKey}`
|
||||
);
|
||||
expect(infoSpy).not.toHaveBeenCalledWith(
|
||||
`Cache saved with the key: ${failed.primaryKey}`
|
||||
);
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Cache saved with the key: ${saved.primaryKey}`
|
||||
);
|
||||
expect(warningSpy).not.toHaveBeenCalled();
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
|
||||
it.each(['modules', 'build'])(
|
||||
'continues saving when the %s cache upload throws',
|
||||
async kind => {
|
||||
const failed = entries.find(entry => entry.kind === kind)!;
|
||||
const saved = entries.find(entry => entry.kind !== kind)!;
|
||||
saveCacheSpy.mockImplementation(async paths => {
|
||||
if (paths[0] === failed.path) {
|
||||
throw new Error('Unable to reach the service');
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheSpy).toHaveBeenCalledTimes(2);
|
||||
expect(warningSpy).toHaveBeenCalledWith(
|
||||
`Save ${kind} cache failed: Unable to reach the service`
|
||||
);
|
||||
expect(infoSpy).toHaveBeenCalledWith(
|
||||
`Cache saved with the key: ${saved.primaryKey}`
|
||||
);
|
||||
expect(setFailedSpy).not.toHaveBeenCalled();
|
||||
}
|
||||
);
|
||||
});
|
||||
+117
-56
@@ -1,22 +1,60 @@
|
||||
import * as exec from '@actions/exec';
|
||||
import * as cache from '@actions/cache';
|
||||
import * as core from '@actions/core';
|
||||
import * as cacheUtils from '../src/cache-utils';
|
||||
import {PackageManagerInfo} from '../src/package-managers';
|
||||
import {jest, describe, it, expect, beforeEach, afterAll} from '@jest/globals';
|
||||
|
||||
jest.unstable_mockModule('@actions/exec', () => ({
|
||||
exec: jest.fn(),
|
||||
getExecOutput: jest.fn()
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/cache', () => ({
|
||||
saveCache: jest.fn(),
|
||||
restoreCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn()
|
||||
}));
|
||||
|
||||
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)
|
||||
}));
|
||||
|
||||
const exec = await import('@actions/exec');
|
||||
const cache = await import('@actions/cache');
|
||||
const core = await import('@actions/core');
|
||||
const cacheUtils = await import('../src/cache-utils.js');
|
||||
import type {PackageManagerInfo} from '../src/package-managers.js';
|
||||
|
||||
describe('getCommandOutput', () => {
|
||||
//Arrange
|
||||
const getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
|
||||
const getExecOutputSpy = exec.getExecOutput as jest.Mock<
|
||||
typeof exec.getExecOutput
|
||||
>;
|
||||
|
||||
it('should return trimmed stdout in case of successful exit code', async () => {
|
||||
//Arrange
|
||||
const stdoutResult = ' stdout ';
|
||||
const trimmedStdout = stdoutResult.trim();
|
||||
|
||||
getExecOutputSpy.mockImplementation((commandLine: string) => {
|
||||
return new Promise<exec.ExecOutput>(resolve => {
|
||||
resolve({exitCode: 0, stdout: stdoutResult, stderr: ''});
|
||||
});
|
||||
getExecOutputSpy.mockImplementation(async (commandLine: string) => {
|
||||
return {exitCode: 0, stdout: stdoutResult, stderr: ''};
|
||||
});
|
||||
|
||||
//Act + Assert
|
||||
@@ -29,10 +67,8 @@ describe('getCommandOutput', () => {
|
||||
//Arrange
|
||||
const stderrResult = 'error message';
|
||||
|
||||
getExecOutputSpy.mockImplementation((commandLine: string) => {
|
||||
return new Promise<exec.ExecOutput>(resolve => {
|
||||
resolve({exitCode: 10, stdout: '', stderr: stderrResult});
|
||||
});
|
||||
getExecOutputSpy.mockImplementation(async (commandLine: string) => {
|
||||
return {exitCode: 10, stdout: '', stderr: stderrResult};
|
||||
});
|
||||
|
||||
//Act + Assert
|
||||
@@ -47,8 +83,11 @@ describe('getPackageManagerInfo', () => {
|
||||
//Arrange
|
||||
const packageManagerName = 'default';
|
||||
const expectedResult = {
|
||||
dependencyFilePattern: 'go.sum',
|
||||
cacheFolderCommandList: ['go env GOMODCACHE', 'go env GOCACHE']
|
||||
dependencyFilePattern: 'go.mod',
|
||||
cacheFolderCommandList: [
|
||||
{kind: 'modules', command: 'go env GOMODCACHE'},
|
||||
{kind: 'build', command: 'go env GOCACHE'}
|
||||
]
|
||||
};
|
||||
|
||||
//Act + Assert
|
||||
@@ -70,22 +109,35 @@ describe('getPackageManagerInfo', () => {
|
||||
|
||||
describe('getCacheDirectoryPath', () => {
|
||||
//Arrange
|
||||
const getExecOutputSpy = jest.spyOn(exec, 'getExecOutput');
|
||||
const getExecOutputSpy = exec.getExecOutput as jest.Mock<
|
||||
typeof exec.getExecOutput
|
||||
>;
|
||||
|
||||
const validPackageManager: PackageManagerInfo = {
|
||||
dependencyFilePattern: 'go.sum',
|
||||
cacheFolderCommandList: ['go env GOMODCACHE', 'go env GOCACHE']
|
||||
dependencyFilePattern: 'go.mod',
|
||||
cacheFolderCommandList: [
|
||||
{kind: 'modules', command: 'go env GOMODCACHE'},
|
||||
{kind: 'build', command: 'go env GOCACHE'}
|
||||
]
|
||||
};
|
||||
|
||||
it('should return path to the cache folders which specified package manager uses', async () => {
|
||||
//Arrange
|
||||
getExecOutputSpy.mockImplementation((commandLine: string) => {
|
||||
return new Promise<exec.ExecOutput>(resolve => {
|
||||
resolve({exitCode: 0, stdout: 'path/to/cache/folder', stderr: ''});
|
||||
});
|
||||
getExecOutputSpy.mockImplementation(async (commandLine: string) => {
|
||||
return {
|
||||
exitCode: 0,
|
||||
stdout:
|
||||
commandLine === 'go env GOMODCACHE'
|
||||
? 'path/to/modules'
|
||||
: 'path/to/build',
|
||||
stderr: ''
|
||||
};
|
||||
});
|
||||
|
||||
const expectedResult = ['path/to/cache/folder', 'path/to/cache/folder'];
|
||||
const expectedResult = [
|
||||
{kind: 'modules', path: 'path/to/modules'},
|
||||
{kind: 'build', path: 'path/to/build'}
|
||||
];
|
||||
|
||||
//Act + Assert
|
||||
return cacheUtils
|
||||
@@ -93,33 +145,43 @@ describe('getCacheDirectoryPath', () => {
|
||||
.then(data => expect(data).toEqual(expectedResult));
|
||||
});
|
||||
|
||||
it('should return path to the cache folder if one command return empty str', async () => {
|
||||
//Arrange
|
||||
getExecOutputSpy.mockImplementationOnce((commandLine: string) => {
|
||||
return new Promise<exec.ExecOutput>(resolve => {
|
||||
resolve({exitCode: 0, stdout: 'path/to/cache/folder', stderr: ''});
|
||||
it.each(validPackageManager.cacheFolderCommandList)(
|
||||
'preserves the $kind identity when the other command returns an empty path',
|
||||
async ({kind, command}) => {
|
||||
getExecOutputSpy.mockImplementation(async commandLine => ({
|
||||
exitCode: 0,
|
||||
stdout: commandLine === command ? 'path/to/cache/folder' : '',
|
||||
stderr: ''
|
||||
}));
|
||||
|
||||
await expect(
|
||||
cacheUtils.getCacheDirectoryPath(validPackageManager)
|
||||
).resolves.toEqual([{kind, path: 'path/to/cache/folder'}]);
|
||||
}
|
||||
);
|
||||
|
||||
it.each(validPackageManager.cacheFolderCommandList)(
|
||||
'preserves the $kind identity when the other command fails',
|
||||
async ({kind, command}) => {
|
||||
getExecOutputSpy.mockImplementation(async commandLine => {
|
||||
if (commandLine !== command) {
|
||||
throw new Error('Could not run go env');
|
||||
}
|
||||
return {exitCode: 0, stdout: 'path/to/cache/folder', stderr: ''};
|
||||
});
|
||||
});
|
||||
|
||||
getExecOutputSpy.mockImplementationOnce((commandLine: string) => {
|
||||
return new Promise<exec.ExecOutput>(resolve => {
|
||||
resolve({exitCode: 0, stdout: '', stderr: ''});
|
||||
});
|
||||
});
|
||||
|
||||
const expectedResult = ['path/to/cache/folder'];
|
||||
|
||||
//Act + Assert
|
||||
return cacheUtils
|
||||
.getCacheDirectoryPath(validPackageManager)
|
||||
.then(data => expect(data).toEqual(expectedResult));
|
||||
});
|
||||
await expect(
|
||||
cacheUtils.getCacheDirectoryPath(validPackageManager)
|
||||
).resolves.toEqual([{kind, path: 'path/to/cache/folder'}]);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Could not run go env')
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it('should throw if the both commands return empty str', async () => {
|
||||
getExecOutputSpy.mockImplementation((commandLine: string) => {
|
||||
return new Promise<exec.ExecOutput>(resolve => {
|
||||
resolve({exitCode: 10, stdout: '', stderr: ''});
|
||||
});
|
||||
getExecOutputSpy.mockImplementation(async (commandLine: string) => {
|
||||
return {exitCode: 0, stdout: '', stderr: ''};
|
||||
});
|
||||
|
||||
//Act + Assert
|
||||
@@ -128,11 +190,9 @@ describe('getCacheDirectoryPath', () => {
|
||||
}).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('should throw if the specified package name is invalid', async () => {
|
||||
getExecOutputSpy.mockImplementation((commandLine: string) => {
|
||||
return new Promise<exec.ExecOutput>(resolve => {
|
||||
resolve({exitCode: 10, stdout: '', stderr: 'Error message'});
|
||||
});
|
||||
it('should throw if both commands fail', async () => {
|
||||
getExecOutputSpy.mockImplementation(async (commandLine: string) => {
|
||||
return {exitCode: 10, stdout: '', stderr: 'Error message'};
|
||||
});
|
||||
|
||||
//Act + Assert
|
||||
@@ -144,8 +204,10 @@ describe('getCacheDirectoryPath', () => {
|
||||
|
||||
describe('isCacheFeatureAvailable', () => {
|
||||
//Arrange
|
||||
const isFeatureAvailableSpy = jest.spyOn(cache, 'isFeatureAvailable');
|
||||
const warningSpy = jest.spyOn(core, 'warning');
|
||||
const isFeatureAvailableSpy = cache.isFeatureAvailable as jest.Mock<
|
||||
typeof cache.isFeatureAvailable
|
||||
>;
|
||||
const warningSpy = core.warning as jest.Mock<typeof core.warning>;
|
||||
|
||||
it('should return true when cache feature is available', () => {
|
||||
//Arrange
|
||||
@@ -211,10 +273,9 @@ describe('isCacheFeatureAvailable', () => {
|
||||
});
|
||||
|
||||
describe('isGhes', () => {
|
||||
const pristineEnv = process.env;
|
||||
const pristineEnv = {...process.env};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
process.env = {...pristineEnv};
|
||||
});
|
||||
|
||||
|
||||
+721
-79
@@ -1,17 +1,101 @@
|
||||
import * as core from '@actions/core';
|
||||
import * as io from '@actions/io';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import {
|
||||
jest,
|
||||
describe,
|
||||
it,
|
||||
expect,
|
||||
beforeAll,
|
||||
beforeEach,
|
||||
afterEach,
|
||||
afterAll
|
||||
} from '@jest/globals';
|
||||
import fs from 'fs';
|
||||
import cp from 'child_process';
|
||||
import osm, {type} from 'os';
|
||||
import path from 'path';
|
||||
import * as main from '../src/main';
|
||||
import * as im from '../src/installer';
|
||||
import * as httpm from '@actions/http-client';
|
||||
|
||||
import goJsonData from './data/golang-dl.json';
|
||||
import matchers from '../matchers.json';
|
||||
import goTestManifest from './data/versions-manifest.json';
|
||||
import goJsonData from './data/golang-dl.json' with {type: 'json'};
|
||||
import matchers from '../matchers.json' with {type: 'json'};
|
||||
import goTestManifest from './data/versions-manifest.json' with {type: 'json'};
|
||||
|
||||
import type {IGoVersion} from '../src/installer.js';
|
||||
import type {IToolRelease} from '@actions/tool-cache';
|
||||
import {GO_ENV_OUTPUTS} from '../src/constants.js';
|
||||
|
||||
const httpClientGetJson = jest.fn();
|
||||
|
||||
const realCore = await import('@actions/core');
|
||||
|
||||
jest.unstable_mockModule('@actions/core', () => ({
|
||||
...realCore,
|
||||
getInput: jest.fn(),
|
||||
getBooleanInput: jest.fn(),
|
||||
info: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
setOutput: jest.fn(),
|
||||
exportVariable: jest.fn()
|
||||
}));
|
||||
|
||||
const realOs = (await import('os')).default;
|
||||
const osPlatformMock = jest.fn();
|
||||
const osArchMock = jest.fn();
|
||||
const osExports = {
|
||||
...realOs,
|
||||
platform: osPlatformMock,
|
||||
arch: osArchMock
|
||||
};
|
||||
jest.unstable_mockModule('os', () => ({...osExports, default: osExports}));
|
||||
|
||||
const realPath = (await import('path')).default;
|
||||
const pathJoinMock = jest.fn();
|
||||
const pathExports = {
|
||||
...realPath,
|
||||
join: pathJoinMock
|
||||
};
|
||||
jest.unstable_mockModule('path', () => ({
|
||||
...pathExports,
|
||||
default: pathExports
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('@actions/io', () => ({
|
||||
which: jest.fn(),
|
||||
mkdirP: jest.fn(),
|
||||
rmRF: jest.fn(),
|
||||
mv: jest.fn(),
|
||||
cp: jest.fn()
|
||||
}));
|
||||
|
||||
const realTc = await import('@actions/tool-cache');
|
||||
|
||||
jest.unstable_mockModule('@actions/tool-cache', () => ({
|
||||
...realTc,
|
||||
find: jest.fn(),
|
||||
downloadTool: jest.fn(),
|
||||
extractTar: jest.fn(),
|
||||
extractZip: jest.fn(),
|
||||
cacheDir: jest.fn(),
|
||||
getManifestFromRepo: jest.fn()
|
||||
}));
|
||||
|
||||
const realHttp = await import('@actions/http-client');
|
||||
|
||||
jest.unstable_mockModule('@actions/http-client', () => ({
|
||||
...realHttp,
|
||||
HttpClient: jest.fn().mockImplementation(() => ({
|
||||
getJson: httpClientGetJson
|
||||
}))
|
||||
}));
|
||||
|
||||
jest.unstable_mockModule('../src/go-version-fetch.js', () => ({
|
||||
getVersionsDist: jest.fn()
|
||||
}));
|
||||
|
||||
const core = await import('@actions/core');
|
||||
const io = await import('@actions/io');
|
||||
const tc = await import('@actions/tool-cache');
|
||||
const vf = await import('../src/go-version-fetch.js');
|
||||
const main = await import('../src/main.js');
|
||||
const im = await import('../src/installer.js');
|
||||
const osm = (await import('os')).default;
|
||||
const path = (await import('path')).default;
|
||||
const matcherPattern = matchers.problemMatcher[0].pattern[0];
|
||||
const matcherRegExp = new RegExp(matcherPattern.regexp);
|
||||
const win32Join = path.win32.join;
|
||||
@@ -23,31 +107,33 @@ describe('setup-go', () => {
|
||||
let inputs = {} as any;
|
||||
let os = {} as any;
|
||||
|
||||
let inSpy: jest.SpyInstance;
|
||||
let getBooleanInputSpy: jest.SpyInstance;
|
||||
let exportVarSpy: jest.SpyInstance;
|
||||
let findSpy: jest.SpyInstance;
|
||||
let cnSpy: jest.SpyInstance;
|
||||
let logSpy: jest.SpyInstance;
|
||||
let getSpy: jest.SpyInstance;
|
||||
let platSpy: jest.SpyInstance;
|
||||
let archSpy: jest.SpyInstance;
|
||||
let joinSpy: jest.SpyInstance;
|
||||
let dlSpy: jest.SpyInstance;
|
||||
let extractTarSpy: jest.SpyInstance;
|
||||
let extractZipSpy: jest.SpyInstance;
|
||||
let cacheSpy: jest.SpyInstance;
|
||||
let dbgSpy: jest.SpyInstance;
|
||||
let whichSpy: jest.SpyInstance;
|
||||
let existsSpy: jest.SpyInstance;
|
||||
let readFileSpy: jest.SpyInstance;
|
||||
let mkdirpSpy: jest.SpyInstance;
|
||||
let mkdirSpy: jest.SpyInstance;
|
||||
let symlinkSpy: jest.SpyInstance;
|
||||
let execSpy: jest.SpyInstance;
|
||||
let getManifestSpy: jest.SpyInstance;
|
||||
let getAllVersionsSpy: jest.SpyInstance;
|
||||
let httpmGetJsonSpy: jest.SpyInstance;
|
||||
let inSpy: jest.Mock<typeof core.getInput>;
|
||||
let getBooleanInputSpy: jest.Mock<typeof core.getBooleanInput>;
|
||||
let exportVarSpy: jest.Mock<typeof core.exportVariable>;
|
||||
let findSpy: jest.Mock;
|
||||
let cnSpy: jest.SpiedFunction<typeof process.stdout.write>;
|
||||
let logSpy: jest.Mock;
|
||||
let setOutputSpy: jest.Mock<typeof core.setOutput>;
|
||||
let warningSpy: jest.Mock<typeof core.warning>;
|
||||
let getSpy: jest.Mock;
|
||||
let platSpy: jest.Mock;
|
||||
let archSpy: jest.Mock;
|
||||
let joinSpy: jest.Mock<typeof path.join>;
|
||||
let dlSpy: jest.Mock;
|
||||
let extractTarSpy: jest.Mock;
|
||||
let extractZipSpy: jest.Mock;
|
||||
let cacheSpy: jest.Mock;
|
||||
let dbgSpy: jest.Mock;
|
||||
let whichSpy: jest.Mock;
|
||||
let existsSpy: jest.SpiedFunction<typeof fs.existsSync>;
|
||||
let readFileSpy: jest.SpiedFunction<typeof fs.readFileSync>;
|
||||
let mkdirpSpy: jest.Mock;
|
||||
let mkdirSpy: jest.SpiedFunction<typeof fs.mkdir>;
|
||||
let symlinkSpy: jest.SpiedFunction<typeof fs.symlinkSync>;
|
||||
let execSpy: jest.SpiedFunction<typeof cp.execSync>;
|
||||
let execFileSpy: jest.SpiedFunction<typeof cp.execFileSync>;
|
||||
let getManifestSpy: jest.Mock;
|
||||
let httpmGetJsonSpy: jest.Mock;
|
||||
|
||||
beforeAll(async () => {
|
||||
process.env['GITHUB_ENV'] = ''; // Stub out Environment file functionality so we can verify it writes to standard out (toolkit is backwards compatible)
|
||||
@@ -58,22 +144,28 @@ describe('setup-go', () => {
|
||||
|
||||
// @actions/core
|
||||
inputs = {};
|
||||
inSpy = jest.spyOn(core, 'getInput');
|
||||
inSpy = core.getInput as jest.Mock<typeof core.getInput>;
|
||||
inSpy.mockImplementation(name => inputs[name]);
|
||||
getBooleanInputSpy = jest.spyOn(core, 'getBooleanInput');
|
||||
getBooleanInputSpy = core.getBooleanInput as jest.Mock<
|
||||
typeof core.getBooleanInput
|
||||
>;
|
||||
getBooleanInputSpy.mockImplementation(name => inputs[name]);
|
||||
exportVarSpy = jest.spyOn(core, 'exportVariable');
|
||||
exportVarSpy = core.exportVariable as jest.Mock<typeof core.exportVariable>;
|
||||
|
||||
// node
|
||||
os = {};
|
||||
platSpy = jest.spyOn(osm, 'platform');
|
||||
platSpy = osm.platform as jest.Mock;
|
||||
platSpy.mockImplementation(() => os['platform']);
|
||||
archSpy = jest.spyOn(osm, 'arch');
|
||||
archSpy = osm.arch as jest.Mock;
|
||||
archSpy.mockImplementation(() => os['arch']);
|
||||
execSpy = jest.spyOn(cp, 'execSync');
|
||||
execFileSpy = jest.spyOn(cp, 'execFileSync');
|
||||
execFileSpy.mockImplementation(() => {
|
||||
throw new Error('ENOENT');
|
||||
});
|
||||
|
||||
// switch path join behaviour based on set os.platform
|
||||
joinSpy = jest.spyOn(path, 'join');
|
||||
joinSpy = path.join as jest.Mock<typeof path.join>;
|
||||
joinSpy.mockImplementation((...paths: string[]): string => {
|
||||
if (os['platform'] == 'win32') {
|
||||
return win32Join(...paths);
|
||||
@@ -83,23 +175,22 @@ describe('setup-go', () => {
|
||||
});
|
||||
|
||||
// @actions/tool-cache
|
||||
findSpy = jest.spyOn(tc, 'find');
|
||||
dlSpy = jest.spyOn(tc, 'downloadTool');
|
||||
extractTarSpy = jest.spyOn(tc, 'extractTar');
|
||||
extractZipSpy = jest.spyOn(tc, 'extractZip');
|
||||
cacheSpy = jest.spyOn(tc, 'cacheDir');
|
||||
getSpy = jest.spyOn(im, 'getVersionsDist');
|
||||
getManifestSpy = jest.spyOn(tc, 'getManifestFromRepo');
|
||||
getAllVersionsSpy = jest.spyOn(im, 'getManifest');
|
||||
findSpy = tc.find as jest.Mock;
|
||||
dlSpy = tc.downloadTool as jest.Mock;
|
||||
extractTarSpy = tc.extractTar as jest.Mock;
|
||||
extractZipSpy = tc.extractZip as jest.Mock;
|
||||
cacheSpy = tc.cacheDir as jest.Mock;
|
||||
getSpy = vf.getVersionsDist as jest.Mock;
|
||||
getManifestSpy = tc.getManifestFromRepo as jest.Mock;
|
||||
|
||||
// httm
|
||||
httpmGetJsonSpy = jest.spyOn(httpm.HttpClient.prototype, 'getJson');
|
||||
httpmGetJsonSpy = httpClientGetJson;
|
||||
|
||||
// io
|
||||
whichSpy = jest.spyOn(io, 'which');
|
||||
whichSpy = io.which as jest.Mock;
|
||||
existsSpy = jest.spyOn(fs, 'existsSync');
|
||||
readFileSpy = jest.spyOn(fs, 'readFileSync');
|
||||
mkdirpSpy = jest.spyOn(io, 'mkdirP');
|
||||
mkdirpSpy = io.mkdirP as jest.Mock;
|
||||
|
||||
// fs
|
||||
mkdirSpy = jest.spyOn(fs, 'mkdir');
|
||||
@@ -107,30 +198,28 @@ describe('setup-go', () => {
|
||||
symlinkSpy.mockImplementation(() => {});
|
||||
|
||||
// gets
|
||||
getManifestSpy.mockImplementation(() => <tc.IToolRelease[]>goTestManifest);
|
||||
getManifestSpy.mockImplementation(() => goTestManifest as IToolRelease[]);
|
||||
|
||||
// writes
|
||||
cnSpy = jest.spyOn(process.stdout, 'write');
|
||||
logSpy = jest.spyOn(core, 'info');
|
||||
dbgSpy = jest.spyOn(core, 'debug');
|
||||
getSpy.mockImplementation(() => <im.IGoVersion[] | null>goJsonData);
|
||||
cnSpy.mockImplementation(line => {
|
||||
// uncomment to debug
|
||||
// process.stderr.write('write:' + line + '\n');
|
||||
});
|
||||
logSpy.mockImplementation(line => {
|
||||
// uncomment to debug
|
||||
//process.stderr.write('log:' + line + '\n');
|
||||
});
|
||||
dbgSpy.mockImplementation(msg => {
|
||||
// uncomment to see debug output
|
||||
// process.stderr.write(msg + '\n');
|
||||
});
|
||||
logSpy = core.info as jest.Mock;
|
||||
setOutputSpy = core.setOutput as jest.Mock<typeof core.setOutput>;
|
||||
warningSpy = core.warning as jest.Mock<typeof core.warning>;
|
||||
dbgSpy = core.debug as jest.Mock;
|
||||
getSpy.mockImplementation(() => goJsonData as IGoVersion[] | null);
|
||||
cnSpy.mockImplementation(() => true);
|
||||
logSpy.mockImplementation(() => {});
|
||||
dbgSpy.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// clear out env var set during 'run'
|
||||
// clear out env vars set during 'run'
|
||||
delete process.env[im.GOTOOLCHAIN_ENV_VAR];
|
||||
delete process.env['GO_DOWNLOAD_BASE_URL'];
|
||||
|
||||
// reset the exit code that core.setFailed sets on the error-path tests so
|
||||
// that a passing run does not leak a non-zero process exit code
|
||||
process.exitCode = 0;
|
||||
|
||||
//jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
@@ -165,8 +254,10 @@ describe('setup-go', () => {
|
||||
});
|
||||
|
||||
it('should return manifest from raw URL if repo fetch fails', async () => {
|
||||
getManifestSpy.mockRejectedValue(new Error('Fetch failed'));
|
||||
httpmGetJsonSpy.mockResolvedValue({
|
||||
(getManifestSpy as jest.Mock<any>).mockRejectedValue(
|
||||
new Error('Fetch failed')
|
||||
);
|
||||
(httpmGetJsonSpy as jest.Mock<any>).mockResolvedValue({
|
||||
result: goTestManifest
|
||||
});
|
||||
const manifest = await im.getManifest(undefined);
|
||||
@@ -205,7 +296,7 @@ describe('setup-go', () => {
|
||||
os.arch = 'x64';
|
||||
|
||||
// spec: 1.13.0 => 1.13
|
||||
const match: im.IGoVersion | undefined = await im.findMatch('1.13.0');
|
||||
const match: IGoVersion | undefined = await im.findMatch('1.13.0');
|
||||
expect(match).toBeDefined();
|
||||
const version: string = match ? match.version : '';
|
||||
expect(version).toBe('go1.13');
|
||||
@@ -218,7 +309,7 @@ describe('setup-go', () => {
|
||||
os.arch = 'x64';
|
||||
|
||||
// spec: 1.13 => 1.13.7 (latest)
|
||||
const match: im.IGoVersion | undefined = await im.findMatch('1.13');
|
||||
const match: IGoVersion | undefined = await im.findMatch('1.13');
|
||||
expect(match).toBeDefined();
|
||||
const version: string = match ? match.version : '';
|
||||
expect(version).toBe('go1.13.7');
|
||||
@@ -231,7 +322,7 @@ describe('setup-go', () => {
|
||||
os.arch = 'x64';
|
||||
|
||||
// spec: ^1.13.6 => 1.13.7
|
||||
const match: im.IGoVersion | undefined = await im.findMatch('^1.13.6');
|
||||
const match: IGoVersion | undefined = await im.findMatch('^1.13.6');
|
||||
expect(match).toBeDefined();
|
||||
const version: string = match ? match.version : '';
|
||||
expect(version).toBe('go1.13.7');
|
||||
@@ -244,7 +335,7 @@ describe('setup-go', () => {
|
||||
os.arch = 'x32';
|
||||
|
||||
// spec: 1 => 1.13.7 (latest)
|
||||
const match: im.IGoVersion | undefined = await im.findMatch('1');
|
||||
const match: IGoVersion | undefined = await im.findMatch('1');
|
||||
expect(match).toBeDefined();
|
||||
const version: string = match ? match.version : '';
|
||||
expect(version).toBe('go1.13.7');
|
||||
@@ -257,7 +348,7 @@ describe('setup-go', () => {
|
||||
os.arch = 'x64';
|
||||
|
||||
// spec: 1.14, stable=false => go1.14rc1
|
||||
const match: im.IGoVersion | undefined = await im.findMatch('1.14.0-rc.1');
|
||||
const match: IGoVersion | undefined = await im.findMatch('1.14.0-rc.1');
|
||||
expect(match).toBeDefined();
|
||||
const version: string = match ? match.version : '';
|
||||
expect(version).toBe('go1.14rc1');
|
||||
@@ -562,6 +653,106 @@ describe('setup-go', () => {
|
||||
expect(added).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('go env outputs', () => {
|
||||
const goEnv = {
|
||||
GOPATH: '/Users/testuser/go',
|
||||
GOBIN: '',
|
||||
GOROOT: '/usr/local/go',
|
||||
GOCACHE: '/Users/testuser/.cache/go-build',
|
||||
GOMODCACHE: '/Users/testuser/go/pkg/mod',
|
||||
GOOS: 'darwin',
|
||||
GOARCH: 'arm64',
|
||||
GOTOOLDIR: '/usr/local/go/pkg/tool/darwin_arm64'
|
||||
};
|
||||
|
||||
it('parses the output of go env -json', () => {
|
||||
execFileSpy.mockImplementation(() => JSON.stringify(goEnv));
|
||||
|
||||
expect(main.readGoEnv('/usr/local/go/bin/go')).toEqual(goEnv);
|
||||
});
|
||||
|
||||
it('returns undefined when go env -json is not supported', () => {
|
||||
execFileSpy.mockImplementation(() => {
|
||||
throw new Error('flag provided but not defined: -json');
|
||||
});
|
||||
|
||||
expect(main.readGoEnv('/usr/local/go/bin/go')).toBeUndefined();
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining('flag provided but not defined: -json')
|
||||
);
|
||||
expect(warningSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('returns undefined when the output is not valid JSON', () => {
|
||||
execFileSpy.mockImplementation(() => 'GOPATH="/Users/testuser/go"');
|
||||
|
||||
expect(main.readGoEnv('/usr/local/go/bin/go')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('returns undefined when the output is not a JSON object', () => {
|
||||
execFileSpy.mockImplementation(() => '[]');
|
||||
|
||||
expect(main.readGoEnv('/usr/local/go/bin/go')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('sets an output for each exposed variable', () => {
|
||||
main.setGoEnvOutputs(goEnv);
|
||||
|
||||
expect(GO_ENV_OUTPUTS).toEqual([
|
||||
'GOPATH',
|
||||
'GOBIN',
|
||||
'GOROOT',
|
||||
'GOCACHE',
|
||||
'GOMODCACHE',
|
||||
'GOOS',
|
||||
'GOARCH',
|
||||
'GOTOOLDIR'
|
||||
]);
|
||||
for (const name of GO_ENV_OUTPUTS) {
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(name, goEnv[name] ?? '');
|
||||
}
|
||||
});
|
||||
|
||||
it('falls back to $GOPATH/bin when GOBIN is empty', () => {
|
||||
main.setGoEnvOutputs(goEnv);
|
||||
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(
|
||||
'go-bin-path',
|
||||
'/Users/testuser/go/bin'
|
||||
);
|
||||
});
|
||||
|
||||
it('prefers GOBIN over $GOPATH/bin when it is set', () => {
|
||||
main.setGoEnvOutputs({...goEnv, GOBIN: '/Users/testuser/bin'});
|
||||
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(
|
||||
'go-bin-path',
|
||||
'/Users/testuser/bin'
|
||||
);
|
||||
});
|
||||
|
||||
it('uses the first entry of a multi-entry GOPATH', () => {
|
||||
const GOPATH = ['/Users/testuser/go', '/Users/testuser/other'].join(
|
||||
path.delimiter
|
||||
);
|
||||
|
||||
main.setGoEnvOutputs({...goEnv, GOPATH});
|
||||
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('GOPATH', GOPATH);
|
||||
expect(setOutputSpy).toHaveBeenCalledWith(
|
||||
'go-bin-path',
|
||||
'/Users/testuser/go/bin'
|
||||
);
|
||||
});
|
||||
|
||||
it('leaves variables missing from go env -json empty', () => {
|
||||
main.setGoEnvOutputs({});
|
||||
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('GOPATH', '');
|
||||
expect(setOutputSpy).toHaveBeenCalledWith('go-bin-path', '');
|
||||
});
|
||||
});
|
||||
|
||||
interface Annotation {
|
||||
file: string;
|
||||
line: number;
|
||||
@@ -811,10 +1002,9 @@ describe('setup-go', () => {
|
||||
getManifestSpy.mockImplementation(() => {
|
||||
throw new Error('Unable to download manifest');
|
||||
});
|
||||
httpmGetJsonSpy.mockRejectedValue(
|
||||
(httpmGetJsonSpy as jest.Mock<any>).mockRejectedValue(
|
||||
new Error('Unable to download manifest from raw URL')
|
||||
);
|
||||
getAllVersionsSpy.mockImplementationOnce(() => undefined);
|
||||
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
const toolPath = path.normalize('/cache/go/1.13.7/x64');
|
||||
@@ -1105,4 +1295,456 @@ use .
|
||||
expect(vars).toStrictEqual({GOTOOLCHAIN: 'local'});
|
||||
expect(process.env).toHaveProperty('GOTOOLCHAIN', 'local');
|
||||
});
|
||||
|
||||
describe('go-download-base-url', () => {
|
||||
it('downloads a version from custom base URL using version listing', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
const versionSpec = '1.13.1';
|
||||
const customBaseUrl = 'https://example.com/golang';
|
||||
|
||||
inputs['go-version'] = versionSpec;
|
||||
inputs['go-download-base-url'] = customBaseUrl;
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
const toolPath = path.normalize('/cache/go/1.13.1/x64');
|
||||
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
await main.run();
|
||||
|
||||
const expPath = path.join(toolPath, 'bin');
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Using custom Go download base URL: ${customBaseUrl}`
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith('Install from custom download URL');
|
||||
// Version listing should use custom base URL, not go.dev
|
||||
expect(getSpy).toHaveBeenCalledWith(
|
||||
`${customBaseUrl}/?mode=json&include=all`
|
||||
);
|
||||
expect(dlSpy).toHaveBeenCalled();
|
||||
expect(extractTarSpy).toHaveBeenCalled();
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
|
||||
});
|
||||
|
||||
it('skips version listing for known direct-download URL (aka.ms)', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
const versionSpec = '1.25.0';
|
||||
const customBaseUrl = 'https://aka.ms/golang/release/latest';
|
||||
|
||||
inputs['go-version'] = versionSpec;
|
||||
inputs['go-download-base-url'] = customBaseUrl;
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
const toolPath = path.normalize('/cache/go/1.25.0/x64');
|
||||
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
await main.run();
|
||||
|
||||
const expPath = path.join(toolPath, 'bin');
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'Skipping version listing for known direct-download URL. Constructing download URL directly.'
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Constructed direct download URL: ${customBaseUrl}/go1.25.0.linux-amd64.tar.gz`
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith('Install from custom download URL');
|
||||
expect(getSpy).not.toHaveBeenCalled();
|
||||
expect(dlSpy).toHaveBeenCalled();
|
||||
expect(cnSpy).toHaveBeenCalledWith(`::add-path::${expPath}${osm.EOL}`);
|
||||
});
|
||||
|
||||
it('constructs correct direct download URL for windows (aka.ms)', async () => {
|
||||
os.platform = 'win32';
|
||||
os.arch = 'x64';
|
||||
|
||||
const versionSpec = '1.25.0';
|
||||
const customBaseUrl = 'https://aka.ms/golang/release/latest';
|
||||
|
||||
inputs['go-version'] = versionSpec;
|
||||
inputs['go-download-base-url'] = customBaseUrl;
|
||||
process.env['RUNNER_TEMP'] = 'C:\\temp\\';
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
dlSpy.mockImplementation(async () => 'C:\\temp\\some\\path');
|
||||
extractZipSpy.mockImplementation(() => 'C:\\temp\\some\\other\\path');
|
||||
const toolPath = path.normalize('C:\\cache\\go\\1.25.0\\x64');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(getSpy).not.toHaveBeenCalled();
|
||||
expect(dlSpy).toHaveBeenCalledWith(
|
||||
`${customBaseUrl}/go1.25.0.windows-amd64.zip`,
|
||||
'C:\\temp\\go1.25.0.windows-amd64.zip',
|
||||
undefined
|
||||
);
|
||||
});
|
||||
|
||||
it('skips manifest and downloads directly from custom URL', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
const versionSpec = '1.12.16';
|
||||
const customBaseUrl = 'https://example.com/golang';
|
||||
|
||||
inputs['go-version'] = versionSpec;
|
||||
inputs['go-download-base-url'] = customBaseUrl;
|
||||
inputs['token'] = 'faketoken';
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
const toolPath = path.normalize('/cache/go/1.12.16/x64');
|
||||
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
await main.run();
|
||||
|
||||
// Should not try to use the manifest at all
|
||||
expect(logSpy).not.toHaveBeenCalledWith(
|
||||
expect.stringContaining('Not found in manifest')
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith('Install from custom download URL');
|
||||
});
|
||||
|
||||
it('strips trailing slashes from custom base URL', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
const versionSpec = '1.13.1';
|
||||
const customBaseUrl = 'https://example.com/golang/';
|
||||
|
||||
inputs['go-version'] = versionSpec;
|
||||
inputs['go-download-base-url'] = customBaseUrl;
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
const toolPath = path.normalize('/cache/go/1.13.1/x64');
|
||||
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Acquiring go1.13.1 from https://example.com/golang/go1.13.1.linux-amd64.tar.gz`
|
||||
);
|
||||
});
|
||||
|
||||
it('reads custom base URL from environment variable', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
const versionSpec = '1.13.1';
|
||||
const customBaseUrl = 'https://example.com/golang';
|
||||
|
||||
inputs['go-version'] = versionSpec;
|
||||
process.env['GO_DOWNLOAD_BASE_URL'] = customBaseUrl;
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
const toolPath = path.normalize('/cache/go/1.13.1/x64');
|
||||
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Using custom Go download base URL: ${customBaseUrl}`
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith('Install from custom download URL');
|
||||
});
|
||||
|
||||
it('input takes precedence over environment variable', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
const versionSpec = '1.13.1';
|
||||
const inputUrl = 'https://input.example.com/golang';
|
||||
const envUrl = 'https://env.example.com/golang';
|
||||
|
||||
inputs['go-version'] = versionSpec;
|
||||
inputs['go-download-base-url'] = inputUrl;
|
||||
process.env['GO_DOWNLOAD_BASE_URL'] = envUrl;
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
const toolPath = path.normalize('/cache/go/1.13.1/x64');
|
||||
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Using custom Go download base URL: ${inputUrl}`
|
||||
);
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
`Acquiring go1.13.1 from ${inputUrl}/go1.13.1.linux-amd64.tar.gz`
|
||||
);
|
||||
});
|
||||
|
||||
it('errors when stable alias is used with custom URL', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
inputs['go-version'] = 'stable';
|
||||
inputs['go-download-base-url'] = 'https://example.com/golang';
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
`::error::Version aliases 'stable' are not supported with a custom download base URL. Please specify an exact Go version.${osm.EOL}`
|
||||
);
|
||||
});
|
||||
|
||||
it('logs info when check-latest is used with custom URL', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
const versionSpec = '1.13.1';
|
||||
const customBaseUrl = 'https://example.com/golang';
|
||||
|
||||
inputs['go-version'] = versionSpec;
|
||||
inputs['go-download-base-url'] = customBaseUrl;
|
||||
inputs['check-latest'] = true;
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
const toolPath = path.normalize('/cache/go/1.13.1/x64');
|
||||
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
'check-latest is not supported with a custom download base URL. Using the provided version spec directly.'
|
||||
);
|
||||
});
|
||||
|
||||
it('constructs direct download info correctly', () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
const info = im.getInfoFromDirectDownload(
|
||||
'1.25.0',
|
||||
'x64',
|
||||
'https://aka.ms/golang/release/latest'
|
||||
);
|
||||
|
||||
expect(info.type).toBe('dist');
|
||||
expect(info.downloadUrl).toBe(
|
||||
'https://aka.ms/golang/release/latest/go1.25.0.linux-amd64.tar.gz'
|
||||
);
|
||||
expect(info.fileName).toBe('go1.25.0.linux-amd64.tar.gz');
|
||||
expect(info.resolvedVersion).toBe('1.25.0');
|
||||
});
|
||||
|
||||
it('constructs direct download info for windows', () => {
|
||||
os.platform = 'win32';
|
||||
os.arch = 'x64';
|
||||
|
||||
const info = im.getInfoFromDirectDownload(
|
||||
'1.25.0',
|
||||
'x64',
|
||||
'https://aka.ms/golang/release/latest'
|
||||
);
|
||||
|
||||
expect(info.type).toBe('dist');
|
||||
expect(info.downloadUrl).toBe(
|
||||
'https://aka.ms/golang/release/latest/go1.25.0.windows-amd64.zip'
|
||||
);
|
||||
expect(info.fileName).toBe('go1.25.0.windows-amd64.zip');
|
||||
});
|
||||
|
||||
it('constructs direct download info for arm64', () => {
|
||||
os.platform = 'darwin';
|
||||
os.arch = 'arm64';
|
||||
|
||||
const info = im.getInfoFromDirectDownload(
|
||||
'1.25.0',
|
||||
'arm64',
|
||||
'https://aka.ms/golang/release/latest'
|
||||
);
|
||||
|
||||
expect(info.type).toBe('dist');
|
||||
expect(info.downloadUrl).toBe(
|
||||
'https://aka.ms/golang/release/latest/go1.25.0.darwin-arm64.tar.gz'
|
||||
);
|
||||
expect(info.fileName).toBe('go1.25.0.darwin-arm64.tar.gz');
|
||||
});
|
||||
|
||||
it('caches under actual installed version when it differs from input spec', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
const versionSpec = '1.20';
|
||||
const customBaseUrl = 'https://aka.ms/golang/release/latest';
|
||||
|
||||
inputs['go-version'] = versionSpec;
|
||||
inputs['go-download-base-url'] = customBaseUrl;
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
|
||||
// Mock the installed Go binary reporting a different patch version
|
||||
execFileSpy.mockImplementation(() => 'go version go1.20.14 linux/amd64');
|
||||
|
||||
const expectedToolName = im.customToolCacheName(customBaseUrl);
|
||||
const toolPath = path.normalize(`/cache/${expectedToolName}/1.20.14/x64`);
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
"Requested version '1.20' resolved to installed version '1.20.14'"
|
||||
);
|
||||
// Cache key should use actual version, not the input spec
|
||||
expect(cacheSpy).toHaveBeenCalledWith(
|
||||
expect.any(String),
|
||||
expectedToolName,
|
||||
'1.20.14',
|
||||
'x64'
|
||||
);
|
||||
});
|
||||
|
||||
it('shows clear error with platform/arch and URL on 404', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'arm64';
|
||||
|
||||
const versionSpec = '1.25.0';
|
||||
const customBaseUrl = 'https://example.com/golang';
|
||||
|
||||
inputs['go-version'] = versionSpec;
|
||||
inputs['go-download-base-url'] = customBaseUrl;
|
||||
|
||||
getSpy.mockImplementationOnce(() => {
|
||||
throw new Error('Not a JSON endpoint');
|
||||
});
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
const httpError = new tc.HTTPError(404);
|
||||
dlSpy.mockImplementation(() => {
|
||||
throw httpError;
|
||||
});
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'The requested Go version 1.25.0 is not available for platform linux/arm64'
|
||||
)
|
||||
);
|
||||
expect(cnSpy).toHaveBeenCalledWith(expect.stringContaining('HTTP 404'));
|
||||
});
|
||||
|
||||
it('shows clear error with platform/arch and URL on download failure', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
const versionSpec = '1.25.0';
|
||||
const customBaseUrl = 'https://example.com/golang';
|
||||
|
||||
inputs['go-version'] = versionSpec;
|
||||
inputs['go-download-base-url'] = customBaseUrl;
|
||||
|
||||
getSpy.mockImplementationOnce(() => {
|
||||
throw new Error('Not a JSON endpoint');
|
||||
});
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
dlSpy.mockImplementation(() => {
|
||||
throw new Error('connection refused');
|
||||
});
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
'Failed to download Go 1.25.0 for platform linux/x64'
|
||||
)
|
||||
);
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(customBaseUrl)
|
||||
);
|
||||
});
|
||||
|
||||
it.each(['^1.25.0', '~1.25', '>=1.25.0', '<1.26.0', '1.25.x', '1.x'])(
|
||||
'errors on version range "%s" when version listing is unavailable',
|
||||
async versionSpec => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
inputs['go-version'] = versionSpec;
|
||||
inputs['go-download-base-url'] = 'https://example.com/golang';
|
||||
|
||||
// Simulate version listing not available
|
||||
getSpy.mockImplementationOnce(() => {
|
||||
throw new Error('Not a JSON endpoint');
|
||||
});
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(cnSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
`Version range '${versionSpec}' is not supported with a custom download base URL`
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
it('rejects version range in getInfoFromDirectDownload', () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
expect(() =>
|
||||
im.getInfoFromDirectDownload(
|
||||
'^1.25.0',
|
||||
'x64',
|
||||
'https://example.com/golang'
|
||||
)
|
||||
).toThrow(
|
||||
"Version range '^1.25.0' is not supported with a custom download base URL"
|
||||
);
|
||||
});
|
||||
|
||||
it('passes token as auth header for custom URL downloads', async () => {
|
||||
os.platform = 'linux';
|
||||
os.arch = 'x64';
|
||||
|
||||
const versionSpec = '1.25.0';
|
||||
const customBaseUrl = 'https://private-mirror.example.com/golang';
|
||||
|
||||
inputs['go-version'] = versionSpec;
|
||||
inputs['go-download-base-url'] = customBaseUrl;
|
||||
inputs['token'] = 'ghp_testtoken123';
|
||||
|
||||
getSpy.mockImplementationOnce(() => {
|
||||
throw new Error('Not a JSON endpoint');
|
||||
});
|
||||
|
||||
findSpy.mockImplementation(() => '');
|
||||
dlSpy.mockImplementation(async () => '/some/temp/path');
|
||||
extractTarSpy.mockImplementation(async () => '/some/other/temp/path');
|
||||
const expectedToolName = im.customToolCacheName(customBaseUrl);
|
||||
const toolPath = path.normalize(`/cache/${expectedToolName}/1.25.0/x64`);
|
||||
cacheSpy.mockImplementation(async () => toolPath);
|
||||
|
||||
await main.run();
|
||||
|
||||
expect(dlSpy).toHaveBeenCalledWith(
|
||||
`${customBaseUrl}/go1.25.0.linux-amd64.tar.gz`,
|
||||
undefined,
|
||||
'token ghp_testtoken123'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import {isSelfHosted} from '../src/utils';
|
||||
import {describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
import {isSelfHosted} from '../src/utils.js';
|
||||
|
||||
describe('utils', () => {
|
||||
describe('isSelfHosted', () => {
|
||||
|
||||
@@ -1,38 +1,57 @@
|
||||
import fs from 'fs';
|
||||
import * as io from '@actions/io';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import {jest, describe, it, expect, beforeEach, afterEach} from '@jest/globals';
|
||||
import path from 'path';
|
||||
|
||||
const statSyncMock = jest.fn(() => ({
|
||||
isDirectory: () => true,
|
||||
isFile: () => true
|
||||
}));
|
||||
const readdirSyncMock = jest.fn(() => [] as any);
|
||||
const writeFileSyncMock = jest.fn();
|
||||
const mkdirMock = jest.fn();
|
||||
const symlinkSyncMock = jest.fn();
|
||||
|
||||
jest.unstable_mockModule('@actions/io', () => ({
|
||||
which: jest.fn(),
|
||||
mkdirP: jest.fn(() => Promise.resolve()),
|
||||
rmRF: jest.fn(() => Promise.resolve()),
|
||||
mv: jest.fn(() => Promise.resolve()),
|
||||
cp: jest.fn(() => Promise.resolve())
|
||||
}));
|
||||
|
||||
const realFs = (await import('fs')).default;
|
||||
const fsExports = {
|
||||
...realFs,
|
||||
statSync: statSyncMock,
|
||||
readdirSync: readdirSyncMock,
|
||||
writeFileSync: writeFileSyncMock,
|
||||
mkdir: mkdirMock,
|
||||
symlinkSync: symlinkSyncMock
|
||||
};
|
||||
jest.unstable_mockModule('fs', () => ({...fsExports, default: fsExports}));
|
||||
|
||||
const io = await import('@actions/io');
|
||||
const tc = await import('@actions/tool-cache');
|
||||
|
||||
describe('Windows performance workaround', () => {
|
||||
let mkdirSpy: jest.SpyInstance;
|
||||
let symlinkSpy: jest.SpyInstance;
|
||||
let statSpy: jest.SpyInstance;
|
||||
let readdirSpy: jest.SpyInstance;
|
||||
let writeFileSpy: jest.SpyInstance;
|
||||
let rmRFSpy: jest.SpyInstance;
|
||||
let mkdirPSpy: jest.SpyInstance;
|
||||
let cpSpy: jest.SpyInstance;
|
||||
let rmRFSpy: jest.Mock;
|
||||
let mkdirPSpy: jest.Mock;
|
||||
let cpSpy: jest.Mock;
|
||||
|
||||
let runnerToolCache: string | undefined;
|
||||
beforeEach(() => {
|
||||
mkdirSpy = jest.spyOn(fs, 'mkdir');
|
||||
symlinkSpy = jest.spyOn(fs, 'symlinkSync');
|
||||
statSpy = jest.spyOn(fs, 'statSync');
|
||||
readdirSpy = jest.spyOn(fs, 'readdirSync');
|
||||
writeFileSpy = jest.spyOn(fs, 'writeFileSync');
|
||||
rmRFSpy = jest.spyOn(io, 'rmRF');
|
||||
mkdirPSpy = jest.spyOn(io, 'mkdirP');
|
||||
cpSpy = jest.spyOn(io, 'cp');
|
||||
rmRFSpy = io.rmRF as jest.Mock;
|
||||
mkdirPSpy = io.mkdirP as jest.Mock;
|
||||
cpSpy = io.cp as jest.Mock;
|
||||
|
||||
// default implementations
|
||||
// @ts-ignore - not implement unused methods
|
||||
statSpy.mockImplementation(() => ({
|
||||
isDirectory: () => true
|
||||
statSyncMock.mockImplementation(() => ({
|
||||
isDirectory: () => true,
|
||||
isFile: () => true
|
||||
}));
|
||||
readdirSpy.mockImplementation(() => []);
|
||||
writeFileSpy.mockImplementation(() => {});
|
||||
mkdirSpy.mockImplementation(() => {});
|
||||
symlinkSpy.mockImplementation(() => {});
|
||||
readdirSyncMock.mockImplementation(() => []);
|
||||
writeFileSyncMock.mockImplementation(() => {});
|
||||
mkdirMock.mockImplementation(() => {});
|
||||
symlinkSyncMock.mockImplementation(() => {});
|
||||
rmRFSpy.mockImplementation(() => Promise.resolve());
|
||||
mkdirPSpy.mockImplementation(() => Promise.resolve());
|
||||
cpSpy.mockImplementation(() => Promise.resolve());
|
||||
|
||||
+23
-3
@@ -16,16 +16,36 @@ inputs:
|
||||
description: Used to specify whether caching is needed. Set to true, if you'd like to enable caching.
|
||||
default: true
|
||||
cache-dependency-path:
|
||||
description: 'Used to specify the path to a dependency file - go.sum'
|
||||
description: 'Used to specify the path to a dependency file (e.g., go.mod, go.sum)'
|
||||
architecture:
|
||||
description: 'Target architecture for Go to use. Examples: x86, x64. Will use system architecture by default.'
|
||||
go-download-base-url:
|
||||
description: 'Custom base URL for downloading Go distributions. Use this to download Go from a mirror or custom source. Defaults to "https://go.dev/dl". Can also be set via the GO_DOWNLOAD_BASE_URL environment variable. The input takes precedence over the environment variable.'
|
||||
outputs:
|
||||
go-version:
|
||||
description: 'The installed Go version. Useful when given a version range as input.'
|
||||
cache-hit:
|
||||
description: 'A boolean value to indicate if a cache was hit'
|
||||
description: 'A boolean value indicating whether both module and build cache entries were restored on their primary keys'
|
||||
GOPATH:
|
||||
description: 'The value of `go env GOPATH`.'
|
||||
GOBIN:
|
||||
description: 'The value of `go env GOBIN`.'
|
||||
go-bin-path:
|
||||
description: 'The base Go binary directory: `GOBIN`, otherwise `bin` under the first `GOPATH` entry.'
|
||||
GOROOT:
|
||||
description: 'The value of `go env GOROOT`.'
|
||||
GOCACHE:
|
||||
description: 'The value of `go env GOCACHE`, the build cache directory.'
|
||||
GOMODCACHE:
|
||||
description: 'The value of `go env GOMODCACHE`, the module cache directory.'
|
||||
GOOS:
|
||||
description: 'The value of `go env GOOS`, the target OS in Go notation (linux, darwin, windows).'
|
||||
GOARCH:
|
||||
description: 'The value of `go env GOARCH`, the target architecture in Go notation (amd64, arm64).'
|
||||
GOTOOLDIR:
|
||||
description: 'The value of `go env GOTOOLDIR`, the directory holding the toolchain binaries.'
|
||||
runs:
|
||||
using: 'node20'
|
||||
using: 'node24'
|
||||
main: 'dist/setup/index.js'
|
||||
post: 'dist/cache-save/index.js'
|
||||
post-if: success()
|
||||
Vendored
+86673
-80280
File diff suppressed because it is too large.
Load diff
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
Vendored
+87473
-86713
File diff suppressed because it is too large.
Load diff
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
Loaded 100 of 117 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user