mirror of
https://github.com/actions/cache.git
synced 2025-11-09 11:20:56 +07:00
Compare commits
1 Commits
phantsure/
...
bishal-pdM
| Author | SHA1 | Date | |
|---|---|---|---|
| be52f75d6f |
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Node.js & TypeScript",
|
|
||||||
"image": "mcr.microsoft.com/devcontainers/typescript-node:16-bullseye",
|
|
||||||
// Features to add to the dev container. More info: https://containers.dev/implementors/features.
|
|
||||||
// "features": {},
|
|
||||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
|
||||||
// "forwardPorts": [],
|
|
||||||
// Use 'postCreateCommand' to run commands after the container is created.
|
|
||||||
"postCreateCommand": "npm install"
|
|
||||||
// Configure tool-specific properties.
|
|
||||||
// "customizations": {},
|
|
||||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
|
||||||
// "remoteUser": "root"
|
|
||||||
}
|
|
||||||
21
.github/auto_assign.yml
vendored
Normal file
21
.github/auto_assign.yml
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Set to true to add reviewers to pull requests
|
||||||
|
addReviewers: true
|
||||||
|
|
||||||
|
# Set to true to add assignees to pull requests
|
||||||
|
addAssignees: false
|
||||||
|
|
||||||
|
# A list of reviewers to be added to pull requests (GitHub user name)
|
||||||
|
reviewers:
|
||||||
|
- anuragc617
|
||||||
|
- pallavx
|
||||||
|
- pdotl
|
||||||
|
- phantsure
|
||||||
|
- kotewar
|
||||||
|
- aparna-ravindra
|
||||||
|
- tiwarishub
|
||||||
|
- vsvipul
|
||||||
|
- bishal-pdmsft
|
||||||
|
|
||||||
|
# A number of reviewers added to the pull request
|
||||||
|
# Set 0 to add all the reviewers (default: 0)
|
||||||
|
numberOfReviewers: 1
|
||||||
20
.github/workflows/add-reviewer-pr.yml
vendored
20
.github/workflows/add-reviewer-pr.yml
vendored
@ -1,20 +0,0 @@
|
|||||||
name: Add Reviewer PR
|
|
||||||
on:
|
|
||||||
pull_request_target:
|
|
||||||
types: [opened]
|
|
||||||
jobs:
|
|
||||||
run-action:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Get current oncall
|
|
||||||
id: oncall
|
|
||||||
run: |
|
|
||||||
echo "CURRENT=$(curl --request GET 'https://api.pagerduty.com/oncalls?include[]=users&schedule_ids[]=P5VG2BX&earliest=true' --header 'Authorization: Token token=${{ secrets.PAGERDUTY_TOKEN }}' --header 'Accept: application/vnd.pagerduty+json;version=2' --header 'Content-Type: application/json' | jq -r '.oncalls[].user.name')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Request Review
|
|
||||||
run: |
|
|
||||||
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN}}" https://api.github.com/repos/${{github.repository}}/pulls/${{ github.event.pull_request.number}}/requested_reviewers -d '{"reviewers":["${{steps.oncall.outputs.CURRENT}}"]}'
|
|
||||||
|
|
||||||
- name: Add Assignee
|
|
||||||
run: |
|
|
||||||
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN}}" https://api.github.com/repos/${{github.repository}}/issues/${{ github.event.pull_request.number}}/assignees -d '{"assignees":["${{steps.oncall.outputs.CURRENT}}"]}'
|
|
||||||
16
.github/workflows/assign-issue.yml
vendored
16
.github/workflows/assign-issue.yml
vendored
@ -1,16 +0,0 @@
|
|||||||
name: Assign issue
|
|
||||||
on:
|
|
||||||
issues:
|
|
||||||
types: [opened]
|
|
||||||
jobs:
|
|
||||||
run-action:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Get current oncall
|
|
||||||
id: oncall
|
|
||||||
run: |
|
|
||||||
echo "CURRENT=$(curl --request GET 'https://api.pagerduty.com/oncalls?include[]=users&schedule_ids[]=P5VG2BX&earliest=true' --header 'Authorization: Token token=${{ secrets.PAGERDUTY_TOKEN }}' --header 'Accept: application/vnd.pagerduty+json;version=2' --header 'Content-Type: application/json' | jq -r '.oncalls[].user.name')" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: add_assignees
|
|
||||||
run: |
|
|
||||||
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN}}" https://api.github.com/repos/${{github.repository}}/issues/${{ github.event.issue.number}}/assignees -d '{"assignees":["${{steps.oncall.outputs.CURRENT}}"]}'
|
|
||||||
15
.github/workflows/auto-assign-issues.yml
vendored
Normal file
15
.github/workflows/auto-assign-issues.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
name: Issue assignment
|
||||||
|
|
||||||
|
on:
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
auto-assign:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 'Auto-assign issue'
|
||||||
|
uses: pozil/auto-assign-issue@v1.4.0
|
||||||
|
with:
|
||||||
|
assignees: anuragc617,pallavx,pdotl,phantsure,kotewar,tiwarishub,aparna-ravindra,vsvipul,bishal-pdmsft
|
||||||
|
numOfAssignee: 1
|
||||||
10
.github/workflows/auto-assign.yml
vendored
Normal file
10
.github/workflows/auto-assign.yml
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
name: 'Auto Assign'
|
||||||
|
on:
|
||||||
|
pull_request_target:
|
||||||
|
types: [opened, ready_for_review]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
add-reviews:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: kentaro-m/auto-assign-action@v1.2.1
|
||||||
1
.github/workflows/check-dist.yml
vendored
1
.github/workflows/check-dist.yml
vendored
@ -27,7 +27,6 @@ jobs:
|
|||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16.x
|
node-version: 16.x
|
||||||
cache: npm
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
- name: Rebuild the dist/ directory
|
- name: Rebuild the dist/ directory
|
||||||
|
|||||||
12
.github/workflows/workflow.yml
vendored
12
.github/workflows/workflow.yml
vendored
@ -25,7 +25,17 @@ jobs:
|
|||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16.x
|
node-version: 16.x
|
||||||
cache: npm
|
- name: Determine npm cache directory
|
||||||
|
id: npm-cache
|
||||||
|
run: |
|
||||||
|
echo "::set-output name=dir::$(npm config get cache)"
|
||||||
|
- name: Restore npm cache
|
||||||
|
uses: actions/cache@v3
|
||||||
|
with:
|
||||||
|
path: ${{ steps.npm-cache.outputs.dir }}
|
||||||
|
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-node-
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- name: Prettier Format Check
|
- name: Prettier Format Check
|
||||||
run: npm run format-check
|
run: npm run format-check
|
||||||
|
|||||||
2
.licenses/npm/@actions/cache.dep.yml
generated
2
.licenses/npm/@actions/cache.dep.yml
generated
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: "@actions/cache"
|
name: "@actions/cache"
|
||||||
version: 3.1.0
|
version: 3.0.5
|
||||||
type: npm
|
type: npm
|
||||||
summary:
|
summary:
|
||||||
homepage:
|
homepage:
|
||||||
|
|||||||
33
README.md
33
README.md
@ -2,19 +2,12 @@
|
|||||||
|
|
||||||
This action allows caching dependencies and build outputs to improve workflow execution time.
|
This action allows caching dependencies and build outputs to improve workflow execution time.
|
||||||
|
|
||||||
In addition to this `cache` action, other two actions are also available
|
|
||||||
|
|
||||||
[Restore action](./restore/README.md)
|
|
||||||
|
|
||||||
[Save action](./save/README.md)
|
|
||||||
|
|
||||||
[](https://github.com/actions/cache/actions/workflows/workflow.yml)
|
[](https://github.com/actions/cache/actions/workflows/workflow.yml)
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
See ["Caching dependencies to speed up workflows"](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows).
|
See ["Caching dependencies to speed up workflows"](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows).
|
||||||
|
|
||||||
|
|
||||||
## What's New
|
## What's New
|
||||||
### v3
|
### v3
|
||||||
* Added support for caching from GHES 3.5.
|
* Added support for caching from GHES 3.5.
|
||||||
@ -27,8 +20,6 @@ See ["Caching dependencies to speed up workflows"](https://docs.github.com/en/ac
|
|||||||
* Fixed the download stuck problem by introducing a timeout of 1 hour for cache downloads.
|
* Fixed the download stuck problem by introducing a timeout of 1 hour for cache downloads.
|
||||||
* Fix zstd not working for windows on gnu tar in issues.
|
* Fix zstd not working for windows on gnu tar in issues.
|
||||||
* Allowing users to provide a custom timeout as input for aborting download of a cache segment using an environment variable `SEGMENT_DOWNLOAD_TIMEOUT_MINS`. Default is 60 minutes.
|
* Allowing users to provide a custom timeout as input for aborting download of a cache segment using an environment variable `SEGMENT_DOWNLOAD_TIMEOUT_MINS`. Default is 60 minutes.
|
||||||
* Two new actions available for granular control over caches - [restore](restore/action.yml) and [save](save/action.yml)
|
|
||||||
* Add support for cross os caching. For example, a cache saved on windows can be restored on ubuntu and vice versa.
|
|
||||||
|
|
||||||
Refer [here](https://github.com/actions/cache/blob/v2/README.md) for previous versions
|
Refer [here](https://github.com/actions/cache/blob/v2/README.md) for previous versions
|
||||||
|
|
||||||
@ -46,7 +37,7 @@ If you are using this inside a container, a POSIX-compliant `tar` needs to be in
|
|||||||
* `restore-keys` - An ordered list of prefix-matched keys to use for restoring stale cache if no cache hit occurred for key.
|
* `restore-keys` - An ordered list of prefix-matched keys to use for restoring stale cache if no cache hit occurred for key.
|
||||||
|
|
||||||
#### Environment Variables
|
#### Environment Variables
|
||||||
* `SEGMENT_DOWNLOAD_TIMEOUT_MINS` - Segment download timeout (in minutes, default `60`) to abort download of the segment if not completed in the defined number of minutes. [Read more](https://github.com/actions/cache/blob/main/workarounds.md#cache-segment-restore-timeout)
|
* `SEGMENT_DOWNLOAD_TIMEOUT_MINS` - Segment download timeout (in minutes, default `60`) to abort download of the segment if not completed in the defined number of minutes. [Read more](#cache-segment-restore-timeout)
|
||||||
|
|
||||||
### Outputs
|
### Outputs
|
||||||
|
|
||||||
@ -57,7 +48,7 @@ If you are using this inside a container, a POSIX-compliant `tar` needs to be in
|
|||||||
See [Skipping steps based on cache-hit](#skipping-steps-based-on-cache-hit) for info on using this output
|
See [Skipping steps based on cache-hit](#skipping-steps-based-on-cache-hit) for info on using this output
|
||||||
|
|
||||||
### Cache scopes
|
### Cache scopes
|
||||||
The cache is scoped to the key, [version](#cache-version) and branch. The default branch cache is available to other branches.
|
The cache is scoped to the key and branch. The default branch cache is available to other branches.
|
||||||
|
|
||||||
See [Matching a cache key](https://help.github.com/en/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows#matching-a-cache-key) for more info.
|
See [Matching a cache key](https://help.github.com/en/actions/configuring-and-managing-workflows/caching-dependencies-to-speed-up-workflows#matching-a-cache-key) for more info.
|
||||||
|
|
||||||
@ -127,7 +118,7 @@ See [Examples](examples.md) for a list of `actions/cache` implementations for us
|
|||||||
|
|
||||||
A cache key can include any of the contexts, functions, literals, and operators supported by GitHub Actions.
|
A cache key can include any of the contexts, functions, literals, and operators supported by GitHub Actions.
|
||||||
|
|
||||||
For example, using the [`hashFiles`](https://docs.github.com/en/actions/learn-github-actions/expressions#hashfiles) function allows you to create a new cache when dependencies change.
|
For example, using the [`hashFiles`](https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#hashfiles) function allows you to create a new cache when dependencies change.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
@ -184,13 +175,9 @@ steps:
|
|||||||
|
|
||||||
|
|
||||||
## Cache Version
|
## Cache Version
|
||||||
Cache version is a hash [generated](https://github.com/actions/toolkit/blob/500d0b42fee2552ae9eeb5933091fe2fbf14e72d/packages/cache/src/internal/cacheHttpClient.ts#L73-L90) for a combination of compression tool used (Gzip, Zstd, etc. based on the runner OS) and the `path` of directories being cached. If two caches have different versions, they are identified as unique caches while matching. This for example, means that a cache created on `windows-latest` runner can't be restored on `ubuntu-latest` as cache `Version`s are different.
|
Cache version is unique for a combination of compression tool used for compression of cache (Gzip, Zstd, etc based on runner OS) and the path of directories being cached. If two caches have different versions, they are identified as unique cache entries. This also means that a cache created on `windows-latest` runner can't be restored on `ubuntu-latest` as cache `Version`s are different.
|
||||||
|
|
||||||
> Pro tip: [List caches](https://docs.github.com/en/rest/actions/cache#list-github-actions-caches-for-a-repository) API can be used to get the version of a cache. This can be helpful to troubleshoot cache miss due to version.
|
Example: Below example will create 3 unique caches with same keys. Ubuntu and windows runners will use different compression technique and hence create two different caches. And `build-linux` will create two different caches as the `paths` are different.
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>Example</summary>
|
|
||||||
The workflow will create 3 unique caches with same keys. Ubuntu and windows runners will use different compression technique and hence create two different caches. And `build-linux` will create two different caches as the `paths` are different.
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
jobs:
|
jobs:
|
||||||
@ -237,16 +224,14 @@ jobs:
|
|||||||
if: steps.cache-primes.outputs.cache-hit != 'true'
|
if: steps.cache-primes.outputs.cache-hit != 'true'
|
||||||
run: ./generate-primes -d prime-numbers
|
run: ./generate-primes -d prime-numbers
|
||||||
```
|
```
|
||||||
</details>
|
|
||||||
|
|
||||||
## Known practices and workarounds
|
## Known practices and workarounds
|
||||||
Following are some of the known practices/workarounds which community has used to fulfill specific requirements. You may choose to use them if suits your use case. Note these are not necessarily the only or the recommended solution.
|
Following are some of the known practices/workarounds which community has used to fulfill specific requirements. You may choose to use them if suits your use case. Note these are not necessarily the only or the recommended solution.
|
||||||
|
|
||||||
- [Cache segment restore timeout](./tips-and-workarounds.md#cache-segment-restore-timeout)
|
- [Cache segment restore timeout](./workarounds.md#cache-segment-restore-timeout)
|
||||||
- [Update a cache](./tips-and-workarounds.md#update-a-cache)
|
- [Update a cache](./workarounds.md#update-a-cache)
|
||||||
- [Use cache across feature branches](./tips-and-workarounds.md#use-cache-across-feature-branches)
|
- [Use cache across feature branches](./workarounds.md#use-cache-across-feature-branches)
|
||||||
- [Improving cache restore performance on Windows/Using cross-os caching](./tips-and-workarounds.md#improving-cache-restore-performance-on-windows-using-cross-os-caching)
|
- [Improving cache restore performance on Windows/Using cross-os caching](./workarounds.md#improving-cache-restore-performance-on-windows-using-cross-os-caching)
|
||||||
- [Force deletion of caches overriding default cache eviction policy](./tips-and-workarounds.md#force-deletion-of-caches-overriding-default-cache-eviction-policy)
|
|
||||||
|
|
||||||
#### Windows environment variables
|
#### Windows environment variables
|
||||||
Please note that Windows environment variables (like `%LocalAppData%`) will NOT be expanded by this action. Instead, prefer using `~` in your paths which will expand to HOME directory. For example, instead of `%LocalAppData%`, use `~\AppData\Local`. For a list of supported default environment variables, see [this](https://docs.github.com/en/actions/learn-github-actions/environment-variables) page.
|
Please note that Windows environment variables (like `%LocalAppData%`) will NOT be expanded by this action. Instead, prefer using `~` in your paths which will expand to HOME directory. For example, instead of `%LocalAppData%`, use `~\AppData\Local`. For a list of supported default environment variables, see [this](https://docs.github.com/en/actions/learn-github-actions/environment-variables) page.
|
||||||
|
|||||||
20
RELEASES.md
20
RELEASES.md
@ -40,23 +40,3 @@
|
|||||||
### 3.0.11
|
### 3.0.11
|
||||||
- Update toolkit version to 3.0.5 to include `@actions/core@^1.10.0`
|
- Update toolkit version to 3.0.5 to include `@actions/core@^1.10.0`
|
||||||
- Update `@actions/cache` to use updated `saveState` and `setOutput` functions from `@actions/core@^1.10.0`
|
- Update `@actions/cache` to use updated `saveState` and `setOutput` functions from `@actions/core@^1.10.0`
|
||||||
|
|
||||||
### 3.1.0-beta.1
|
|
||||||
- Update `@actions/cache` on windows to use gnu tar and zstd by default and fallback to bsdtar and zstd if gnu tar is not available. ([issue](https://github.com/actions/cache/issues/984))
|
|
||||||
|
|
||||||
### 3.1.0-beta.2
|
|
||||||
- Added support for fallback to gzip to restore old caches on windows.
|
|
||||||
|
|
||||||
### 3.1.0-beta.3
|
|
||||||
- Bug fixes for bsdtar fallback if gnutar not available and gzip fallback if cache saved using old cache action on windows.
|
|
||||||
|
|
||||||
### 3.2.0-beta.1
|
|
||||||
- Added two new actions - [restore](restore/action.yml) and [save](save/action.yml) for granular control on cache.
|
|
||||||
|
|
||||||
### 3.2.0
|
|
||||||
- Released the two new actions - [restore](restore/action.yml) and [save](save/action.yml) for granular control on cache
|
|
||||||
|
|
||||||
### 3.2.1
|
|
||||||
- Update `@actions/cache` on windows to use gnu tar and zstd by default and fallback to bsdtar and zstd if gnu tar is not available. ([issue](https://github.com/actions/cache/issues/984))
|
|
||||||
- Added support for fallback to gzip to restore old caches on windows.
|
|
||||||
- Added logs for cache version in case of a cache miss.
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import * as cache from "@actions/cache";
|
import * as cache from "@actions/cache";
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
|
|
||||||
import { Events, RefKey } from "../src/constants";
|
import { Events, Outputs, RefKey, State } from "../src/constants";
|
||||||
import * as actionUtils from "../src/utils/actionUtils";
|
import * as actionUtils from "../src/utils/actionUtils";
|
||||||
import * as testUtils from "../src/utils/testUtils";
|
import * as testUtils from "../src/utils/testUtils";
|
||||||
|
|
||||||
@ -79,6 +79,83 @@ test("isExactKeyMatch with same key and different casing returns true", () => {
|
|||||||
expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(true);
|
expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("setOutputAndState with undefined entry to set cache-hit output", () => {
|
||||||
|
const key = "linux-rust";
|
||||||
|
const cacheKey = undefined;
|
||||||
|
|
||||||
|
const setOutputMock = jest.spyOn(core, "setOutput");
|
||||||
|
const saveStateMock = jest.spyOn(core, "saveState");
|
||||||
|
|
||||||
|
actionUtils.setOutputAndState(key, cacheKey);
|
||||||
|
|
||||||
|
expect(setOutputMock).toHaveBeenCalledWith(Outputs.CacheHit, "false");
|
||||||
|
expect(setOutputMock).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(saveStateMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("setOutputAndState with exact match to set cache-hit output and state", () => {
|
||||||
|
const key = "linux-rust";
|
||||||
|
const cacheKey = "linux-rust";
|
||||||
|
|
||||||
|
const setOutputMock = jest.spyOn(core, "setOutput");
|
||||||
|
const saveStateMock = jest.spyOn(core, "saveState");
|
||||||
|
|
||||||
|
actionUtils.setOutputAndState(key, cacheKey);
|
||||||
|
|
||||||
|
expect(setOutputMock).toHaveBeenCalledWith(Outputs.CacheHit, "true");
|
||||||
|
expect(setOutputMock).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(saveStateMock).toHaveBeenCalledWith(State.CacheMatchedKey, cacheKey);
|
||||||
|
expect(saveStateMock).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("setOutputAndState with no exact match to set cache-hit output and state", () => {
|
||||||
|
const key = "linux-rust";
|
||||||
|
const cacheKey = "linux-rust-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||||
|
|
||||||
|
const setOutputMock = jest.spyOn(core, "setOutput");
|
||||||
|
const saveStateMock = jest.spyOn(core, "saveState");
|
||||||
|
|
||||||
|
actionUtils.setOutputAndState(key, cacheKey);
|
||||||
|
|
||||||
|
expect(setOutputMock).toHaveBeenCalledWith(Outputs.CacheHit, "false");
|
||||||
|
expect(setOutputMock).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(saveStateMock).toHaveBeenCalledWith(State.CacheMatchedKey, cacheKey);
|
||||||
|
expect(saveStateMock).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getCacheState with no state returns undefined", () => {
|
||||||
|
const getStateMock = jest.spyOn(core, "getState");
|
||||||
|
getStateMock.mockImplementation(() => {
|
||||||
|
return "";
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = actionUtils.getCacheState();
|
||||||
|
|
||||||
|
expect(state).toBe(undefined);
|
||||||
|
|
||||||
|
expect(getStateMock).toHaveBeenCalledWith(State.CacheMatchedKey);
|
||||||
|
expect(getStateMock).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("getCacheState with valid state", () => {
|
||||||
|
const cacheKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||||
|
|
||||||
|
const getStateMock = jest.spyOn(core, "getState");
|
||||||
|
getStateMock.mockImplementation(() => {
|
||||||
|
return cacheKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = actionUtils.getCacheState();
|
||||||
|
|
||||||
|
expect(state).toEqual(cacheKey);
|
||||||
|
|
||||||
|
expect(getStateMock).toHaveBeenCalledWith(State.CacheMatchedKey);
|
||||||
|
expect(getStateMock).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
test("logWarning logs a message with a warning prefix", () => {
|
test("logWarning logs a message with a warning prefix", () => {
|
||||||
const message = "A warning occurred.";
|
const message = "A warning occurred.";
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import * as cache from "@actions/cache";
|
import * as cache from "@actions/cache";
|
||||||
import * as core from "@actions/core";
|
import * as core from "@actions/core";
|
||||||
|
|
||||||
import { Events, RefKey } from "../src/constants";
|
import { Events, Inputs, RefKey } from "../src/constants";
|
||||||
import run from "../src/restore";
|
import run from "../src/restore";
|
||||||
import * as actionUtils from "../src/utils/actionUtils";
|
import * as actionUtils from "../src/utils/actionUtils";
|
||||||
import * as testUtils from "../src/utils/testUtils";
|
import * as testUtils from "../src/utils/testUtils";
|
||||||
@ -45,6 +45,158 @@ afterEach(() => {
|
|||||||
delete process.env[RefKey];
|
delete process.env[RefKey];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("restore with invalid event outputs warning", async () => {
|
||||||
|
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
const invalidEvent = "commit_comment";
|
||||||
|
process.env[Events.Key] = invalidEvent;
|
||||||
|
delete process.env[RefKey];
|
||||||
|
await run();
|
||||||
|
expect(logWarningMock).toHaveBeenCalledWith(
|
||||||
|
`Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.`
|
||||||
|
);
|
||||||
|
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restore without AC available should no-op", async () => {
|
||||||
|
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
||||||
|
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||||
|
() => false
|
||||||
|
);
|
||||||
|
|
||||||
|
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||||
|
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput");
|
||||||
|
|
||||||
|
await run();
|
||||||
|
|
||||||
|
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||||
|
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(setCacheHitOutputMock).toHaveBeenCalledWith(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restore on GHES without AC available should no-op", async () => {
|
||||||
|
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||||
|
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||||
|
() => false
|
||||||
|
);
|
||||||
|
|
||||||
|
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||||
|
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput");
|
||||||
|
|
||||||
|
await run();
|
||||||
|
|
||||||
|
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||||
|
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(setCacheHitOutputMock).toHaveBeenCalledWith(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restore on GHES with AC available ", async () => {
|
||||||
|
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||||
|
const path = "node_modules";
|
||||||
|
const key = "node-test";
|
||||||
|
testUtils.setInputs({
|
||||||
|
path: path,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
|
||||||
|
const infoMock = jest.spyOn(core, "info");
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
const stateMock = jest.spyOn(core, "saveState");
|
||||||
|
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput");
|
||||||
|
const restoreCacheMock = jest
|
||||||
|
.spyOn(cache, "restoreCache")
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return Promise.resolve(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
await run();
|
||||||
|
|
||||||
|
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||||
|
|
||||||
|
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||||
|
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(setCacheHitOutputMock).toHaveBeenCalledWith(true);
|
||||||
|
|
||||||
|
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||||
|
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restore with no path should fail", async () => {
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||||
|
await run();
|
||||||
|
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||||
|
// this input isn't necessary for restore b/c tarball contains entries relative to workspace
|
||||||
|
expect(failedMock).not.toHaveBeenCalledWith(
|
||||||
|
"Input required and not supplied: path"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restore with no key", async () => {
|
||||||
|
testUtils.setInput(Inputs.Path, "node_modules");
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||||
|
await run();
|
||||||
|
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||||
|
expect(failedMock).toHaveBeenCalledWith(
|
||||||
|
"Input required and not supplied: key"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restore with too many keys should fail", async () => {
|
||||||
|
const path = "node_modules";
|
||||||
|
const key = "node-test";
|
||||||
|
const restoreKeys = [...Array(20).keys()].map(x => x.toString());
|
||||||
|
testUtils.setInputs({
|
||||||
|
path: path,
|
||||||
|
key,
|
||||||
|
restoreKeys
|
||||||
|
});
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||||
|
await run();
|
||||||
|
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, restoreKeys);
|
||||||
|
expect(failedMock).toHaveBeenCalledWith(
|
||||||
|
`Key Validation Error: Keys are limited to a maximum of 10.`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restore with large key should fail", async () => {
|
||||||
|
const path = "node_modules";
|
||||||
|
const key = "foo".repeat(512); // Over the 512 character limit
|
||||||
|
testUtils.setInputs({
|
||||||
|
path: path,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||||
|
await run();
|
||||||
|
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||||
|
expect(failedMock).toHaveBeenCalledWith(
|
||||||
|
`Key Validation Error: ${key} cannot be larger than 512 characters.`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restore with invalid key should fail", async () => {
|
||||||
|
const path = "node_modules";
|
||||||
|
const key = "comma,comma";
|
||||||
|
testUtils.setInputs({
|
||||||
|
path: path,
|
||||||
|
key
|
||||||
|
});
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||||
|
await run();
|
||||||
|
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||||
|
expect(failedMock).toHaveBeenCalledWith(
|
||||||
|
`Key Validation Error: ${key} cannot contain commas.`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test("restore with no cache found", async () => {
|
test("restore with no cache found", async () => {
|
||||||
const path = "node_modules";
|
const path = "node_modules";
|
||||||
const key = "node-test";
|
const key = "node-test";
|
||||||
@ -68,8 +220,6 @@ test("restore with no cache found", async () => {
|
|||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||||
|
|
||||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||||
expect(stateMock).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
expect(infoMock).toHaveBeenCalledWith(
|
expect(infoMock).toHaveBeenCalledWith(
|
||||||
@ -102,8 +252,6 @@ test("restore with restore keys and no cache found", async () => {
|
|||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]);
|
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]);
|
||||||
|
|
||||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||||
expect(stateMock).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
expect(infoMock).toHaveBeenCalledWith(
|
expect(infoMock).toHaveBeenCalledWith(
|
||||||
@ -122,7 +270,7 @@ test("restore with cache found for key", async () => {
|
|||||||
const infoMock = jest.spyOn(core, "info");
|
const infoMock = jest.spyOn(core, "info");
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
const stateMock = jest.spyOn(core, "saveState");
|
const stateMock = jest.spyOn(core, "saveState");
|
||||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput");
|
||||||
const restoreCacheMock = jest
|
const restoreCacheMock = jest
|
||||||
.spyOn(cache, "restoreCache")
|
.spyOn(cache, "restoreCache")
|
||||||
.mockImplementationOnce(() => {
|
.mockImplementationOnce(() => {
|
||||||
@ -135,11 +283,8 @@ test("restore with cache found for key", async () => {
|
|||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||||
|
|
||||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||||
expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", key);
|
|
||||||
expect(stateMock).toHaveBeenCalledTimes(2);
|
|
||||||
|
|
||||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true");
|
expect(setCacheHitOutputMock).toHaveBeenCalledWith(true);
|
||||||
|
|
||||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||||
@ -158,7 +303,7 @@ test("restore with cache found for restore key", async () => {
|
|||||||
const infoMock = jest.spyOn(core, "info");
|
const infoMock = jest.spyOn(core, "info");
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
const stateMock = jest.spyOn(core, "saveState");
|
const stateMock = jest.spyOn(core, "saveState");
|
||||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput");
|
||||||
const restoreCacheMock = jest
|
const restoreCacheMock = jest
|
||||||
.spyOn(cache, "restoreCache")
|
.spyOn(cache, "restoreCache")
|
||||||
.mockImplementationOnce(() => {
|
.mockImplementationOnce(() => {
|
||||||
@ -171,11 +316,9 @@ test("restore with cache found for restore key", async () => {
|
|||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]);
|
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]);
|
||||||
|
|
||||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||||
expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", restoreKey);
|
|
||||||
expect(stateMock).toHaveBeenCalledTimes(2);
|
|
||||||
|
|
||||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
|
expect(setCacheHitOutputMock).toHaveBeenCalledWith(false);
|
||||||
|
|
||||||
expect(infoMock).toHaveBeenCalledWith(
|
expect(infoMock).toHaveBeenCalledWith(
|
||||||
`Cache restored from key: ${restoreKey}`
|
`Cache restored from key: ${restoreKey}`
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,326 +0,0 @@
|
|||||||
import * as cache from "@actions/cache";
|
|
||||||
import * as core from "@actions/core";
|
|
||||||
|
|
||||||
import { Events, Inputs, RefKey } from "../src/constants";
|
|
||||||
import run from "../src/restoreImpl";
|
|
||||||
import { StateProvider } from "../src/stateProvider";
|
|
||||||
import * as actionUtils from "../src/utils/actionUtils";
|
|
||||||
import * as testUtils from "../src/utils/testUtils";
|
|
||||||
|
|
||||||
jest.mock("../src/utils/actionUtils");
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation(
|
|
||||||
(key, cacheResult) => {
|
|
||||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
|
||||||
return actualUtils.isExactKeyMatch(key, cacheResult);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => {
|
|
||||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
|
||||||
return actualUtils.isValidEvent();
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
|
||||||
(name, options) => {
|
|
||||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
|
||||||
return actualUtils.getInputAsArray(name, options);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
process.env[Events.Key] = Events.Push;
|
|
||||||
process.env[RefKey] = "refs/heads/feature-branch";
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
|
||||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
|
||||||
() => true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
testUtils.clearInputs();
|
|
||||||
delete process.env[Events.Key];
|
|
||||||
delete process.env[RefKey];
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore with invalid event outputs warning", async () => {
|
|
||||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const invalidEvent = "commit_comment";
|
|
||||||
process.env[Events.Key] = invalidEvent;
|
|
||||||
delete process.env[RefKey];
|
|
||||||
await run(new StateProvider());
|
|
||||||
expect(logWarningMock).toHaveBeenCalledWith(
|
|
||||||
`Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.`
|
|
||||||
);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore without AC available should no-op", async () => {
|
|
||||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
|
||||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
|
||||||
() => false
|
|
||||||
);
|
|
||||||
|
|
||||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
|
||||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
|
||||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore on GHES without AC available should no-op", async () => {
|
|
||||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
|
||||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
|
||||||
() => false
|
|
||||||
);
|
|
||||||
|
|
||||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
|
||||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
|
||||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore on GHES with AC available ", async () => {
|
|
||||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
|
||||||
const path = "node_modules";
|
|
||||||
const key = "node-test";
|
|
||||||
testUtils.setInputs({
|
|
||||||
path: path,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
const infoMock = jest.spyOn(core, "info");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const stateMock = jest.spyOn(core, "saveState");
|
|
||||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
|
||||||
const restoreCacheMock = jest
|
|
||||||
.spyOn(cache, "restoreCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(key);
|
|
||||||
});
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
|
||||||
|
|
||||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
|
||||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true");
|
|
||||||
|
|
||||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore with no path should fail", async () => {
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
|
||||||
await run(new StateProvider());
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
|
||||||
// this input isn't necessary for restore b/c tarball contains entries relative to workspace
|
|
||||||
expect(failedMock).not.toHaveBeenCalledWith(
|
|
||||||
"Input required and not supplied: path"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore with no key", async () => {
|
|
||||||
testUtils.setInput(Inputs.Path, "node_modules");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
|
||||||
await run(new StateProvider());
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
|
||||||
expect(failedMock).toHaveBeenCalledWith(
|
|
||||||
"Input required and not supplied: key"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore with too many keys should fail", async () => {
|
|
||||||
const path = "node_modules";
|
|
||||||
const key = "node-test";
|
|
||||||
const restoreKeys = [...Array(20).keys()].map(x => x.toString());
|
|
||||||
testUtils.setInputs({
|
|
||||||
path: path,
|
|
||||||
key,
|
|
||||||
restoreKeys
|
|
||||||
});
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
|
||||||
await run(new StateProvider());
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, restoreKeys);
|
|
||||||
expect(failedMock).toHaveBeenCalledWith(
|
|
||||||
`Key Validation Error: Keys are limited to a maximum of 10.`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore with large key should fail", async () => {
|
|
||||||
const path = "node_modules";
|
|
||||||
const key = "foo".repeat(512); // Over the 512 character limit
|
|
||||||
testUtils.setInputs({
|
|
||||||
path: path,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
|
||||||
await run(new StateProvider());
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
|
||||||
expect(failedMock).toHaveBeenCalledWith(
|
|
||||||
`Key Validation Error: ${key} cannot be larger than 512 characters.`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore with invalid key should fail", async () => {
|
|
||||||
const path = "node_modules";
|
|
||||||
const key = "comma,comma";
|
|
||||||
testUtils.setInputs({
|
|
||||||
path: path,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
|
||||||
await run(new StateProvider());
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
|
||||||
expect(failedMock).toHaveBeenCalledWith(
|
|
||||||
`Key Validation Error: ${key} cannot contain commas.`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore with no cache found", async () => {
|
|
||||||
const path = "node_modules";
|
|
||||||
const key = "node-test";
|
|
||||||
testUtils.setInputs({
|
|
||||||
path: path,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
const infoMock = jest.spyOn(core, "info");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const stateMock = jest.spyOn(core, "saveState");
|
|
||||||
const restoreCacheMock = jest
|
|
||||||
.spyOn(cache, "restoreCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
|
||||||
|
|
||||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
|
|
||||||
expect(infoMock).toHaveBeenCalledWith(
|
|
||||||
`Cache not found for input keys: ${key}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore with restore keys and no cache found", async () => {
|
|
||||||
const path = "node_modules";
|
|
||||||
const key = "node-test";
|
|
||||||
const restoreKey = "node-";
|
|
||||||
testUtils.setInputs({
|
|
||||||
path: path,
|
|
||||||
key,
|
|
||||||
restoreKeys: [restoreKey]
|
|
||||||
});
|
|
||||||
|
|
||||||
const infoMock = jest.spyOn(core, "info");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const stateMock = jest.spyOn(core, "saveState");
|
|
||||||
const restoreCacheMock = jest
|
|
||||||
.spyOn(cache, "restoreCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]);
|
|
||||||
|
|
||||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
|
|
||||||
expect(infoMock).toHaveBeenCalledWith(
|
|
||||||
`Cache not found for input keys: ${key}, ${restoreKey}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore with cache found for key", async () => {
|
|
||||||
const path = "node_modules";
|
|
||||||
const key = "node-test";
|
|
||||||
testUtils.setInputs({
|
|
||||||
path: path,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
const infoMock = jest.spyOn(core, "info");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const stateMock = jest.spyOn(core, "saveState");
|
|
||||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
|
||||||
const restoreCacheMock = jest
|
|
||||||
.spyOn(cache, "restoreCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(key);
|
|
||||||
});
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
|
||||||
|
|
||||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
|
||||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true");
|
|
||||||
|
|
||||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore with cache found for restore key", async () => {
|
|
||||||
const path = "node_modules";
|
|
||||||
const key = "node-test";
|
|
||||||
const restoreKey = "node-";
|
|
||||||
testUtils.setInputs({
|
|
||||||
path: path,
|
|
||||||
key,
|
|
||||||
restoreKeys: [restoreKey]
|
|
||||||
});
|
|
||||||
|
|
||||||
const infoMock = jest.spyOn(core, "info");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const stateMock = jest.spyOn(core, "saveState");
|
|
||||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
|
||||||
const restoreCacheMock = jest
|
|
||||||
.spyOn(cache, "restoreCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(restoreKey);
|
|
||||||
});
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]);
|
|
||||||
|
|
||||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
|
||||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
|
|
||||||
expect(infoMock).toHaveBeenCalledWith(
|
|
||||||
`Cache restored from key: ${restoreKey}`
|
|
||||||
);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
@ -1,177 +0,0 @@
|
|||||||
import * as cache from "@actions/cache";
|
|
||||||
import * as core from "@actions/core";
|
|
||||||
|
|
||||||
import { Events, RefKey } from "../src/constants";
|
|
||||||
import run from "../src/restoreOnly";
|
|
||||||
import * as actionUtils from "../src/utils/actionUtils";
|
|
||||||
import * as testUtils from "../src/utils/testUtils";
|
|
||||||
|
|
||||||
jest.mock("../src/utils/actionUtils");
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation(
|
|
||||||
(key, cacheResult) => {
|
|
||||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
|
||||||
return actualUtils.isExactKeyMatch(key, cacheResult);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => {
|
|
||||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
|
||||||
return actualUtils.isValidEvent();
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
|
||||||
(name, options) => {
|
|
||||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
|
||||||
return actualUtils.getInputAsArray(name, options);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
process.env[Events.Key] = Events.Push;
|
|
||||||
process.env[RefKey] = "refs/heads/feature-branch";
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
|
||||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
|
||||||
() => true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
testUtils.clearInputs();
|
|
||||||
delete process.env[Events.Key];
|
|
||||||
delete process.env[RefKey];
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore with no cache found", async () => {
|
|
||||||
const path = "node_modules";
|
|
||||||
const key = "node-test";
|
|
||||||
testUtils.setInputs({
|
|
||||||
path: path,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
const infoMock = jest.spyOn(core, "info");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const outputMock = jest.spyOn(core, "setOutput");
|
|
||||||
const restoreCacheMock = jest
|
|
||||||
.spyOn(cache, "restoreCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
await run();
|
|
||||||
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
|
||||||
|
|
||||||
expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key);
|
|
||||||
expect(outputMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
|
|
||||||
expect(infoMock).toHaveBeenCalledWith(
|
|
||||||
`Cache not found for input keys: ${key}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore with restore keys and no cache found", async () => {
|
|
||||||
const path = "node_modules";
|
|
||||||
const key = "node-test";
|
|
||||||
const restoreKey = "node-";
|
|
||||||
testUtils.setInputs({
|
|
||||||
path: path,
|
|
||||||
key,
|
|
||||||
restoreKeys: [restoreKey]
|
|
||||||
});
|
|
||||||
|
|
||||||
const infoMock = jest.spyOn(core, "info");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const outputMock = jest.spyOn(core, "setOutput");
|
|
||||||
const restoreCacheMock = jest
|
|
||||||
.spyOn(cache, "restoreCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(undefined);
|
|
||||||
});
|
|
||||||
|
|
||||||
await run();
|
|
||||||
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]);
|
|
||||||
|
|
||||||
expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
|
|
||||||
expect(infoMock).toHaveBeenCalledWith(
|
|
||||||
`Cache not found for input keys: ${key}, ${restoreKey}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore with cache found for key", async () => {
|
|
||||||
const path = "node_modules";
|
|
||||||
const key = "node-test";
|
|
||||||
testUtils.setInputs({
|
|
||||||
path: path,
|
|
||||||
key
|
|
||||||
});
|
|
||||||
|
|
||||||
const infoMock = jest.spyOn(core, "info");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const outputMock = jest.spyOn(core, "setOutput");
|
|
||||||
const restoreCacheMock = jest
|
|
||||||
.spyOn(cache, "restoreCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(key);
|
|
||||||
});
|
|
||||||
|
|
||||||
await run();
|
|
||||||
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
|
||||||
|
|
||||||
expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key);
|
|
||||||
expect(outputMock).toHaveBeenCalledWith("cache-hit", "true");
|
|
||||||
expect(outputMock).toHaveBeenCalledWith("cache-matched-key", key);
|
|
||||||
|
|
||||||
expect(outputMock).toHaveBeenCalledTimes(3);
|
|
||||||
|
|
||||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("restore with cache found for restore key", async () => {
|
|
||||||
const path = "node_modules";
|
|
||||||
const key = "node-test";
|
|
||||||
const restoreKey = "node-";
|
|
||||||
testUtils.setInputs({
|
|
||||||
path: path,
|
|
||||||
key,
|
|
||||||
restoreKeys: [restoreKey]
|
|
||||||
});
|
|
||||||
|
|
||||||
const infoMock = jest.spyOn(core, "info");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const outputMock = jest.spyOn(core, "setOutput");
|
|
||||||
const restoreCacheMock = jest
|
|
||||||
.spyOn(cache, "restoreCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(restoreKey);
|
|
||||||
});
|
|
||||||
|
|
||||||
await run();
|
|
||||||
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]);
|
|
||||||
|
|
||||||
expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key);
|
|
||||||
expect(outputMock).toHaveBeenCalledWith("cache-hit", "false");
|
|
||||||
expect(outputMock).toHaveBeenCalledWith("cache-matched-key", restoreKey);
|
|
||||||
|
|
||||||
expect(outputMock).toHaveBeenCalledTimes(3);
|
|
||||||
|
|
||||||
expect(infoMock).toHaveBeenCalledWith(
|
|
||||||
`Cache restored from key: ${restoreKey}`
|
|
||||||
);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
@ -15,8 +15,8 @@ beforeAll(() => {
|
|||||||
return jest.requireActual("@actions/core").getInput(name, options);
|
return jest.requireActual("@actions/core").getInput(name, options);
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.spyOn(core, "getState").mockImplementation(name => {
|
jest.spyOn(actionUtils, "getCacheState").mockImplementation(() => {
|
||||||
return jest.requireActual("@actions/core").getState(name);
|
return jest.requireActual("../src/utils/actionUtils").getCacheState();
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
||||||
@ -65,7 +65,72 @@ afterEach(() => {
|
|||||||
delete process.env[RefKey];
|
delete process.env[RefKey];
|
||||||
});
|
});
|
||||||
|
|
||||||
test("save with valid inputs uploads a cache", async () => {
|
test("save with invalid event outputs warning", async () => {
|
||||||
|
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
const invalidEvent = "commit_comment";
|
||||||
|
process.env[Events.Key] = invalidEvent;
|
||||||
|
delete process.env[RefKey];
|
||||||
|
await run();
|
||||||
|
expect(logWarningMock).toHaveBeenCalledWith(
|
||||||
|
`Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.`
|
||||||
|
);
|
||||||
|
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("save with no primary key in state outputs warning", async () => {
|
||||||
|
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
|
||||||
|
const savedCacheKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||||
|
jest.spyOn(core, "getState")
|
||||||
|
// Cache Entry State
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return savedCacheKey;
|
||||||
|
})
|
||||||
|
// Cache Key State
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return "";
|
||||||
|
});
|
||||||
|
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||||
|
|
||||||
|
await run();
|
||||||
|
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||||
|
expect(logWarningMock).toHaveBeenCalledWith(
|
||||||
|
`Error retrieving key from state.`
|
||||||
|
);
|
||||||
|
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("save without AC available should no-op", async () => {
|
||||||
|
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||||
|
() => false
|
||||||
|
);
|
||||||
|
|
||||||
|
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||||
|
|
||||||
|
await run();
|
||||||
|
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("save on ghes without AC available should no-op", async () => {
|
||||||
|
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||||
|
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||||
|
() => false
|
||||||
|
);
|
||||||
|
|
||||||
|
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||||
|
|
||||||
|
await run();
|
||||||
|
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("save on GHES with AC available", async () => {
|
||||||
|
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
|
||||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||||
@ -74,11 +139,234 @@ test("save with valid inputs uploads a cache", async () => {
|
|||||||
jest.spyOn(core, "getState")
|
jest.spyOn(core, "getState")
|
||||||
// Cache Entry State
|
// Cache Entry State
|
||||||
.mockImplementationOnce(() => {
|
.mockImplementationOnce(() => {
|
||||||
return primaryKey;
|
return savedCacheKey;
|
||||||
})
|
})
|
||||||
// Cache Key State
|
// Cache Key State
|
||||||
.mockImplementationOnce(() => {
|
.mockImplementationOnce(() => {
|
||||||
return savedCacheKey;
|
return primaryKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
const inputPath = "node_modules";
|
||||||
|
testUtils.setInput(Inputs.Path, inputPath);
|
||||||
|
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
||||||
|
|
||||||
|
const cacheId = 4;
|
||||||
|
const saveCacheMock = jest
|
||||||
|
.spyOn(cache, "saveCache")
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return Promise.resolve(cacheId);
|
||||||
|
});
|
||||||
|
|
||||||
|
await run();
|
||||||
|
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, {
|
||||||
|
uploadChunkSize: 4000000
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("save with exact match returns early", async () => {
|
||||||
|
const infoMock = jest.spyOn(core, "info");
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
|
||||||
|
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||||
|
const savedCacheKey = primaryKey;
|
||||||
|
|
||||||
|
jest.spyOn(core, "getState")
|
||||||
|
// Cache Entry State
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return savedCacheKey;
|
||||||
|
})
|
||||||
|
// Cache Key State
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return primaryKey;
|
||||||
|
});
|
||||||
|
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||||
|
|
||||||
|
await run();
|
||||||
|
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||||
|
expect(infoMock).toHaveBeenCalledWith(
|
||||||
|
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
|
||||||
|
);
|
||||||
|
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("save with missing input outputs warning", async () => {
|
||||||
|
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
|
||||||
|
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||||
|
const savedCacheKey = "Linux-node-";
|
||||||
|
|
||||||
|
jest.spyOn(core, "getState")
|
||||||
|
// Cache Entry State
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return savedCacheKey;
|
||||||
|
})
|
||||||
|
// Cache Key State
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return primaryKey;
|
||||||
|
});
|
||||||
|
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||||
|
|
||||||
|
await run();
|
||||||
|
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||||
|
expect(logWarningMock).toHaveBeenCalledWith(
|
||||||
|
"Input required and not supplied: path"
|
||||||
|
);
|
||||||
|
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("save with large cache outputs warning", async () => {
|
||||||
|
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
|
||||||
|
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||||
|
const savedCacheKey = "Linux-node-";
|
||||||
|
|
||||||
|
jest.spyOn(core, "getState")
|
||||||
|
// Cache Entry State
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return savedCacheKey;
|
||||||
|
})
|
||||||
|
// Cache Key State
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return primaryKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
const inputPath = "node_modules";
|
||||||
|
testUtils.setInput(Inputs.Path, inputPath);
|
||||||
|
|
||||||
|
const saveCacheMock = jest
|
||||||
|
.spyOn(cache, "saveCache")
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new Error(
|
||||||
|
"Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await run();
|
||||||
|
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||||
|
[inputPath],
|
||||||
|
primaryKey,
|
||||||
|
expect.anything()
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(logWarningMock).toHaveBeenCalledWith(
|
||||||
|
"Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache."
|
||||||
|
);
|
||||||
|
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("save with reserve cache failure outputs warning", async () => {
|
||||||
|
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
|
||||||
|
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||||
|
const savedCacheKey = "Linux-node-";
|
||||||
|
|
||||||
|
jest.spyOn(core, "getState")
|
||||||
|
// Cache Entry State
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return savedCacheKey;
|
||||||
|
})
|
||||||
|
// Cache Key State
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return primaryKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
const inputPath = "node_modules";
|
||||||
|
testUtils.setInput(Inputs.Path, inputPath);
|
||||||
|
|
||||||
|
const saveCacheMock = jest
|
||||||
|
.spyOn(cache, "saveCache")
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
const actualCache = jest.requireActual("@actions/cache");
|
||||||
|
const error = new actualCache.ReserveCacheError(
|
||||||
|
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
|
||||||
|
await run();
|
||||||
|
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||||
|
[inputPath],
|
||||||
|
primaryKey,
|
||||||
|
expect.anything()
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(logWarningMock).toHaveBeenCalledWith(
|
||||||
|
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
|
||||||
|
);
|
||||||
|
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("save with server error outputs warning", async () => {
|
||||||
|
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
|
||||||
|
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||||
|
const savedCacheKey = "Linux-node-";
|
||||||
|
|
||||||
|
jest.spyOn(core, "getState")
|
||||||
|
// Cache Entry State
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return savedCacheKey;
|
||||||
|
})
|
||||||
|
// Cache Key State
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return primaryKey;
|
||||||
|
});
|
||||||
|
|
||||||
|
const inputPath = "node_modules";
|
||||||
|
testUtils.setInput(Inputs.Path, inputPath);
|
||||||
|
|
||||||
|
const saveCacheMock = jest
|
||||||
|
.spyOn(cache, "saveCache")
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
throw new Error("HTTP Error Occurred");
|
||||||
|
});
|
||||||
|
|
||||||
|
await run();
|
||||||
|
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||||
|
[inputPath],
|
||||||
|
primaryKey,
|
||||||
|
expect.anything()
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||||
|
expect(logWarningMock).toHaveBeenCalledWith("HTTP Error Occurred");
|
||||||
|
|
||||||
|
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("save with valid inputs uploads a cache", async () => {
|
||||||
|
const failedMock = jest.spyOn(core, "setFailed");
|
||||||
|
|
||||||
|
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||||
|
const savedCacheKey = "Linux-node-";
|
||||||
|
|
||||||
|
jest.spyOn(core, "getState")
|
||||||
|
// Cache Entry State
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return savedCacheKey;
|
||||||
|
})
|
||||||
|
// Cache Key State
|
||||||
|
.mockImplementationOnce(() => {
|
||||||
|
return primaryKey;
|
||||||
});
|
});
|
||||||
|
|
||||||
const inputPath = "node_modules";
|
const inputPath = "node_modules";
|
||||||
|
|||||||
@ -1,386 +0,0 @@
|
|||||||
import * as cache from "@actions/cache";
|
|
||||||
import * as core from "@actions/core";
|
|
||||||
|
|
||||||
import { Events, Inputs, RefKey } from "../src/constants";
|
|
||||||
import run from "../src/saveImpl";
|
|
||||||
import { StateProvider } from "../src/stateProvider";
|
|
||||||
import * as actionUtils from "../src/utils/actionUtils";
|
|
||||||
import * as testUtils from "../src/utils/testUtils";
|
|
||||||
|
|
||||||
jest.mock("@actions/core");
|
|
||||||
jest.mock("@actions/cache");
|
|
||||||
jest.mock("../src/utils/actionUtils");
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
jest.spyOn(core, "getInput").mockImplementation((name, options) => {
|
|
||||||
return jest.requireActual("@actions/core").getInput(name, options);
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
|
||||||
(name, options) => {
|
|
||||||
return jest
|
|
||||||
.requireActual("../src/utils/actionUtils")
|
|
||||||
.getInputAsArray(name, options);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "getInputAsInt").mockImplementation(
|
|
||||||
(name, options) => {
|
|
||||||
return jest
|
|
||||||
.requireActual("../src/utils/actionUtils")
|
|
||||||
.getInputAsInt(name, options);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation(
|
|
||||||
(key, cacheResult) => {
|
|
||||||
return jest
|
|
||||||
.requireActual("../src/utils/actionUtils")
|
|
||||||
.isExactKeyMatch(key, cacheResult);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => {
|
|
||||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
|
||||||
return actualUtils.isValidEvent();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
process.env[Events.Key] = Events.Push;
|
|
||||||
process.env[RefKey] = "refs/heads/feature-branch";
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
|
||||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
|
||||||
() => true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
testUtils.clearInputs();
|
|
||||||
delete process.env[Events.Key];
|
|
||||||
delete process.env[RefKey];
|
|
||||||
});
|
|
||||||
|
|
||||||
test("save with invalid event outputs warning", async () => {
|
|
||||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
const invalidEvent = "commit_comment";
|
|
||||||
process.env[Events.Key] = invalidEvent;
|
|
||||||
delete process.env[RefKey];
|
|
||||||
await run(new StateProvider());
|
|
||||||
expect(logWarningMock).toHaveBeenCalledWith(
|
|
||||||
`Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.`
|
|
||||||
);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("save with no primary key in state outputs warning", async () => {
|
|
||||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
|
|
||||||
const savedCacheKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
|
||||||
jest.spyOn(core, "getState")
|
|
||||||
// Cache Entry State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return "";
|
|
||||||
})
|
|
||||||
// Cache Key State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return savedCacheKey;
|
|
||||||
});
|
|
||||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
|
||||||
expect(logWarningMock).toHaveBeenCalledWith(`Key is not specified.`);
|
|
||||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("save without AC available should no-op", async () => {
|
|
||||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
|
||||||
() => false
|
|
||||||
);
|
|
||||||
|
|
||||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("save on ghes without AC available should no-op", async () => {
|
|
||||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
|
||||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
|
||||||
() => false
|
|
||||||
);
|
|
||||||
|
|
||||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("save on GHES with AC available", async () => {
|
|
||||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
|
|
||||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
|
||||||
const savedCacheKey = "Linux-node-";
|
|
||||||
|
|
||||||
jest.spyOn(core, "getState")
|
|
||||||
// Cache Entry State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return savedCacheKey;
|
|
||||||
})
|
|
||||||
// Cache Key State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return primaryKey;
|
|
||||||
});
|
|
||||||
|
|
||||||
const inputPath = "node_modules";
|
|
||||||
testUtils.setInput(Inputs.Path, inputPath);
|
|
||||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
|
||||||
|
|
||||||
const cacheId = 4;
|
|
||||||
const saveCacheMock = jest
|
|
||||||
.spyOn(cache, "saveCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(cacheId);
|
|
||||||
});
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, {
|
|
||||||
uploadChunkSize: 4000000
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("save with exact match returns early", async () => {
|
|
||||||
const infoMock = jest.spyOn(core, "info");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
|
|
||||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
|
||||||
const savedCacheKey = primaryKey;
|
|
||||||
|
|
||||||
jest.spyOn(core, "getState")
|
|
||||||
// Cache Entry State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return savedCacheKey;
|
|
||||||
})
|
|
||||||
// Cache Key State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return primaryKey;
|
|
||||||
});
|
|
||||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
|
||||||
expect(infoMock).toHaveBeenCalledWith(
|
|
||||||
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
|
|
||||||
);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("save with missing input outputs warning", async () => {
|
|
||||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
|
|
||||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
|
||||||
const savedCacheKey = "Linux-node-";
|
|
||||||
|
|
||||||
jest.spyOn(core, "getState")
|
|
||||||
// Cache Entry State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return savedCacheKey;
|
|
||||||
})
|
|
||||||
// Cache Key State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return primaryKey;
|
|
||||||
});
|
|
||||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
|
||||||
expect(logWarningMock).toHaveBeenCalledWith(
|
|
||||||
"Input required and not supplied: path"
|
|
||||||
);
|
|
||||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("save with large cache outputs warning", async () => {
|
|
||||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
|
|
||||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
|
||||||
const savedCacheKey = "Linux-node-";
|
|
||||||
|
|
||||||
jest.spyOn(core, "getState")
|
|
||||||
// Cache Entry State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return savedCacheKey;
|
|
||||||
})
|
|
||||||
// Cache Key State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return primaryKey;
|
|
||||||
});
|
|
||||||
|
|
||||||
const inputPath = "node_modules";
|
|
||||||
testUtils.setInput(Inputs.Path, inputPath);
|
|
||||||
|
|
||||||
const saveCacheMock = jest
|
|
||||||
.spyOn(cache, "saveCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
throw new Error(
|
|
||||||
"Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache."
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
|
||||||
[inputPath],
|
|
||||||
primaryKey,
|
|
||||||
expect.anything()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(logWarningMock).toHaveBeenCalledWith(
|
|
||||||
"Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache."
|
|
||||||
);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("save with reserve cache failure outputs warning", async () => {
|
|
||||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
|
|
||||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
|
||||||
const savedCacheKey = "Linux-node-";
|
|
||||||
|
|
||||||
jest.spyOn(core, "getState")
|
|
||||||
// Cache Entry State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return savedCacheKey;
|
|
||||||
})
|
|
||||||
// Cache Key State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return primaryKey;
|
|
||||||
});
|
|
||||||
|
|
||||||
const inputPath = "node_modules";
|
|
||||||
testUtils.setInput(Inputs.Path, inputPath);
|
|
||||||
|
|
||||||
const saveCacheMock = jest
|
|
||||||
.spyOn(cache, "saveCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
const actualCache = jest.requireActual("@actions/cache");
|
|
||||||
const error = new actualCache.ReserveCacheError(
|
|
||||||
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
|
|
||||||
);
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
|
||||||
[inputPath],
|
|
||||||
primaryKey,
|
|
||||||
expect.anything()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(logWarningMock).toHaveBeenCalledWith(
|
|
||||||
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
|
|
||||||
);
|
|
||||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("save with server error outputs warning", async () => {
|
|
||||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
|
|
||||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
|
||||||
const savedCacheKey = "Linux-node-";
|
|
||||||
|
|
||||||
jest.spyOn(core, "getState")
|
|
||||||
// Cache Entry State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return savedCacheKey;
|
|
||||||
})
|
|
||||||
// Cache Key State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return primaryKey;
|
|
||||||
});
|
|
||||||
|
|
||||||
const inputPath = "node_modules";
|
|
||||||
testUtils.setInput(Inputs.Path, inputPath);
|
|
||||||
|
|
||||||
const saveCacheMock = jest
|
|
||||||
.spyOn(cache, "saveCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
throw new Error("HTTP Error Occurred");
|
|
||||||
});
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
|
||||||
[inputPath],
|
|
||||||
primaryKey,
|
|
||||||
expect.anything()
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(logWarningMock).toHaveBeenCalledWith("HTTP Error Occurred");
|
|
||||||
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("save with valid inputs uploads a cache", async () => {
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
|
|
||||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
|
||||||
const savedCacheKey = "Linux-node-";
|
|
||||||
|
|
||||||
jest.spyOn(core, "getState")
|
|
||||||
// Cache Entry State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return savedCacheKey;
|
|
||||||
})
|
|
||||||
// Cache Key State
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return primaryKey;
|
|
||||||
});
|
|
||||||
|
|
||||||
const inputPath = "node_modules";
|
|
||||||
testUtils.setInput(Inputs.Path, inputPath);
|
|
||||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
|
||||||
|
|
||||||
const cacheId = 4;
|
|
||||||
const saveCacheMock = jest
|
|
||||||
.spyOn(cache, "saveCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(cacheId);
|
|
||||||
});
|
|
||||||
|
|
||||||
await run(new StateProvider());
|
|
||||||
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, {
|
|
||||||
uploadChunkSize: 4000000
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
import * as cache from "@actions/cache";
|
|
||||||
import * as core from "@actions/core";
|
|
||||||
|
|
||||||
import { Events, Inputs, RefKey } from "../src/constants";
|
|
||||||
import run from "../src/saveOnly";
|
|
||||||
import * as actionUtils from "../src/utils/actionUtils";
|
|
||||||
import * as testUtils from "../src/utils/testUtils";
|
|
||||||
|
|
||||||
jest.mock("@actions/core");
|
|
||||||
jest.mock("@actions/cache");
|
|
||||||
jest.mock("../src/utils/actionUtils");
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
jest.spyOn(core, "getInput").mockImplementation((name, options) => {
|
|
||||||
return jest.requireActual("@actions/core").getInput(name, options);
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.spyOn(core, "setOutput").mockImplementation((key, value) => {
|
|
||||||
return jest.requireActual("@actions/core").getInput(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
|
||||||
(name, options) => {
|
|
||||||
return jest
|
|
||||||
.requireActual("../src/utils/actionUtils")
|
|
||||||
.getInputAsArray(name, options);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "getInputAsInt").mockImplementation(
|
|
||||||
(name, options) => {
|
|
||||||
return jest
|
|
||||||
.requireActual("../src/utils/actionUtils")
|
|
||||||
.getInputAsInt(name, options);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation(
|
|
||||||
(key, cacheResult) => {
|
|
||||||
return jest
|
|
||||||
.requireActual("../src/utils/actionUtils")
|
|
||||||
.isExactKeyMatch(key, cacheResult);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => {
|
|
||||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
|
||||||
return actualUtils.isValidEvent();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
process.env[Events.Key] = Events.Push;
|
|
||||||
process.env[RefKey] = "refs/heads/feature-branch";
|
|
||||||
|
|
||||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
|
||||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
|
||||||
() => true
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
testUtils.clearInputs();
|
|
||||||
delete process.env[Events.Key];
|
|
||||||
delete process.env[RefKey];
|
|
||||||
});
|
|
||||||
|
|
||||||
test("save with valid inputs uploads a cache", async () => {
|
|
||||||
const failedMock = jest.spyOn(core, "setFailed");
|
|
||||||
|
|
||||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
|
||||||
|
|
||||||
const inputPath = "node_modules";
|
|
||||||
testUtils.setInput(Inputs.Key, primaryKey);
|
|
||||||
testUtils.setInput(Inputs.Path, inputPath);
|
|
||||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
|
||||||
|
|
||||||
const cacheId = 4;
|
|
||||||
const saveCacheMock = jest
|
|
||||||
.spyOn(cache, "saveCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(cacheId);
|
|
||||||
});
|
|
||||||
|
|
||||||
await run();
|
|
||||||
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, {
|
|
||||||
uploadChunkSize: 4000000
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("save failing logs the warning message", async () => {
|
|
||||||
const warningMock = jest.spyOn(core, "warning");
|
|
||||||
|
|
||||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
|
||||||
|
|
||||||
const inputPath = "node_modules";
|
|
||||||
testUtils.setInput(Inputs.Key, primaryKey);
|
|
||||||
testUtils.setInput(Inputs.Path, inputPath);
|
|
||||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
|
||||||
|
|
||||||
const cacheId = -1;
|
|
||||||
const saveCacheMock = jest
|
|
||||||
.spyOn(cache, "saveCache")
|
|
||||||
.mockImplementationOnce(() => {
|
|
||||||
return Promise.resolve(cacheId);
|
|
||||||
});
|
|
||||||
|
|
||||||
await run();
|
|
||||||
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, {
|
|
||||||
uploadChunkSize: 4000000
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(warningMock).toHaveBeenCalledTimes(1);
|
|
||||||
expect(warningMock).toHaveBeenCalledWith("Cache save failed.");
|
|
||||||
});
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
import * as core from "@actions/core";
|
|
||||||
|
|
||||||
import { Events, RefKey, State } from "../src/constants";
|
|
||||||
import {
|
|
||||||
IStateProvider,
|
|
||||||
NullStateProvider,
|
|
||||||
StateProvider
|
|
||||||
} from "../src/stateProvider";
|
|
||||||
|
|
||||||
jest.mock("@actions/core");
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
jest.spyOn(core, "getInput").mockImplementation((name, options) => {
|
|
||||||
return jest.requireActual("@actions/core").getInput(name, options);
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.spyOn(core, "setOutput").mockImplementation((key, value) => {
|
|
||||||
return jest.requireActual("@actions/core").setOutput(key, value);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
delete process.env[Events.Key];
|
|
||||||
delete process.env[RefKey];
|
|
||||||
});
|
|
||||||
|
|
||||||
test("StateProvider saves states", async () => {
|
|
||||||
const states = new Map<string, string>();
|
|
||||||
const getStateMock = jest
|
|
||||||
.spyOn(core, "getState")
|
|
||||||
.mockImplementation(key => states.get(key) || "");
|
|
||||||
|
|
||||||
const saveStateMock = jest
|
|
||||||
.spyOn(core, "saveState")
|
|
||||||
.mockImplementation((key, value) => {
|
|
||||||
states.set(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
const setOutputMock = jest
|
|
||||||
.spyOn(core, "setOutput")
|
|
||||||
.mockImplementation((key, value) => {
|
|
||||||
return jest.requireActual("@actions/core").setOutput(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
const cacheMatchedKey = "node-cache";
|
|
||||||
|
|
||||||
const stateProvider: IStateProvider = new StateProvider();
|
|
||||||
stateProvider.setState("stateKey", "stateValue");
|
|
||||||
stateProvider.setState(State.CacheMatchedKey, cacheMatchedKey);
|
|
||||||
const stateValue = stateProvider.getState("stateKey");
|
|
||||||
const cacheStateValue = stateProvider.getCacheState();
|
|
||||||
|
|
||||||
expect(stateValue).toBe("stateValue");
|
|
||||||
expect(cacheStateValue).toBe(cacheMatchedKey);
|
|
||||||
expect(getStateMock).toHaveBeenCalledTimes(2);
|
|
||||||
expect(saveStateMock).toHaveBeenCalledTimes(2);
|
|
||||||
expect(setOutputMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test("NullStateProvider saves outputs", async () => {
|
|
||||||
const getStateMock = jest
|
|
||||||
.spyOn(core, "getState")
|
|
||||||
.mockImplementation(name =>
|
|
||||||
jest.requireActual("@actions/core").getState(name)
|
|
||||||
);
|
|
||||||
|
|
||||||
const setOutputMock = jest
|
|
||||||
.spyOn(core, "setOutput")
|
|
||||||
.mockImplementation((key, value) => {
|
|
||||||
return jest.requireActual("@actions/core").setOutput(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
const saveStateMock = jest
|
|
||||||
.spyOn(core, "saveState")
|
|
||||||
.mockImplementation((key, value) => {
|
|
||||||
return jest.requireActual("@actions/core").saveState(key, value);
|
|
||||||
});
|
|
||||||
|
|
||||||
const cacheMatchedKey = "node-cache";
|
|
||||||
const nullStateProvider: IStateProvider = new NullStateProvider();
|
|
||||||
nullStateProvider.setState(State.CacheMatchedKey, "outputValue");
|
|
||||||
nullStateProvider.setState(State.CachePrimaryKey, cacheMatchedKey);
|
|
||||||
nullStateProvider.getState("outputKey");
|
|
||||||
nullStateProvider.getCacheState();
|
|
||||||
|
|
||||||
expect(getStateMock).toHaveBeenCalledTimes(0);
|
|
||||||
expect(setOutputMock).toHaveBeenCalledTimes(2);
|
|
||||||
expect(saveStateMock).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
@ -21,7 +21,7 @@ runs:
|
|||||||
using: 'node16'
|
using: 'node16'
|
||||||
main: 'dist/restore/index.js'
|
main: 'dist/restore/index.js'
|
||||||
post: 'dist/save/index.js'
|
post: 'dist/save/index.js'
|
||||||
post-if: success()
|
post-if: 'success()'
|
||||||
branding:
|
branding:
|
||||||
icon: 'archive'
|
icon: 'archive'
|
||||||
color: 'gray-dark'
|
color: 'gray-dark'
|
||||||
|
|||||||
61183
dist/restore-only/index.js
vendored
61183
dist/restore-only/index.js
vendored
File diff suppressed because one or more lines are too long
1127
dist/restore/index.js
vendored
1127
dist/restore/index.js
vendored
File diff suppressed because it is too large
Load Diff
61218
dist/save-only/index.js
vendored
61218
dist/save-only/index.js
vendored
File diff suppressed because one or more lines are too long
1124
dist/save/index.js
vendored
1124
dist/save/index.js
vendored
File diff suppressed because it is too large
Load Diff
19
examples.md
19
examples.md
@ -309,29 +309,14 @@ We cache the elements of the Cabal store separately, as the entirety of `~/.caba
|
|||||||
For npm, cache files are stored in `~/.npm` on Posix, or `~\AppData\npm-cache` on Windows, but it's possible to use `npm config get cache` to find the path on any platform. See [the npm docs](https://docs.npmjs.com/cli/cache#cache) for more details.
|
For npm, cache files are stored in `~/.npm` on Posix, or `~\AppData\npm-cache` on Windows, but it's possible to use `npm config get cache` to find the path on any platform. See [the npm docs](https://docs.npmjs.com/cli/cache#cache) for more details.
|
||||||
|
|
||||||
If using `npm config` to retrieve the cache directory, ensure you run [actions/setup-node](https://github.com/actions/setup-node) first to ensure your `npm` version is correct.
|
If using `npm config` to retrieve the cache directory, ensure you run [actions/setup-node](https://github.com/actions/setup-node) first to ensure your `npm` version is correct.
|
||||||
After [deprecation](https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/) of save-state and set-output commands, the correct way to set output is using `${GITHUB_OUTPUT}`. For linux, we can use `${GITHUB_OUTPUT}` whereas for windows we need to use `${env:GITHUB_OUTPUT}` due to two different default shells in these two different OS ie `bash` and `pwsh` respectively.
|
|
||||||
|
|
||||||
>Note: It is not recommended to cache `node_modules`, as it can break across Node versions and won't work with `npm ci`
|
>Note: It is not recommended to cache `node_modules`, as it can break across Node versions and won't work with `npm ci`
|
||||||
|
|
||||||
### **Get npm cache directory using same shell**
|
|
||||||
### Bash shell
|
|
||||||
```yaml
|
```yaml
|
||||||
- name: Get npm cache directory
|
- name: Get npm cache directory
|
||||||
id: npm-cache-dir
|
id: npm-cache-dir
|
||||||
shell: bash
|
run: |
|
||||||
run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT}
|
echo "::set-output name=dir::$(npm config get cache)"
|
||||||
```
|
|
||||||
|
|
||||||
### PWSH shell
|
|
||||||
```yaml
|
|
||||||
- name: Get npm cache directory
|
|
||||||
id: npm-cache-dir
|
|
||||||
shell: pwsh
|
|
||||||
run: echo "dir=$(npm config get cache)" >> ${env:GITHUB_OUTPUT}
|
|
||||||
```
|
|
||||||
`Get npm cache directory` step can then be used with `actions/cache` as shown below
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
- uses: actions/cache@v3
|
- uses: actions/cache@v3
|
||||||
id: npm-cache # use this to check for `cache-hit` ==> if: steps.npm-cache.outputs.cache-hit != 'true'
|
id: npm-cache # use this to check for `cache-hit` ==> if: steps.npm-cache.outputs.cache-hit != 'true'
|
||||||
with:
|
with:
|
||||||
|
|||||||
6528
package-lock.json
generated
6528
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "cache",
|
"name": "cache",
|
||||||
"version": "3.2.1",
|
"version": "3.0.11",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Cache dependencies and build outputs",
|
"description": "Cache dependencies and build outputs",
|
||||||
"main": "dist/restore/index.js",
|
"main": "dist/restore/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && ncc build -o dist/restore src/restore.ts && ncc build -o dist/save src/save.ts && ncc build -o dist/restore-only src/restoreOnly.ts && ncc build -o dist/save-only src/saveOnly.ts",
|
"build": "tsc && ncc build -o dist/restore src/restore.ts && ncc build -o dist/save src/save.ts",
|
||||||
"test": "tsc --noEmit && jest --coverage",
|
"test": "tsc --noEmit && jest --coverage",
|
||||||
"lint": "eslint **/*.ts --cache",
|
"lint": "eslint **/*.ts --cache",
|
||||||
"format": "prettier --write **/*.ts",
|
"format": "prettier --write **/*.ts",
|
||||||
@ -23,29 +23,29 @@
|
|||||||
"author": "GitHub",
|
"author": "GitHub",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@actions/cache": "^3.1.0",
|
"@actions/cache": "^3.0.5",
|
||||||
"@actions/core": "^1.10.0",
|
"@actions/core": "^1.10.0",
|
||||||
"@actions/exec": "^1.1.1",
|
"@actions/exec": "^1.1.1",
|
||||||
"@actions/io": "^1.1.2"
|
"@actions/io": "^1.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^27.5.2",
|
"@types/jest": "^27.5.0",
|
||||||
"@types/nock": "^11.1.0",
|
"@types/nock": "^11.1.0",
|
||||||
"@types/node": "^16.18.3",
|
"@types/node": "^16.11.33",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.45.0",
|
"@typescript-eslint/eslint-plugin": "^5.22.0",
|
||||||
"@typescript-eslint/parser": "^5.45.0",
|
"@typescript-eslint/parser": "^5.22.0",
|
||||||
"@zeit/ncc": "^0.20.5",
|
"@zeit/ncc": "^0.20.5",
|
||||||
"eslint": "^8.28.0",
|
"eslint": "^8.14.0",
|
||||||
"eslint-config-prettier": "^8.5.0",
|
"eslint-config-prettier": "^8.5.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-jest": "^26.9.0",
|
"eslint-plugin-jest": "^26.1.5",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
"eslint-plugin-simple-import-sort": "^7.0.0",
|
"eslint-plugin-simple-import-sort": "^7.0.0",
|
||||||
"jest": "^28.1.3",
|
"jest": "^28.0.3",
|
||||||
"jest-circus": "^27.5.1",
|
"jest-circus": "^27.5.1",
|
||||||
"nock": "^13.2.9",
|
"nock": "^13.2.4",
|
||||||
"prettier": "^2.8.0",
|
"prettier": "^2.6.2",
|
||||||
"ts-jest": "^28.0.8",
|
"ts-jest": "^28.0.2",
|
||||||
"typescript": "^4.9.3"
|
"typescript": "^4.6.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,131 +0,0 @@
|
|||||||
# Restore action
|
|
||||||
|
|
||||||
The restore action, as the name suggest, restores a cache. It acts similar to the`cache` action except that it doesn't have a post step to save the cache. This action can provide you a granular control to only restore a cache without having to necessarily save it. It accepts the same set of inputs as the `cache` action.
|
|
||||||
|
|
||||||
## Inputs
|
|
||||||
|
|
||||||
* `path` - A list of files, directories, and wildcard patterns to cache and restore. See [`@actions/glob`](https://github.com/actions/toolkit/tree/main/packages/glob) for supported patterns.
|
|
||||||
* `key` - String used while saving cache for restoring the cache
|
|
||||||
* `restore-keys` - An ordered list of prefix-matched keys to use for restoring stale cache if no cache hit occurred for key.
|
|
||||||
|
|
||||||
## Outputs
|
|
||||||
|
|
||||||
* `cache-hit` - A boolean value to indicate an exact match was found for the key.
|
|
||||||
* `cache-primary-key` - Cache primary key passed in the input to use in subsequent steps of the workflow.
|
|
||||||
* `cache-matched-key` - Key of the cache that was restored, it could either be the primary key on cache-hit or a partial/complete match of one of the restore keys.
|
|
||||||
|
|
||||||
> **Note**
|
|
||||||
`cache-hit` will be set to `true` only when cache hit occurs for the exact `key` match. For a partial key match via `restore-keys` or a cache miss, it will be set to `false`.
|
|
||||||
|
|
||||||
### Environment Variables
|
|
||||||
* `SEGMENT_DOWNLOAD_TIMEOUT_MINS` - Segment download timeout (in minutes, default `60`) to abort download of the segment if not completed in the defined number of minutes. [Read more](https://github.com/actions/cache/blob/main/workarounds.md#cache-segment-restore-timeout)
|
|
||||||
|
|
||||||
## Use cases
|
|
||||||
|
|
||||||
As this is a newly introduced action to give users more control in their workflows, below are some use cases where one can use this action.
|
|
||||||
|
|
||||||
### Only restore cache
|
|
||||||
|
|
||||||
In case you are using another workflow to create and save your cache that can be reused by other jobs in your repository, this action will take care of your restore only needs.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/cache/restore@v3
|
|
||||||
id: cache
|
|
||||||
with:
|
|
||||||
path: path/to/dependencies
|
|
||||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
run: /install.sh
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: /build.sh
|
|
||||||
|
|
||||||
- name: Publish package to public
|
|
||||||
run: /publish.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
Once the cache is restored, this action won't run any post step to do post-processing like `actions/cache` and the rest of the workflow will run as usual.
|
|
||||||
|
|
||||||
### Save intermediate private build artifacts
|
|
||||||
|
|
||||||
In case of multi-module projects, where the built artifact of one project needs to be reused in subsequent child modules, the need of rebuilding the parent module again and again with every build can be eliminated. The `actions/cache` or `actions/cache/save` action can be used to build and save the parent module artifact once, and restored multiple times while building the child modules.
|
|
||||||
|
|
||||||
|
|
||||||
#### Step 1 - Build the parent module and save it
|
|
||||||
```yaml
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: /build-parent-module.sh
|
|
||||||
|
|
||||||
- uses: actions/cache/save@v3
|
|
||||||
id: cache
|
|
||||||
with:
|
|
||||||
path: path/to/dependencies
|
|
||||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step 2 - Restore the built artifact from cache using the same key and path
|
|
||||||
```yaml
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/cache/restore@v3
|
|
||||||
id: cache
|
|
||||||
with:
|
|
||||||
path: path/to/dependencies
|
|
||||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
|
||||||
|
|
||||||
- name: Install Dependencies
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
run: /install.sh
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: /build-child-module.sh
|
|
||||||
|
|
||||||
- name: Publish package to public
|
|
||||||
run: /publish.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Exit workflow on cache miss
|
|
||||||
|
|
||||||
You can use the output of this action to exit the workflow on cache miss. This way you can restrict your workflow to only initiate the build when `cache-hit` occurs, in other words, cache with exact key is found.
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- uses: actions/cache/restore@v3
|
|
||||||
id: cache
|
|
||||||
with:
|
|
||||||
path: path/to/dependencies
|
|
||||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
|
||||||
|
|
||||||
- name: Check cache hit
|
|
||||||
if: steps.cache.outputs.cache-hit != 'true'
|
|
||||||
run: exit 1
|
|
||||||
|
|
||||||
- name: Build
|
|
||||||
run: /build.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
|
|
||||||
|
|
||||||
#### Reusing primary key and restored key in the save action
|
|
||||||
|
|
||||||
Usually you may want to use same `key` in both actions/cache/restore` and `actions/cache/save` action. To achieve this, use `outputs` from the restore action to reuse the same primary key (or the key of the cache that was restored).
|
|
||||||
|
|
||||||
#### Using restore action outputs to make save action behave just like the cache action
|
|
||||||
|
|||||||